Skip to content

blender

plantimager.blender Link

Camera Link

Camera(scene, data, hdri_enabled=False)
Source code in plantimager/blender.py
188
189
190
191
192
193
194
195
196
197
def __init__(self, scene, data, hdri_enabled=False):
    self.scene = scene
    self.cam = scene.camera
    self.render = scene.render
    self.data = data
    self.hdri_enabled = hdri_enabled
    if hdri_enabled:
        self.setup_hdri()
    else:
        self.setup_background()

set_intrinsics Link

set_intrinsics(width, height, focal)

:input w image width :input h image height :input f focal length (equiv. 35mm)

Source code in plantimager/blender.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def set_intrinsics(self, width, height, focal):
    """
    :input w image width
    :input h image height
    :input f focal length (equiv. 35mm)
    """

    self.render.resolution_x = width
    self.render.resolution_y = height
    self.render.resolution_percentage = 100

    # Set camera fov in degrees
    self.cam.data.angle = 2 * np.arctan(35 / focal)
    self.cam.data.clip_end = 10000

MultiClassObject Link

MultiClassObject(scene, data)
Source code in plantimager/blender.py
328
329
330
331
332
333
334
def __init__(self, scene, data):
    self.data = data
    self.scene = scene
    self.objects = {}
    self.classes = []
    self.scene_materials = [m.name for m in self.data.materials]
    self.scene_objects = [o.name for o in self.data.objects]

load_obj Link

load_obj(fname, dx=None, dy=None, dz=None, colorize=True, palette_location=None)

move object by dx, dy, dz if specified

Source code in plantimager/blender.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def load_obj(self, fname, dx=None, dy=None, dz=None, colorize=True, palette_location=None):
    """move object by dx, dy, dz if specified"""

    for o in self.objects.values():
        self.data.objects.remove(o, do_unlink=True)
    self.objects = {}

    for m in bpy.data.materials:
        if not m.name in self.scene_materials:
            bpy.data.materials.remove(m)

    bpy.ops.import_scene.obj(filepath=fname)
    self.update_classes(colorize, palette_location)

    for m in self.classes:
        for o in self.data.objects:
            if m in o.name:
                self.objects[m] = o
                break

    try:
        dx = float(dx)
    except:
        dx = 0.0
    try:
        dy = float(dy)
    except:
        dy = 0.0
    try:
        dz = float(dz)
    except:
        dz = 0.0

    for o in bpy.context.scene.objects:
        if o.name not in self.scene_objects:
            bpy.ops.object.select_all(action='DESELECT')
            # bpy.context.scene.objects.link(o)
            o.location.x = dx
            o.location.y = dy
            o.location.x = dz
            o.select_set(True)
            bpy.context.view_layer.objects.active = o
            logger.debug("transform %s" % o.name)
            bpy.ops.object.select_all(action='DESELECT')

    self.location = {
        "x": dx,
        "y": dy,
        "z": dz
    }

_get_log_filepath Link

_get_log_filepath(path)

Returns a path for the blender log file.

Parameters:

Name Type Description Default
path str

A file path or directory to use as blender log file location.

required

Returns:

Type Description
str

The blender log file path.

Source code in plantimager/blender.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def _get_log_filepath(path):
    """Returns a path for the blender log file.

    Parameters
    ----------
    path : str
        A file path or directory to use as blender log file location.

    Returns
    -------
    str
        The blender log file path.
    """
    from pathlib import Path
    path = Path(path)
    if path.is_file():
        path = path.parent
    return str(path.joinpath('blender.log'))

check_engine Link

check_engine(engine='CYCLES')

Check the rendering engine.

Parameters:

Name Type Description Default
engine (CYCLES, BLENDER_EEVEE, BLENDER_WORKBENCH)

The name of the rendering engine to use. Defaults to CYCLES.

"CYCLES"
Source code in plantimager/blender.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def check_engine(engine="CYCLES"):
    """Check the rendering engine.

    Parameters
    ----------
    engine : {"CYCLES", "BLENDER_EEVEE", "BLENDER_WORKBENCH"}, optional
        The name of the rendering engine to use.
        Defaults to ``CYCLES``.
    """
    engine = engine.upper()
    try:
        assert bpy.context.scene.render.engine == engine
    except AssertionError:
        logger.warning(f"The selected engine is not '{engine}', it is '{bpy.context.scene.render.engine}'!")
    return

clean_mesh Link

clean_mesh(fname, out)

Mesh cleaning function.

Parameters:

Name Type Description Default
fname str

The file path to the mesh object to load in Blender.

required
out str

The file path to us to export the cleaned mesh object.

required
Source code in plantimager/blender.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def clean_mesh(fname, out):
    """Mesh cleaning function.

    Parameters
    ----------
    fname : str
        The file path to the mesh object to load in Blender.
    out : str
        The file path to us to export the cleaned mesh object.
    """
    check_engine()
    log = _get_log_filepath(fname)
    load_obj(fname, log=log)

    def _clean_obj(out):
        bpy.ops.object.select_all(action='SELECT')
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        # Remove vertices that are too close:
        bpy.ops.mesh.remove_doubles(threshold=0.01)
        # Close any hole in the mesh:
        bpy.ops.mesh.fill_holes(0)
        bpy.ops.mesh.set_normals_from_faces()
        # Export the mesh:
        bpy.ops.export_scene.obj(filepath=out)

    # Redirect blender outputs to a log file:
    with open(log, mode="a") as f:
        with redirect_stdout(f), redirect_stderr(f):
            _clean_obj(out)

    return

load_obj Link

load_obj(fname, log=None)

Load a mesh in Blender.

Parameters:

Name Type Description Default
fname str

The file path to the mesh object to load in Blender.

required
log str

The blender log file path.

None
Source code in plantimager/blender.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def load_obj(fname, log=None):
    """Load a mesh in Blender.

    Parameters
    ----------
    fname : str
        The file path to the mesh object to load in Blender.
    log : str, optional
        The blender log file path.
    """
    if log is None:
        log = _get_log_filepath(fname)

    def _clean_scene():
        # Start by selecting all objects from initialized scene (with a cube) and remove them all:
        bpy.ops.object.select_all(action='SELECT')
        bpy.ops.object.delete()

    def _open_obj(fname):
        # Load the mesh in Blender:
        bpy.ops.import_scene.obj(filepath=fname)

    with open(log, mode="a") as f:
        # Redirect blender outputs to a log file:
        with redirect_stdout(f), redirect_stderr(f):
            _clean_scene()
            _open_obj(fname)

    # List all imported objects & set them as active:
    o = bpy.data.objects[list(bpy.data.objects.keys())[0]]
    bpy.context.view_layer.objects.active = o
    return

split_by_material Link

split_by_material(fname, out, material_class_corres)

Mesh splitting function.

Parameters:

Name Type Description Default
fname str

The file path to the mesh object to load in Blender.

required
out str

The file path to us to export the cleaned mesh object.

required
material_class_corres dict

A renaming dictionary, map LPY class names to semantic class names.

required
Source code in plantimager/blender.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def split_by_material(fname, out, material_class_corres):
    """Mesh splitting function.

    Parameters
    ----------
    fname : str
        The file path to the mesh object to load in Blender.
    out : str
        The file path to us to export the cleaned mesh object.
    material_class_corres : dict
        A renaming dictionary, map LPY class names to semantic class names.
    """
    check_engine()
    log = _get_log_filepath(fname)
    load_obj(fname, log=log)

    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.separate(type='MATERIAL')
    bpy.ops.object.mode_set(mode='OBJECT')

    # After separating, all pieces are selected and named accordingly:
    for o in bpy.context.selected_objects:
        # Remove x rotation from LPY
        o.rotation_euler[0] = 0
        # Rename object by the material applied to it
        if o.active_material.name in material_class_corres:
            class_name = material_class_corres[o.active_material.name]
            o.name = class_name
            o.active_material.name = class_name
        else:
            o.name = o.active_material.name

    # Export the mesh:
    with open(log, mode="a") as f:
        with redirect_stdout(f), redirect_stderr(f):
            bpy.ops.export_scene.obj(filepath=out)

    return