Coverage for trimesh/interfaces/blender.py: 67%
55 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
1import os
2import platform
4from .. import util
5from ..constants import log
6from ..resources import get_string
7from ..typed import BooleanOperationType, Iterable
8from .generic import MeshScript
10if platform.system() == "Windows":
11 # try to find Blender install on Windows
12 # split existing path by delimiter
13 _search_path = [i for i in os.environ.get("PATH", "").split(";") if len(i) > 0]
14 for pf in [r"C:\Program Files", r"C:\Program Files (x86)"]:
15 pf = os.path.join(pf, "Blender Foundation")
16 if os.path.exists(pf):
17 for p in os.listdir(pf):
18 if "Blender" in p:
19 _search_path.append(os.path.join(pf, p))
20 _search_path = ";".join(set(_search_path))
21 log.debug("searching for blender in: %s", _search_path)
22elif platform.system() == "Darwin":
23 # try to find Blender on Mac OSX
24 _search_path = [i for i in os.environ.get("PATH", "").split(":") if len(i) > 0]
25 _search_path.extend(
26 [
27 "/Applications/blender.app/Contents/MacOS",
28 "/Applications/Blender.app/Contents/MacOS",
29 "/Applications/Blender/blender.app/Contents/MacOS",
30 ]
31 )
32 _search_path = ":".join(set(_search_path))
33 log.debug("searching for blender in: %s", _search_path)
34else:
35 _search_path = os.environ.get("PATH", "")
37_blender_executable = util.which("blender", path=_search_path)
38exists = _blender_executable is not None
40# a map that translates:
41# `trimesh.BooleanOperationType` -> blender value
42_blender_bool = {
43 "union": "UNION",
44 "difference": "DIFFERENCE",
45 "intersection": "INTERSECT",
46}
49def boolean(
50 meshes: Iterable,
51 operation: BooleanOperationType = "difference",
52 use_exact: bool = True,
53 use_self: bool = False,
54 debug: bool = False,
55 check_volume: bool = True,
56):
57 """
58 Run a boolean operation with multiple meshes using Blender.
60 Parameters
61 -----------
62 meshes
63 List of mesh objects to be operated on
64 operation
65 Type of boolean operation ("difference", "union", "intersect").
66 use_exact
67 Use the "exact" mode as opposed to the "fast" mode.
68 use_self
69 Whether to consider self-intersections.
70 debug
71 Provide additional output for troubleshooting.
72 check_volume
73 Raise an error if not all meshes are watertight
74 positive volumes. Advanced users may want to ignore
75 this check as it is expensive.
77 Returns
78 ----------
79 result
80 The result of the boolean operation on the provided meshes.
81 """
82 if not exists:
83 raise ValueError("No blender available!")
84 if check_volume and not all(m.is_volume for m in meshes):
85 raise ValueError("Not all meshes are volumes!")
87 # conversions from the trimesh `BooleanOperationType` to the blender option
88 key = operation.lower().strip()
89 if key not in _blender_bool:
90 raise ValueError(
91 f"`{operation}` is not a valid boolean: `{_blender_bool.keys()}`"
92 )
94 if use_exact:
95 solver_options = "EXACT"
96 else:
97 solver_options = "FAST"
99 # get the template from our resources folder
100 template = get_string("templates/blender_boolean.py.tmpl")
101 # use string substitutions rather than `string.Template` as we aren't going
102 # to be filling in all the values here, `MeshScript` is going to be
103 # the source of `$MESH_PRE`, etc.
104 script = (
105 template.replace("$OPERATION", _blender_bool[key])
106 .replace("$SOLVER_OPTIONS", solver_options)
107 .replace("$USE_SELF", f"{use_self}")
108 )
109 with MeshScript(meshes=meshes, script=script, debug=debug) as blend:
110 result = blend.run(_blender_executable + " --background --python $SCRIPT")
112 result = util.make_sequence(result)
113 for m in result:
114 # blender returns actively incorrect face normals
115 m.face_normals = None
117 return util.concatenate(result)
120def unwrap(
121 mesh, angle_limit: float = 66.0, island_margin: float = 0.0, debug: bool = False
122):
123 """
124 Run an unwrap operation using blender.
125 """
126 if not exists:
127 raise ValueError("No blender available!")
129 # get the template from our resources folder
130 template = get_string("templates/blender_unwrap.py.template")
131 script = template.replace("$ANGLE_LIMIT", f"{angle_limit:.6f}").replace(
132 "$ISLAND_MARGIN", f"{island_margin:.6f}"
133 )
135 with MeshScript(meshes=[mesh], script=script, exchange="obj", debug=debug) as blend:
136 result = blend.run(_blender_executable + " --background --python $SCRIPT")
138 for m in util.make_sequence(result):
139 # blender returns actively incorrect face normals
140 m.face_normals = None
142 return result