Skip to content
Snippets Groups Projects
import_obj.py 55.4 KiB
Newer Older
# SPDX-License-Identifier: GPL-2.0-or-later

# Script copyright (C) Campbell Barton
# Contributors: Campbell Barton, Jiri Hnidek, Paolo Ciccone

"""
This script imports a Wavefront OBJ files to Blender.

Usage:
Run this script from "File->Import" menu and then load the desired OBJ file.
Note, This loads mesh objects and materials only, nurbs and curves are not supported.

http://wiki.blender.org/index.php/Scripts/Manual/Import/wavefront_obj
"""

import os
import time
import bpy
import mathutils
from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
from bpy_extras.wm_utils.progress_report import ProgressReport
    Returns 1 string representing the value for this line
    None will be returned if there's only 1 word
Campbell Barton's avatar
Campbell Barton committed
    length = len(line_split)
    if length == 1:
        return None

    elif length == 2:
        return line_split[1]

    elif length > 2:
        return b' '.join(line_split[1:])
def filenames_group_by_ext(line, ext):
    """
    Splits material libraries supporting spaces, so:
    b'foo bar.mtl baz spam.MTL' -> (b'foo bar.mtl', b'baz spam.MTL')
    Also handle " chars (some software use those to protect filenames with spaces, see T67266... sic).
    # Note that we assume that if there are some " in that line,
    # then all filenames are properly enclosed within those...
    start = line.find(b'"') + 1
    if start != 0:
        while start != 0:
            end = line.find(b'"', start)
            if end != -1:
                yield line[start:end]
                start = line.find(b'"', end + 1) + 1
            else:
                break
        return

    line_lower = line.lower()
    i_prev = 0
    while i_prev != -1 and i_prev < len(line):
        i = line_lower.find(ext, i_prev)
        if i != -1:
            i += len(ext)
        yield line[i_prev:i].strip()
        i_prev = i


def obj_image_load(img_data, context_imagepath_map, line, DIR, recursive, relpath):
    Mainly uses comprehensiveImageLoad
    But we try all space-separated items from current line when file is not found with last one
    (users keep generating/using image files with spaces in a format that does not support them, sigh...)
    Also tries to replace '_' with ' ' for Max's exporter replaces spaces with underscores.
    Also handle " chars (some software use those to protect filenames with spaces, see T67266... sic).
    Also corrects img_data (in case filenames with spaces have been split up in multiple entries, see T72148).

    start = line.find(b'"') + 1
    if start != 0:
        end = line.find(b'"', start)
        if end != 0:
            filepath_parts = (line[start:end],)

    image = None
    for i in range(-1, -len(filepath_parts), -1):
        imagepath = os.fsdecode(b" ".join(filepath_parts[i:]))
        image = context_imagepath_map.get(imagepath, ...)
        if image is ...:
            image = load_image(imagepath, DIR, recursive=recursive, relpath=relpath)
            if image is None and "_" in imagepath:
                image = load_image(imagepath.replace("_", " "), DIR, recursive=recursive, relpath=relpath)
            if image is not None:
                context_imagepath_map[imagepath] = image
                del img_data[i:]
                img_data.append(imagepath)
        else:
            del img_data[i:]
            img_data.append(imagepath)
            break;

    if image is None:
        imagepath = os.fsdecode(filepath_parts[-1])
        image = load_image(imagepath, DIR, recursive=recursive, place_holder=True, relpath=relpath)
        context_imagepath_map[imagepath] = image
                     material_libs, unique_materials,
                     use_image_search, float_func):
    Create all the used materials in this obj,
    assign colors and images to the materials from all referenced material libs
    from math import sqrt
    from bpy_extras import node_shader_utils
Campbell Barton's avatar
Campbell Barton committed
    DIR = os.path.dirname(filepath)
    # Don't load the same image multiple times
    context_imagepath_map = {}

    nodal_material_wrap_map = {}
    def load_material_image(blender_material, mat_wrap, context_material_name, img_data, line, type):
        """
        Set textures defined in .mtl file.
        """
        # Absolute path - c:\.. etc would work here
        image = obj_image_load(img_data, context_imagepath_map, line, DIR, use_image_search, relpath)

        curr_token = []
        for token in img_data[:-1]:
            if token.startswith(b'-') and token[1:].isalpha():
                if curr_token:
                    map_options[curr_token[0]] = curr_token[1:]
                curr_token[:] = []
            curr_token.append(token)
        if curr_token:
            map_options[curr_token[0]] = curr_token[1:]
        map_offset = map_options.get(b'-o')
        map_scale = map_options.get(b'-s')
        if map_offset is not None:
            map_offset = tuple(map(float_func, map_offset))
        if map_scale is not None:
            map_scale = tuple(map(float_func, map_scale))
        def _generic_tex_set(nodetex, image, texcoords, translation, scale):
            nodetex.image = image
            nodetex.texcoords = texcoords
            if translation is not None:
                nodetex.translation = translation
            if scale is not None:
                nodetex.scale = scale

        # Adds textures for materials (rendering)
        if type == 'Kd':
            _generic_tex_set(mat_wrap.base_color_texture, image, 'UV', map_offset, map_scale)
            # XXX Not supported?
            print("WARNING, currently unsupported ambient texture, skipped.")
            _generic_tex_set(mat_wrap.specular_texture, image, 'UV', map_offset, map_scale)
            _generic_tex_set(mat_wrap.emission_color_texture, image, 'UV', map_offset, map_scale)
            bump_mult = map_options.get(b'-bm')
            bump_mult = float(bump_mult[0]) if (bump_mult and len(bump_mult[0]) > 1) else 1.0
            mat_wrap.normalmap_strength_set(bump_mult)
            _generic_tex_set(mat_wrap.normalmap_texture, image, 'UV', map_offset, map_scale)
            _generic_tex_set(mat_wrap.alpha_texture, image, 'UV', map_offset, map_scale)
            # XXX Not supported?
            print("WARNING, currently unsupported displacement texture, skipped.")
            # ~ mat_wrap.bump_image_set(image)
            # ~ mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale)
            map_type = map_options.get(b'-type')
            if map_type and map_type != [b'sphere']:
Loading
Loading full blame...