procfunc 0.30.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- procfunc/__init__.py +87 -0
- procfunc/color.py +57 -0
- procfunc/compute_graph/__init__.py +28 -0
- procfunc/compute_graph/compute_graph.py +115 -0
- procfunc/compute_graph/node.py +200 -0
- procfunc/compute_graph/operators_info.py +92 -0
- procfunc/compute_graph/proxy.py +173 -0
- procfunc/compute_graph/util.py +282 -0
- procfunc/context.py +115 -0
- procfunc/control.py +174 -0
- procfunc/nodes/__init__.py +66 -0
- procfunc/nodes/bindings_util.py +196 -0
- procfunc/nodes/bpy_node_info.py +280 -0
- procfunc/nodes/compositor.py +2242 -0
- procfunc/nodes/execute/construct_nodes.py +571 -0
- procfunc/nodes/execute/construct_special_cases.py +246 -0
- procfunc/nodes/execute/execute.py +548 -0
- procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
- procfunc/nodes/execute/util.py +247 -0
- procfunc/nodes/func.py +1417 -0
- procfunc/nodes/geo.py +4240 -0
- procfunc/nodes/manifest.json +8769 -0
- procfunc/nodes/math.py +644 -0
- procfunc/nodes/node_function.py +160 -0
- procfunc/nodes/shader.py +2359 -0
- procfunc/nodes/types.py +347 -0
- procfunc/ops/__init__.py +35 -0
- procfunc/ops/_util.py +275 -0
- procfunc/ops/addons.py +59 -0
- procfunc/ops/attr.py +426 -0
- procfunc/ops/collection.py +90 -0
- procfunc/ops/curve.py +18 -0
- procfunc/ops/file.py +126 -0
- procfunc/ops/manifest.json +39149 -0
- procfunc/ops/mesh.py +1510 -0
- procfunc/ops/modifier.py +603 -0
- procfunc/ops/object.py +258 -0
- procfunc/ops/primitives/__init__.py +31 -0
- procfunc/ops/primitives/camera.py +45 -0
- procfunc/ops/primitives/curve.py +71 -0
- procfunc/ops/primitives/light.py +114 -0
- procfunc/ops/primitives/mesh.py +358 -0
- procfunc/ops/uv.py +271 -0
- procfunc/random.py +247 -0
- procfunc/tracer/__init__.py +43 -0
- procfunc/tracer/decorator.py +121 -0
- procfunc/tracer/patch.py +494 -0
- procfunc/tracer/proxy.py +127 -0
- procfunc/tracer/trace.py +222 -0
- procfunc/transforms/__init__.py +49 -0
- procfunc/transforms/cleanup.py +214 -0
- procfunc/transforms/convert.py +20 -0
- procfunc/transforms/distribution.py +191 -0
- procfunc/transforms/extract_materials.py +116 -0
- procfunc/transforms/infer_distribution.py +326 -0
- procfunc/transforms/parameters.py +15 -0
- procfunc/transforms/util.py +35 -0
- procfunc/transpiler/__init__.py +24 -0
- procfunc/transpiler/bpy_to_computegraph.py +1348 -0
- procfunc/transpiler/codegen.py +919 -0
- procfunc/transpiler/identifiers.py +595 -0
- procfunc/transpiler/main.py +299 -0
- procfunc/types.py +380 -0
- procfunc/util/__init__.py +0 -0
- procfunc/util/bpy_info.py +145 -0
- procfunc/util/camera.py +0 -0
- procfunc/util/keyframe.py +70 -0
- procfunc/util/log.py +96 -0
- procfunc/util/manifest.py +121 -0
- procfunc/util/pytree.py +343 -0
- procfunc/util/teardown.py +37 -0
- procfunc-0.30.0.dist-info/METADATA +120 -0
- procfunc-0.30.0.dist-info/RECORD +76 -0
- procfunc-0.30.0.dist-info/WHEEL +5 -0
- procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
- procfunc-0.30.0.dist-info/top_level.txt +1 -0
procfunc/ops/mesh.py
ADDED
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
from dataclasses import asdict, dataclass
|
|
2
|
+
from typing import Literal, Tuple, Unpack
|
|
3
|
+
|
|
4
|
+
import bpy
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import procfunc as pf
|
|
8
|
+
from procfunc import types as t
|
|
9
|
+
from procfunc.ops._util import (
|
|
10
|
+
execute_mesh_op,
|
|
11
|
+
execute_object_op,
|
|
12
|
+
extract_edge_mask,
|
|
13
|
+
extract_face_mask,
|
|
14
|
+
extract_vertex_mask,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
TProportionalEditFalloff = Literal[
|
|
18
|
+
"SMOOTH",
|
|
19
|
+
"SPHERE",
|
|
20
|
+
"ROOT",
|
|
21
|
+
"INVERSE_SQUARE",
|
|
22
|
+
"SHARP",
|
|
23
|
+
"LINEAR",
|
|
24
|
+
"CONSTANT",
|
|
25
|
+
"RANDOM",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
30
|
+
def transform_apply(
|
|
31
|
+
mutates_obj: t.MeshObject,
|
|
32
|
+
location: bool = True,
|
|
33
|
+
rotation: bool = True,
|
|
34
|
+
scale: bool = True,
|
|
35
|
+
):
|
|
36
|
+
execute_object_op(
|
|
37
|
+
bpy.ops.object.transform_apply,
|
|
38
|
+
objs=mutates_obj,
|
|
39
|
+
location=location,
|
|
40
|
+
rotation=rotation,
|
|
41
|
+
scale=scale,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
46
|
+
def transform(
|
|
47
|
+
mutates_obj: t.MeshObject,
|
|
48
|
+
location: t.Vector | None = None,
|
|
49
|
+
rotation_euler: t.Vector | t.Euler | None = None,
|
|
50
|
+
scale: t.Vector | None = None,
|
|
51
|
+
):
|
|
52
|
+
obj = mutates_obj.item()
|
|
53
|
+
|
|
54
|
+
if location is not None:
|
|
55
|
+
obj.location += t.Vector(location)
|
|
56
|
+
if rotation_euler is not None:
|
|
57
|
+
obj.rotation_mode = "QUATERNION"
|
|
58
|
+
obj.rotation_quaternion = obj.rotation_quaternion @ t.Quaternion(rotation_euler)
|
|
59
|
+
if scale is not None:
|
|
60
|
+
obj.scale = obj.scale * t.Vector(scale)
|
|
61
|
+
|
|
62
|
+
transform_apply(
|
|
63
|
+
mutates_obj,
|
|
64
|
+
location=location is not None,
|
|
65
|
+
rotation=rotation_euler is not None,
|
|
66
|
+
scale=scale is not None,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
71
|
+
def delete_geometry(
|
|
72
|
+
mutates_obj: t.MeshObject,
|
|
73
|
+
vertex_mask: np.ndarray | None = None,
|
|
74
|
+
edge_mask: np.ndarray | None = None,
|
|
75
|
+
face_mask: np.ndarray | None = None,
|
|
76
|
+
type: Literal["VERT", "EDGE", "FACE", "EDGE_FACE", "ONLY_FACE"] = "VERT",
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Based on bpy.ops.mesh.delete"""
|
|
79
|
+
execute_mesh_op(
|
|
80
|
+
bpy.ops.mesh.delete,
|
|
81
|
+
mutates_obj,
|
|
82
|
+
vertex_mask=vertex_mask,
|
|
83
|
+
edge_mask=edge_mask,
|
|
84
|
+
face_mask=face_mask,
|
|
85
|
+
type=type,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class ProportionalEditProperties:
|
|
91
|
+
falloff: TProportionalEditFalloff | None = None
|
|
92
|
+
size: float = 1.0
|
|
93
|
+
connected: bool = False
|
|
94
|
+
projected: bool = False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
98
|
+
def extrude_edges(
|
|
99
|
+
mutates_obj: t.MeshObject,
|
|
100
|
+
edge_mask: np.ndarray | None = None,
|
|
101
|
+
use_normal_flip: bool = False,
|
|
102
|
+
mirror: bool = False,
|
|
103
|
+
value: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
104
|
+
orient_type: Literal[
|
|
105
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
106
|
+
] = "GLOBAL",
|
|
107
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
108
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
109
|
+
) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Extrude individual edges and move
|
|
112
|
+
|
|
113
|
+
Based on bpy.ops.mesh.extrude_edges_move
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
edge_mask: Boolean array selecting edges to extrude.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
120
|
+
|
|
121
|
+
execute_mesh_op(
|
|
122
|
+
bpy.ops.mesh.extrude_edges_move,
|
|
123
|
+
mutates_obj,
|
|
124
|
+
edge_mask=edge_mask,
|
|
125
|
+
MESH_OT_extrude_edges_indiv={
|
|
126
|
+
"use_normal_flip": use_normal_flip,
|
|
127
|
+
"mirror": mirror,
|
|
128
|
+
},
|
|
129
|
+
TRANSFORM_OT_translate={
|
|
130
|
+
"value": value,
|
|
131
|
+
"orient_type": orient_type,
|
|
132
|
+
"constraint_axis": constraint_axis,
|
|
133
|
+
"mirror": mirror,
|
|
134
|
+
"use_proportional_edit": proportional_edit.falloff is not None,
|
|
135
|
+
"proportional_edit_falloff": proportional_edit.falloff or "SMOOTH",
|
|
136
|
+
"proportional_size": proportional_edit.size,
|
|
137
|
+
"use_proportional_connected": proportional_edit.connected,
|
|
138
|
+
"use_proportional_projected": proportional_edit.projected,
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def region_to_loop(
|
|
144
|
+
obj: t.MeshObject,
|
|
145
|
+
face_mask: np.ndarray | None = None,
|
|
146
|
+
) -> np.ndarray:
|
|
147
|
+
"""
|
|
148
|
+
Select boundary edges of face regions
|
|
149
|
+
|
|
150
|
+
Based on bpy.ops.mesh.region_to_loop
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
face_mask: Boolean array selecting face regions to convert to loops.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Boolean array selecting edges that were converted to loops.
|
|
157
|
+
"""
|
|
158
|
+
execute_mesh_op(
|
|
159
|
+
bpy.ops.mesh.region_to_loop,
|
|
160
|
+
obj,
|
|
161
|
+
face_mask=face_mask,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return extract_edge_mask(obj)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Conversion functions moved to ops.object.py
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
171
|
+
def bridge_edge_loops(
|
|
172
|
+
mutates_obj: t.MeshObject,
|
|
173
|
+
edge_mask: np.ndarray | None = None,
|
|
174
|
+
type: Literal["SINGLE", "PAIRS", "FAN"] = "SINGLE",
|
|
175
|
+
use_merge: bool = False,
|
|
176
|
+
merge_factor: float = 0.5,
|
|
177
|
+
twist_offset: int = 0,
|
|
178
|
+
number_cuts: int = 0,
|
|
179
|
+
interpolation: Literal["PATH", "SURFACE"] = "PATH",
|
|
180
|
+
smoothness: float = 1.0,
|
|
181
|
+
profile_shape_factor: float = 0.0,
|
|
182
|
+
profile_shape: Literal[
|
|
183
|
+
"SMOOTH", "SPHERE", "ROOT", "INVERSE_SQUARE", "SHARP", "LINEAR"
|
|
184
|
+
] = "SMOOTH",
|
|
185
|
+
) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Create faces between two edge loops
|
|
188
|
+
|
|
189
|
+
Based on bpy.ops.mesh.bridge_edge_loops
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
edge_mask: Boolean array selecting edges to bridge between.
|
|
193
|
+
"""
|
|
194
|
+
execute_mesh_op(
|
|
195
|
+
bpy.ops.mesh.bridge_edge_loops,
|
|
196
|
+
mutates_obj,
|
|
197
|
+
edge_mask=edge_mask,
|
|
198
|
+
type=type,
|
|
199
|
+
use_merge=use_merge,
|
|
200
|
+
merge_factor=merge_factor,
|
|
201
|
+
twist_offset=twist_offset,
|
|
202
|
+
number_cuts=number_cuts,
|
|
203
|
+
interpolation=interpolation,
|
|
204
|
+
smoothness=smoothness,
|
|
205
|
+
profile_shape_factor=profile_shape_factor,
|
|
206
|
+
profile_shape=profile_shape,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
211
|
+
def normals_make_consistent(
|
|
212
|
+
mutates_obj: t.MeshObject,
|
|
213
|
+
face_mask: np.ndarray | None = None,
|
|
214
|
+
inside: bool = False,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Make face normals point outside or inside
|
|
218
|
+
|
|
219
|
+
Based on bpy.ops.mesh.normals_make_consistent
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
face_mask: Boolean array selecting faces to make consistent. If None, operates on entire mesh.
|
|
223
|
+
"""
|
|
224
|
+
execute_mesh_op(
|
|
225
|
+
bpy.ops.mesh.normals_make_consistent,
|
|
226
|
+
mutates_obj,
|
|
227
|
+
face_mask=face_mask,
|
|
228
|
+
inside=inside,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
233
|
+
def remove_doubles(
|
|
234
|
+
mutates_obj: t.MeshObject,
|
|
235
|
+
vertex_mask: np.ndarray | None = None,
|
|
236
|
+
edge_mask: np.ndarray | None = None,
|
|
237
|
+
face_mask: np.ndarray | None = None,
|
|
238
|
+
threshold: float = 0.0001,
|
|
239
|
+
use_unselected: bool = False,
|
|
240
|
+
use_sharp_edge_from_normals: bool = False,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Remove duplicate vertices
|
|
244
|
+
|
|
245
|
+
Based on bpy.ops.mesh.remove_doubles
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
vertex_mask: Boolean array selecting vertices to check for duplicates. If None, operates on entire mesh.
|
|
249
|
+
"""
|
|
250
|
+
execute_mesh_op(
|
|
251
|
+
bpy.ops.mesh.remove_doubles,
|
|
252
|
+
mutates_obj,
|
|
253
|
+
vertex_mask=vertex_mask,
|
|
254
|
+
edge_mask=edge_mask,
|
|
255
|
+
face_mask=face_mask,
|
|
256
|
+
threshold=threshold,
|
|
257
|
+
use_unselected=use_unselected,
|
|
258
|
+
use_sharp_edge_from_normals=use_sharp_edge_from_normals,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
263
|
+
def quads_convert_to_tris(
|
|
264
|
+
mutates_obj: t.MeshObject,
|
|
265
|
+
face_mask: np.ndarray | None = None,
|
|
266
|
+
# quad_method: Literal[
|
|
267
|
+
# "BEAUTY", "FIXED", "FIXED_ALTERNATE", "SHORTEST_DIAGONAL"
|
|
268
|
+
# ] = "BEAUTY",
|
|
269
|
+
# ngon_method: Literal["BEAUTY", "CLIP"] = "BEAUTY",
|
|
270
|
+
) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Convert quad faces to triangular faces
|
|
273
|
+
|
|
274
|
+
Based on bpy.ops.mesh.quads_convert_to_tris
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
face_mask: Boolean array selecting faces to convert. If None, operates on entire mesh.
|
|
278
|
+
|
|
279
|
+
Note: quad_method and ngon_method not currently included, they are never used in infinigen
|
|
280
|
+
but could be re-added if useful
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
execute_mesh_op(
|
|
284
|
+
bpy.ops.mesh.quads_convert_to_tris,
|
|
285
|
+
mutates_obj,
|
|
286
|
+
face_mask=face_mask,
|
|
287
|
+
quad_method="BEAUTY",
|
|
288
|
+
ngon_method="BEAUTY",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
293
|
+
def separate_mask(
|
|
294
|
+
mutates_obj: t.MeshObject,
|
|
295
|
+
vertex_mask: np.ndarray | None = None,
|
|
296
|
+
edge_mask: np.ndarray | None = None,
|
|
297
|
+
face_mask: np.ndarray | None = None,
|
|
298
|
+
) -> t.MeshObject:
|
|
299
|
+
"""
|
|
300
|
+
Separate selected geometry into a new mesh
|
|
301
|
+
|
|
302
|
+
Based on bpy.ops.mesh.separate
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
vertex_mask: Boolean array selecting vertices to separate.
|
|
306
|
+
edge_mask: Boolean array selecting edges to separate.
|
|
307
|
+
face_mask: Boolean array selecting faces to separate.
|
|
308
|
+
|
|
309
|
+
Note: we dont currently support the type="MATERIAL" option, please extract this mask explicitly and pass it in.
|
|
310
|
+
"""
|
|
311
|
+
execute_mesh_op(
|
|
312
|
+
bpy.ops.mesh.separate,
|
|
313
|
+
mutates_obj,
|
|
314
|
+
vertex_mask=vertex_mask,
|
|
315
|
+
edge_mask=edge_mask,
|
|
316
|
+
face_mask=face_mask,
|
|
317
|
+
type="SELECTED",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
assert len(bpy.context.selected_objects) == 2, (
|
|
321
|
+
f"{mutates_obj.item().name=} {list(bpy.context.selected_objects)}"
|
|
322
|
+
)
|
|
323
|
+
result_obj = bpy.context.selected_objects[1]
|
|
324
|
+
assert result_obj is not mutates_obj.item(), (
|
|
325
|
+
f"{mutates_obj.item().name=} {result_obj.name=} {bpy.data.objects.keys()}"
|
|
326
|
+
)
|
|
327
|
+
return t.MeshObject(result_obj)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
331
|
+
def separate_loose(
|
|
332
|
+
mutates_obj: t.MeshObject,
|
|
333
|
+
) -> list[t.MeshObject]:
|
|
334
|
+
"""
|
|
335
|
+
Separate loose mesh islands into new objects
|
|
336
|
+
|
|
337
|
+
Based on bpy.ops.mesh.separate
|
|
338
|
+
|
|
339
|
+
"""
|
|
340
|
+
execute_mesh_op(
|
|
341
|
+
bpy.ops.mesh.separate,
|
|
342
|
+
mutates_obj,
|
|
343
|
+
type="LOOSE",
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return [t.MeshObject(o) for o in bpy.context.selected_objects]
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
350
|
+
def fill_grid(
|
|
351
|
+
mutates_obj: t.MeshObject,
|
|
352
|
+
edge_mask: np.ndarray,
|
|
353
|
+
span: int = 1,
|
|
354
|
+
offset: int = 0,
|
|
355
|
+
use_interp_simple: bool = False,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Fill grid from two edge loops
|
|
359
|
+
|
|
360
|
+
Based on bpy.ops.mesh.fill_grid
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
edge_mask: Boolean array selecting edge loops to fill between.
|
|
364
|
+
"""
|
|
365
|
+
execute_mesh_op(
|
|
366
|
+
bpy.ops.mesh.fill_grid,
|
|
367
|
+
mutates_obj,
|
|
368
|
+
edge_mask=edge_mask,
|
|
369
|
+
span=span,
|
|
370
|
+
offset=offset,
|
|
371
|
+
use_interp_simple=use_interp_simple,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
376
|
+
def edge_face_add(
|
|
377
|
+
mutates_obj: t.MeshObject,
|
|
378
|
+
edge_mask: np.ndarray,
|
|
379
|
+
) -> None:
|
|
380
|
+
"""
|
|
381
|
+
Add an edge or face to selected
|
|
382
|
+
|
|
383
|
+
Based on bpy.ops.mesh.edge_face_add
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
edge_mask: Boolean array selecting edges to add faces to.
|
|
387
|
+
"""
|
|
388
|
+
execute_mesh_op(
|
|
389
|
+
bpy.ops.mesh.edge_face_add,
|
|
390
|
+
mutates_obj,
|
|
391
|
+
edge_mask=edge_mask,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
396
|
+
def duplicate(
|
|
397
|
+
mutates_obj: t.MeshObject,
|
|
398
|
+
vertex_mask: np.ndarray | None = None,
|
|
399
|
+
edge_mask: np.ndarray | None = None,
|
|
400
|
+
face_mask: np.ndarray | None = None,
|
|
401
|
+
):
|
|
402
|
+
"""
|
|
403
|
+
Duplicate selected faces
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
mutate_obj: MeshObject providing source and destination geometry
|
|
407
|
+
vertex_mask: If enabled, duplicate these vertices
|
|
408
|
+
edge_mask: If enabled, duplicate these edges
|
|
409
|
+
face_mask: If enabled, duplicate these faces
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
mask over vertices edges or faces, depending on which mask was provided
|
|
413
|
+
"""
|
|
414
|
+
execute_mesh_op(
|
|
415
|
+
bpy.ops.mesh.duplicate,
|
|
416
|
+
mutates_obj,
|
|
417
|
+
vertex_mask=vertex_mask,
|
|
418
|
+
edge_mask=edge_mask,
|
|
419
|
+
face_mask=face_mask,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if vertex_mask is not None:
|
|
423
|
+
return extract_vertex_mask(mutates_obj)
|
|
424
|
+
if edge_mask is not None:
|
|
425
|
+
return extract_edge_mask(mutates_obj)
|
|
426
|
+
if face_mask is not None:
|
|
427
|
+
return extract_face_mask(mutates_obj)
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
432
|
+
def extrude_faces(
|
|
433
|
+
mutates_obj: t.MeshObject,
|
|
434
|
+
face_mask: np.ndarray | None = None,
|
|
435
|
+
use_normal_flip: bool = False,
|
|
436
|
+
use_dissolve_ortho_edges: bool = False,
|
|
437
|
+
mirror: bool = False,
|
|
438
|
+
value: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
439
|
+
orient_type: Literal[
|
|
440
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
441
|
+
] = "GLOBAL",
|
|
442
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
443
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
444
|
+
) -> None:
|
|
445
|
+
"""
|
|
446
|
+
Extrude region and move result
|
|
447
|
+
|
|
448
|
+
Based on bpy.ops.mesh.extrude_region_move
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
face_mask: Boolean array selecting faces to extrude. If None, operates on entire mesh.
|
|
452
|
+
"""
|
|
453
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
454
|
+
execute_mesh_op(
|
|
455
|
+
bpy.ops.mesh.extrude_region_move,
|
|
456
|
+
mutates_obj,
|
|
457
|
+
face_mask=face_mask,
|
|
458
|
+
MESH_OT_extrude_region={
|
|
459
|
+
"use_normal_flip": use_normal_flip,
|
|
460
|
+
"use_dissolve_ortho_edges": use_dissolve_ortho_edges,
|
|
461
|
+
"mirror": mirror,
|
|
462
|
+
},
|
|
463
|
+
TRANSFORM_OT_translate={
|
|
464
|
+
"value": value,
|
|
465
|
+
"orient_type": orient_type,
|
|
466
|
+
"constraint_axis": constraint_axis,
|
|
467
|
+
"mirror": mirror,
|
|
468
|
+
"use_proportional_edit": proportional_edit.falloff is not None,
|
|
469
|
+
"proportional_edit_falloff": proportional_edit.falloff or "SMOOTH",
|
|
470
|
+
"proportional_size": proportional_edit.size,
|
|
471
|
+
"use_proportional_connected": proportional_edit.connected,
|
|
472
|
+
"use_proportional_projected": proportional_edit.projected,
|
|
473
|
+
},
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
478
|
+
def subdivide(
|
|
479
|
+
mutates_obj: t.MeshObject,
|
|
480
|
+
vertex_mask: np.ndarray | None = None,
|
|
481
|
+
edge_mask: np.ndarray | None = None,
|
|
482
|
+
face_mask: np.ndarray | None = None,
|
|
483
|
+
number_cuts: int = 1,
|
|
484
|
+
smoothness: float = 0.0,
|
|
485
|
+
ngon: bool = True,
|
|
486
|
+
quadcorner: Literal["STRAIGHT_CUT", "INNER_VERT", "PATH", "FAN"] = "STRAIGHT_CUT",
|
|
487
|
+
fractal: float = 0.0,
|
|
488
|
+
fractal_along_normal: float = 0.0,
|
|
489
|
+
seed: int = 0,
|
|
490
|
+
) -> None:
|
|
491
|
+
"""
|
|
492
|
+
Subdivide selected edges
|
|
493
|
+
|
|
494
|
+
Based on bpy.ops.mesh.subdivide
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
face_mask: Boolean array selecting faces to subdivide. If None, operates on entire mesh.
|
|
498
|
+
"""
|
|
499
|
+
execute_mesh_op(
|
|
500
|
+
bpy.ops.mesh.subdivide,
|
|
501
|
+
mutates_obj,
|
|
502
|
+
vertex_mask=vertex_mask,
|
|
503
|
+
edge_mask=edge_mask,
|
|
504
|
+
face_mask=face_mask,
|
|
505
|
+
number_cuts=number_cuts,
|
|
506
|
+
smoothness=smoothness,
|
|
507
|
+
ngon=ngon,
|
|
508
|
+
quadcorner=quadcorner,
|
|
509
|
+
fractal=fractal,
|
|
510
|
+
fractal_along_normal=fractal_along_normal,
|
|
511
|
+
seed=seed,
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
516
|
+
def unsubdivide(
|
|
517
|
+
mutates_obj: t.MeshObject,
|
|
518
|
+
iterations: int = 2,
|
|
519
|
+
) -> None:
|
|
520
|
+
"""
|
|
521
|
+
Un-subdivide selected edges and faces
|
|
522
|
+
|
|
523
|
+
Based on bpy.ops.mesh.unsubdivide
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
iterations: Number of times to un-subdivide.
|
|
527
|
+
"""
|
|
528
|
+
execute_mesh_op(
|
|
529
|
+
bpy.ops.mesh.unsubdivide,
|
|
530
|
+
mutates_obj,
|
|
531
|
+
iterations=iterations,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
536
|
+
def inset(
|
|
537
|
+
mutates_obj: t.MeshObject,
|
|
538
|
+
face_mask: np.ndarray | None = None,
|
|
539
|
+
use_boundary: bool = True,
|
|
540
|
+
use_even_offset: bool = True,
|
|
541
|
+
use_relative_offset: bool = False,
|
|
542
|
+
use_edge_rail: bool = False,
|
|
543
|
+
thickness: float = 0.0,
|
|
544
|
+
depth: float = 0.0,
|
|
545
|
+
use_outset: bool = False,
|
|
546
|
+
use_select_inset: bool = False,
|
|
547
|
+
use_individual: bool = False,
|
|
548
|
+
use_interpolate: bool = True,
|
|
549
|
+
) -> np.ndarray:
|
|
550
|
+
"""
|
|
551
|
+
Inset new faces into selected faces
|
|
552
|
+
|
|
553
|
+
Based on bpy.ops.mesh.inset
|
|
554
|
+
|
|
555
|
+
# TODO: use_select_inset as np.array output
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
face_mask: Boolean array selecting faces to inset. If None, operates on entire mesh.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Boolean array selecting faces that were inset.
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
execute_mesh_op(
|
|
565
|
+
bpy.ops.mesh.inset,
|
|
566
|
+
mutates_obj,
|
|
567
|
+
face_mask=face_mask,
|
|
568
|
+
use_boundary=use_boundary,
|
|
569
|
+
use_even_offset=use_even_offset,
|
|
570
|
+
use_relative_offset=use_relative_offset,
|
|
571
|
+
use_edge_rail=use_edge_rail,
|
|
572
|
+
thickness=thickness,
|
|
573
|
+
depth=depth,
|
|
574
|
+
use_outset=use_outset,
|
|
575
|
+
use_select_inset=use_select_inset,
|
|
576
|
+
use_individual=use_individual,
|
|
577
|
+
use_interpolate=use_interpolate,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
return extract_face_mask(mutates_obj)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
584
|
+
def inset_individual(
|
|
585
|
+
mutates_obj: t.MeshObject,
|
|
586
|
+
face_mask: np.ndarray | None = None,
|
|
587
|
+
use_boundary: bool = True,
|
|
588
|
+
use_even_offset: bool = True,
|
|
589
|
+
use_relative_offset: bool = False,
|
|
590
|
+
use_edge_rail: bool = False,
|
|
591
|
+
thickness: float = 0.0,
|
|
592
|
+
depth: float = 0.0,
|
|
593
|
+
use_outset: bool = False,
|
|
594
|
+
use_interpolate: bool = True,
|
|
595
|
+
):
|
|
596
|
+
execute_mesh_op(
|
|
597
|
+
bpy.ops.mesh.inset,
|
|
598
|
+
mutates_obj,
|
|
599
|
+
face_mask=face_mask,
|
|
600
|
+
use_boundary=use_boundary,
|
|
601
|
+
use_even_offset=use_even_offset,
|
|
602
|
+
use_relative_offset=use_relative_offset,
|
|
603
|
+
use_edge_rail=use_edge_rail,
|
|
604
|
+
thickness=thickness,
|
|
605
|
+
depth=depth,
|
|
606
|
+
use_outset=use_outset,
|
|
607
|
+
use_select_inset=False,
|
|
608
|
+
use_individual=True,
|
|
609
|
+
use_interpolate=use_interpolate,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
return extract_face_mask(mutates_obj)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
616
|
+
def bisect(
|
|
617
|
+
mutates_obj: t.MeshObject,
|
|
618
|
+
face_mask: np.ndarray | None = None,
|
|
619
|
+
plane_co: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
620
|
+
plane_no: Tuple[float, float, float] = (1.0, 0.0, 0.0),
|
|
621
|
+
use_fill: bool = False,
|
|
622
|
+
clear_inner: bool = False,
|
|
623
|
+
clear_outer: bool = False,
|
|
624
|
+
threshold: float = 0.0001,
|
|
625
|
+
flip: bool = False,
|
|
626
|
+
) -> None:
|
|
627
|
+
"""
|
|
628
|
+
Cut geometry along a plane
|
|
629
|
+
|
|
630
|
+
Based on bpy.ops.mesh.bisect
|
|
631
|
+
|
|
632
|
+
TODO: add edge_mask ?
|
|
633
|
+
|
|
634
|
+
NOTE: xstart, xend, ystart, yend are not supported as these relate to UI input, please manually specify the plane_c
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
mutates_obj: MeshObject to bisect
|
|
638
|
+
face_mask: Boolean array selecting faces to bisect. If None, operates on entire mesh.
|
|
639
|
+
plane_co: Location of the plane
|
|
640
|
+
plane_no: Normal of the plane
|
|
641
|
+
"""
|
|
642
|
+
execute_mesh_op(
|
|
643
|
+
bpy.ops.mesh.bisect,
|
|
644
|
+
mutates_obj,
|
|
645
|
+
face_mask=face_mask,
|
|
646
|
+
plane_co=plane_co,
|
|
647
|
+
plane_no=plane_no,
|
|
648
|
+
use_fill=use_fill,
|
|
649
|
+
clear_inner=clear_inner,
|
|
650
|
+
clear_outer=clear_outer,
|
|
651
|
+
threshold=threshold,
|
|
652
|
+
flip=flip,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
657
|
+
def convex_hull(
|
|
658
|
+
mutates_obj: t.MeshObject,
|
|
659
|
+
vertex_mask: np.ndarray | None = None,
|
|
660
|
+
delete_unused: bool = True,
|
|
661
|
+
use_existing_faces: bool = True,
|
|
662
|
+
make_holes: bool = False,
|
|
663
|
+
join_triangles: bool = True,
|
|
664
|
+
face_threshold: float = 0.698132,
|
|
665
|
+
shape_threshold: float = 0.698132,
|
|
666
|
+
uvs: bool = False,
|
|
667
|
+
vcols: bool = False,
|
|
668
|
+
seam: bool = False,
|
|
669
|
+
sharp: bool = False,
|
|
670
|
+
materials: bool = False,
|
|
671
|
+
) -> None:
|
|
672
|
+
"""
|
|
673
|
+
Enclose selected vertices in a convex hull
|
|
674
|
+
|
|
675
|
+
Based on bpy.ops.mesh.convex_hull
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
vertex_mask: Boolean array selecting vertices for hull computation. If None, operates on entire mesh.
|
|
679
|
+
"""
|
|
680
|
+
bpy.context.view_layer.objects.active = mutates_obj.item()
|
|
681
|
+
execute_mesh_op(
|
|
682
|
+
bpy.ops.mesh.convex_hull,
|
|
683
|
+
mutates_obj,
|
|
684
|
+
vertex_mask=vertex_mask,
|
|
685
|
+
delete_unused=delete_unused,
|
|
686
|
+
use_existing_faces=use_existing_faces,
|
|
687
|
+
make_holes=make_holes,
|
|
688
|
+
join_triangles=join_triangles,
|
|
689
|
+
face_threshold=face_threshold,
|
|
690
|
+
shape_threshold=shape_threshold,
|
|
691
|
+
uvs=uvs,
|
|
692
|
+
vcols=vcols,
|
|
693
|
+
seam=seam,
|
|
694
|
+
sharp=sharp,
|
|
695
|
+
materials=materials,
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@dataclass
|
|
700
|
+
class BevelProperties:
|
|
701
|
+
offset: float = 0.1
|
|
702
|
+
offset_pct: float = 0.1
|
|
703
|
+
offset_type: Literal["OFFSET", "WIDTH", "DEPTH", "PERCENT", "ABSOLUTE"] = "OFFSET"
|
|
704
|
+
profile_type: Literal["SUPERELLIPSE", "CUSTOM"] = "SUPERELLIPSE"
|
|
705
|
+
segments: int = 1
|
|
706
|
+
profile: float = 0.5
|
|
707
|
+
clamp_overlap: bool = False
|
|
708
|
+
loop_slide: bool = True
|
|
709
|
+
mark_seam: bool = False
|
|
710
|
+
mark_sharp: bool = False
|
|
711
|
+
material: int = -1
|
|
712
|
+
harden_normals: bool = False
|
|
713
|
+
face_strength_mode: Literal["NONE", "NEW", "AFFECTED", "ALL"] = "NONE"
|
|
714
|
+
miter_outer: Literal["SHARP", "PATCH", "ARC"] = "SHARP"
|
|
715
|
+
miter_inner: Literal["SHARP", "ARC"] = "SHARP"
|
|
716
|
+
spread: float = 0.1
|
|
717
|
+
vmesh_method: Literal["ADJ", "CUTOFF"] = "ADJ"
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
721
|
+
def bevel_vertices(
|
|
722
|
+
mutates_obj: t.MeshObject,
|
|
723
|
+
vertex_mask: np.ndarray | None = None,
|
|
724
|
+
**kwargs: Unpack[BevelProperties],
|
|
725
|
+
) -> None:
|
|
726
|
+
"""
|
|
727
|
+
Cut into selected items at an angle to create bevel or chamfer
|
|
728
|
+
|
|
729
|
+
Based on bpy.ops.mesh.bevel
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
vertex_mask: Boolean array selecting vertices to bevel. If None, operates on entire mesh.
|
|
733
|
+
"""
|
|
734
|
+
bevel = BevelProperties(**kwargs)
|
|
735
|
+
execute_mesh_op(
|
|
736
|
+
bpy.ops.mesh.bevel,
|
|
737
|
+
mutates_obj,
|
|
738
|
+
vertex_mask=vertex_mask,
|
|
739
|
+
affect="VERTICES",
|
|
740
|
+
**asdict(bevel),
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
745
|
+
def bevel_edges(
|
|
746
|
+
mutates_obj: t.MeshObject,
|
|
747
|
+
edge_mask: np.ndarray | None = None,
|
|
748
|
+
**kwargs: Unpack[BevelProperties],
|
|
749
|
+
) -> None:
|
|
750
|
+
"""
|
|
751
|
+
Cut into selected items at an angle to create bevel or chamfer
|
|
752
|
+
|
|
753
|
+
Based on bpy.ops.mesh.bevel
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
edge_mask: Boolean array selecting edges to bevel. If None, operates on entire mesh.
|
|
757
|
+
"""
|
|
758
|
+
bevel = BevelProperties(**kwargs)
|
|
759
|
+
execute_mesh_op(
|
|
760
|
+
bpy.ops.mesh.bevel,
|
|
761
|
+
mutates_obj,
|
|
762
|
+
edge_mask=edge_mask,
|
|
763
|
+
affect="EDGES",
|
|
764
|
+
**asdict(bevel),
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
769
|
+
def select_loose(
|
|
770
|
+
mutates_obj: t.MeshObject,
|
|
771
|
+
vertex_mask: np.ndarray | None = None,
|
|
772
|
+
edge_mask: np.ndarray | None = None,
|
|
773
|
+
face_mask: np.ndarray | None = None,
|
|
774
|
+
extend: bool = False,
|
|
775
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
776
|
+
"""
|
|
777
|
+
Select loose geometry.
|
|
778
|
+
|
|
779
|
+
Based on bpy.ops.mesh.select_loose
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
Tuple of (vertex_mask, edge_mask, face_mask) of the resulting selection.
|
|
783
|
+
"""
|
|
784
|
+
execute_mesh_op(
|
|
785
|
+
bpy.ops.mesh.select_loose,
|
|
786
|
+
mutates_obj,
|
|
787
|
+
vertex_mask=vertex_mask,
|
|
788
|
+
edge_mask=edge_mask,
|
|
789
|
+
face_mask=face_mask,
|
|
790
|
+
empty_mask_mode="execute",
|
|
791
|
+
extend=extend,
|
|
792
|
+
)
|
|
793
|
+
return (
|
|
794
|
+
extract_vertex_mask(mutates_obj),
|
|
795
|
+
extract_edge_mask(mutates_obj),
|
|
796
|
+
extract_face_mask(mutates_obj),
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
801
|
+
def select_more(
|
|
802
|
+
mutates_obj: t.MeshObject,
|
|
803
|
+
vertex_mask: np.ndarray | None = None,
|
|
804
|
+
edge_mask: np.ndarray | None = None,
|
|
805
|
+
face_mask: np.ndarray | None = None,
|
|
806
|
+
use_face_step: bool = True,
|
|
807
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
808
|
+
"""
|
|
809
|
+
Select more vertices, edges or faces connected to current selection.
|
|
810
|
+
|
|
811
|
+
Based on bpy.ops.mesh.select_more
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
Tuple of (vertex_mask, edge_mask, face_mask) of the resulting selection.
|
|
815
|
+
"""
|
|
816
|
+
execute_mesh_op(
|
|
817
|
+
bpy.ops.mesh.select_more,
|
|
818
|
+
mutates_obj,
|
|
819
|
+
vertex_mask=vertex_mask,
|
|
820
|
+
edge_mask=edge_mask,
|
|
821
|
+
face_mask=face_mask,
|
|
822
|
+
empty_mask_mode="execute",
|
|
823
|
+
use_face_step=use_face_step,
|
|
824
|
+
)
|
|
825
|
+
return (
|
|
826
|
+
extract_vertex_mask(mutates_obj),
|
|
827
|
+
extract_edge_mask(mutates_obj),
|
|
828
|
+
extract_face_mask(mutates_obj),
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
833
|
+
def loop_multi_select(
|
|
834
|
+
mutates_obj: t.MeshObject,
|
|
835
|
+
vertex_mask: np.ndarray | None = None,
|
|
836
|
+
edge_mask: np.ndarray | None = None,
|
|
837
|
+
face_mask: np.ndarray | None = None,
|
|
838
|
+
ring: bool = False,
|
|
839
|
+
) -> np.ndarray:
|
|
840
|
+
"""
|
|
841
|
+
Select a loop of connected edges by connection type.
|
|
842
|
+
|
|
843
|
+
Based on bpy.ops.mesh.loop_multi_select
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
ring: If True, select edge rings instead of edge loops.
|
|
847
|
+
|
|
848
|
+
Returns:
|
|
849
|
+
Boolean edge mask of the resulting selection.
|
|
850
|
+
"""
|
|
851
|
+
execute_mesh_op(
|
|
852
|
+
bpy.ops.mesh.loop_multi_select,
|
|
853
|
+
mutates_obj,
|
|
854
|
+
vertex_mask=vertex_mask,
|
|
855
|
+
edge_mask=edge_mask,
|
|
856
|
+
face_mask=face_mask,
|
|
857
|
+
empty_mask_mode="execute",
|
|
858
|
+
ring=ring,
|
|
859
|
+
)
|
|
860
|
+
return extract_edge_mask(mutates_obj)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
864
|
+
def fill(
|
|
865
|
+
mutates_obj: t.MeshObject,
|
|
866
|
+
edge_mask: np.ndarray | None = None,
|
|
867
|
+
use_beauty: bool = True,
|
|
868
|
+
) -> None:
|
|
869
|
+
"""
|
|
870
|
+
Fill a selected edge loop with faces
|
|
871
|
+
|
|
872
|
+
Based on bpy.ops.mesh.fill
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
edge_mask: Boolean array selecting edge loop to fill.
|
|
876
|
+
"""
|
|
877
|
+
execute_mesh_op(
|
|
878
|
+
bpy.ops.mesh.fill,
|
|
879
|
+
mutates_obj,
|
|
880
|
+
edge_mask=edge_mask,
|
|
881
|
+
use_beauty=use_beauty,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
886
|
+
def poke(
|
|
887
|
+
mutates_obj: t.MeshObject,
|
|
888
|
+
face_mask: np.ndarray | None = None,
|
|
889
|
+
offset: float = 0.0,
|
|
890
|
+
use_relative_offset: bool = False,
|
|
891
|
+
center_mode: Literal["MEDIAN_WEIGHTED", "MEDIAN", "BOUNDS"] = "MEDIAN_WEIGHTED",
|
|
892
|
+
) -> None:
|
|
893
|
+
"""
|
|
894
|
+
Split selected faces into individual triangles
|
|
895
|
+
|
|
896
|
+
Based on bpy.ops.mesh.poke
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
face_mask: Boolean array selecting faces to poke.
|
|
900
|
+
"""
|
|
901
|
+
execute_mesh_op(
|
|
902
|
+
bpy.ops.mesh.poke,
|
|
903
|
+
mutates_obj,
|
|
904
|
+
face_mask=face_mask,
|
|
905
|
+
offset=offset,
|
|
906
|
+
use_relative_offset=use_relative_offset,
|
|
907
|
+
center_mode=center_mode,
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
912
|
+
def flip_normals(
|
|
913
|
+
mutates_obj: t.MeshObject,
|
|
914
|
+
face_mask: np.ndarray | None = None,
|
|
915
|
+
only_clnors: bool = False,
|
|
916
|
+
) -> None:
|
|
917
|
+
"""
|
|
918
|
+
Flip the direction of selected faces' normals
|
|
919
|
+
|
|
920
|
+
Based on bpy.ops.mesh.flip_normals
|
|
921
|
+
|
|
922
|
+
Args:
|
|
923
|
+
face_mask: Boolean array selecting faces to flip normals. If None, operates on entire mesh.
|
|
924
|
+
"""
|
|
925
|
+
execute_mesh_op(
|
|
926
|
+
bpy.ops.mesh.flip_normals,
|
|
927
|
+
mutates_obj,
|
|
928
|
+
face_mask=face_mask,
|
|
929
|
+
only_clnors=only_clnors,
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
934
|
+
def extrude_faces_shrink_fatten(
|
|
935
|
+
mutates_obj: t.MeshObject,
|
|
936
|
+
use_normal_flip: bool = False,
|
|
937
|
+
use_dissolve_ortho_edges: bool = False,
|
|
938
|
+
mirror: bool = False,
|
|
939
|
+
value: float = 0.0,
|
|
940
|
+
use_even_offset: bool = False,
|
|
941
|
+
snap: bool = False,
|
|
942
|
+
use_accurate: bool = False,
|
|
943
|
+
face_mask: np.ndarray | None = None,
|
|
944
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
945
|
+
) -> None:
|
|
946
|
+
"""
|
|
947
|
+
Extrude region and shrink/fatten
|
|
948
|
+
|
|
949
|
+
Based on bpy.ops.mesh.extrude_region_shrink_fatten
|
|
950
|
+
|
|
951
|
+
Args:
|
|
952
|
+
face_mask: Boolean array selecting faces to extrude. If None, operates on entire mesh.
|
|
953
|
+
"""
|
|
954
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
955
|
+
execute_mesh_op(
|
|
956
|
+
bpy.ops.mesh.extrude_region_shrink_fatten,
|
|
957
|
+
mutates_obj,
|
|
958
|
+
face_mask=face_mask,
|
|
959
|
+
MESH_OT_extrude_region={
|
|
960
|
+
"use_normal_flip": use_normal_flip,
|
|
961
|
+
"use_dissolve_ortho_edges": use_dissolve_ortho_edges,
|
|
962
|
+
"mirror": mirror,
|
|
963
|
+
},
|
|
964
|
+
TRANSFORM_OT_shrink_fatten={
|
|
965
|
+
"value": value,
|
|
966
|
+
"use_even_offset": use_even_offset,
|
|
967
|
+
"mirror": mirror,
|
|
968
|
+
"use_proportional_edit": proportional_edit.falloff is not None,
|
|
969
|
+
"proportional_edit_falloff": proportional_edit.falloff or "SMOOTH",
|
|
970
|
+
"proportional_size": proportional_edit.size,
|
|
971
|
+
"use_proportional_connected": proportional_edit.connected,
|
|
972
|
+
"use_proportional_projected": proportional_edit.projected,
|
|
973
|
+
"snap": snap,
|
|
974
|
+
"use_accurate": use_accurate,
|
|
975
|
+
},
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
980
|
+
def tris_convert_to_quads(
|
|
981
|
+
mutates_obj: t.MeshObject,
|
|
982
|
+
face_threshold: float = 0.698132,
|
|
983
|
+
shape_threshold: float = 0.698132,
|
|
984
|
+
uvs: bool = False,
|
|
985
|
+
vcols: bool = False,
|
|
986
|
+
seam: bool = False,
|
|
987
|
+
sharp: bool = False,
|
|
988
|
+
materials: bool = False,
|
|
989
|
+
face_mask: np.ndarray | None = None,
|
|
990
|
+
) -> None:
|
|
991
|
+
"""
|
|
992
|
+
Convert triangles to quads
|
|
993
|
+
|
|
994
|
+
Based on bpy.ops.mesh.tris_convert_to_quads
|
|
995
|
+
|
|
996
|
+
Args:
|
|
997
|
+
face_mask: Boolean array selecting faces to convert. If None, operates on entire mesh.
|
|
998
|
+
"""
|
|
999
|
+
execute_mesh_op(
|
|
1000
|
+
bpy.ops.mesh.tris_convert_to_quads,
|
|
1001
|
+
mutates_obj,
|
|
1002
|
+
face_mask=face_mask,
|
|
1003
|
+
face_threshold=face_threshold,
|
|
1004
|
+
shape_threshold=shape_threshold,
|
|
1005
|
+
uvs=uvs,
|
|
1006
|
+
vcols=vcols,
|
|
1007
|
+
seam=seam,
|
|
1008
|
+
sharp=sharp,
|
|
1009
|
+
materials=materials,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1014
|
+
def merge(
|
|
1015
|
+
mutates_obj: t.MeshObject,
|
|
1016
|
+
type: Literal["CENTER", "CURSOR", "COLLAPSE"] = "CENTER",
|
|
1017
|
+
uvs: bool = False,
|
|
1018
|
+
vertex_mask: np.ndarray | None = None,
|
|
1019
|
+
edge_mask: np.ndarray | None = None,
|
|
1020
|
+
face_mask: np.ndarray | None = None,
|
|
1021
|
+
) -> None:
|
|
1022
|
+
"""
|
|
1023
|
+
Merge selected vertices
|
|
1024
|
+
|
|
1025
|
+
Based on bpy.ops.mesh.merge
|
|
1026
|
+
|
|
1027
|
+
Args:
|
|
1028
|
+
vertex_mask: Boolean array selecting vertices to merge. At most one can be provided. If all are None, operates on entire mesh.
|
|
1029
|
+
edge_mask: Boolean array selecting edges to merge. At most one can be provided. If all are None, operates on entire mesh.
|
|
1030
|
+
face_mask: Boolean array selecting faces to merge. At most one can be provided. If all are None, operates on entire mesh.
|
|
1031
|
+
|
|
1032
|
+
Note: Only one of vertex_mask, edge_mask, or face_mask should be provided.
|
|
1033
|
+
"""
|
|
1034
|
+
execute_mesh_op(
|
|
1035
|
+
bpy.ops.mesh.merge,
|
|
1036
|
+
mutates_obj,
|
|
1037
|
+
vertex_mask=vertex_mask,
|
|
1038
|
+
edge_mask=edge_mask,
|
|
1039
|
+
face_mask=face_mask,
|
|
1040
|
+
type=type,
|
|
1041
|
+
uvs=uvs,
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1046
|
+
def mark_sharp(
|
|
1047
|
+
mutates_obj: t.MeshObject,
|
|
1048
|
+
clear: bool = False,
|
|
1049
|
+
use_verts: bool = False,
|
|
1050
|
+
edge_mask: np.ndarray | None = None,
|
|
1051
|
+
) -> None:
|
|
1052
|
+
"""
|
|
1053
|
+
Mark selected edges as sharp
|
|
1054
|
+
|
|
1055
|
+
Based on bpy.ops.mesh.mark_sharp
|
|
1056
|
+
|
|
1057
|
+
Args:
|
|
1058
|
+
edge_mask: Boolean array selecting edges to mark/unmark as sharp. If None, operates on entire mesh.
|
|
1059
|
+
"""
|
|
1060
|
+
execute_mesh_op(
|
|
1061
|
+
bpy.ops.mesh.mark_sharp,
|
|
1062
|
+
mutates_obj,
|
|
1063
|
+
edge_mask=edge_mask,
|
|
1064
|
+
clear=clear,
|
|
1065
|
+
use_verts=use_verts,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1070
|
+
def dissolve_limited(
|
|
1071
|
+
mutates_obj: t.MeshObject,
|
|
1072
|
+
angle_limit: float = 0.0872665,
|
|
1073
|
+
use_dissolve_boundaries: bool = False,
|
|
1074
|
+
delimit: set[Literal["NORMAL", "MATERIAL", "SEAM", "SHARP", "UV"]] = {"NORMAL"},
|
|
1075
|
+
edge_mask: np.ndarray | None = None,
|
|
1076
|
+
) -> None:
|
|
1077
|
+
"""
|
|
1078
|
+
Dissolve selected edges and faces limited by the angle of adjacent faces
|
|
1079
|
+
|
|
1080
|
+
Based on bpy.ops.mesh.dissolve_limited
|
|
1081
|
+
|
|
1082
|
+
Args:
|
|
1083
|
+
edge_mask: Boolean array selecting edges to dissolve. If None, operates on entire mesh.
|
|
1084
|
+
"""
|
|
1085
|
+
execute_mesh_op(
|
|
1086
|
+
bpy.ops.mesh.dissolve_limited,
|
|
1087
|
+
mutates_obj,
|
|
1088
|
+
angle_limit=angle_limit,
|
|
1089
|
+
use_dissolve_boundaries=use_dissolve_boundaries,
|
|
1090
|
+
delimit=delimit,
|
|
1091
|
+
edge_mask=edge_mask,
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1096
|
+
def extrude_vertices(
|
|
1097
|
+
mutates_obj: t.MeshObject,
|
|
1098
|
+
vertex_mask: np.ndarray,
|
|
1099
|
+
mirror: bool = False,
|
|
1100
|
+
value: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
1101
|
+
orient_type: Literal[
|
|
1102
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
1103
|
+
] = "GLOBAL",
|
|
1104
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
1105
|
+
**kwargs: Unpack[ProportionalEditProperties],
|
|
1106
|
+
) -> None:
|
|
1107
|
+
"""
|
|
1108
|
+
Extrude individual vertices and move
|
|
1109
|
+
|
|
1110
|
+
Based on bpy.ops.mesh.extrude_vertices_move
|
|
1111
|
+
|
|
1112
|
+
Args:
|
|
1113
|
+
vertex_mask: Boolean array selecting vertices to extrude. If None, operates on entire mesh.
|
|
1114
|
+
"""
|
|
1115
|
+
proportional_edit = ProportionalEditProperties(**kwargs)
|
|
1116
|
+
|
|
1117
|
+
execute_mesh_op(
|
|
1118
|
+
bpy.ops.mesh.extrude_vertices_move,
|
|
1119
|
+
mutates_obj,
|
|
1120
|
+
vertex_mask=vertex_mask,
|
|
1121
|
+
MESH_OT_extrude_verts_indiv={
|
|
1122
|
+
"mirror": mirror,
|
|
1123
|
+
},
|
|
1124
|
+
TRANSFORM_OT_translate={
|
|
1125
|
+
"value": value,
|
|
1126
|
+
"orient_type": orient_type,
|
|
1127
|
+
"constraint_axis": constraint_axis,
|
|
1128
|
+
"mirror": mirror,
|
|
1129
|
+
"use_proportional_edit": proportional_edit.falloff is not None,
|
|
1130
|
+
"proportional_edit_falloff": proportional_edit.falloff or "SMOOTH",
|
|
1131
|
+
"proportional_size": proportional_edit.size,
|
|
1132
|
+
"use_proportional_connected": proportional_edit.connected,
|
|
1133
|
+
"use_proportional_projected": proportional_edit.projected,
|
|
1134
|
+
},
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1139
|
+
def fill_holes(
|
|
1140
|
+
mutates_obj: t.MeshObject,
|
|
1141
|
+
sides: int = 4,
|
|
1142
|
+
) -> None:
|
|
1143
|
+
"""
|
|
1144
|
+
Fill in holes (boundary edge loops)
|
|
1145
|
+
|
|
1146
|
+
Based on bpy.ops.mesh.fill_holes
|
|
1147
|
+
"""
|
|
1148
|
+
execute_mesh_op(
|
|
1149
|
+
bpy.ops.mesh.fill_holes,
|
|
1150
|
+
mutates_obj,
|
|
1151
|
+
sides=sides,
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1156
|
+
def spin(
|
|
1157
|
+
mutates_obj: t.MeshObject,
|
|
1158
|
+
vertex_mask: np.ndarray | None = None,
|
|
1159
|
+
steps: int = 12,
|
|
1160
|
+
dupli: bool = False,
|
|
1161
|
+
angle: float = 1.5708,
|
|
1162
|
+
use_auto_merge: bool = True,
|
|
1163
|
+
use_normal_flip: bool = False,
|
|
1164
|
+
center: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
1165
|
+
axis: Tuple[float, float, float] = (0.0, 0.0, 1.0),
|
|
1166
|
+
) -> None:
|
|
1167
|
+
"""
|
|
1168
|
+
Extrude selected vertices in a circle around the cursor in indicated viewport
|
|
1169
|
+
|
|
1170
|
+
Based on bpy.ops.mesh.spin
|
|
1171
|
+
|
|
1172
|
+
Args:
|
|
1173
|
+
vertex_mask: Boolean array selecting vertices to extrude in spin. If None, operates on entire mesh.
|
|
1174
|
+
"""
|
|
1175
|
+
execute_mesh_op(
|
|
1176
|
+
bpy.ops.mesh.spin,
|
|
1177
|
+
mutates_obj,
|
|
1178
|
+
steps=steps,
|
|
1179
|
+
dupli=dupli,
|
|
1180
|
+
angle=angle,
|
|
1181
|
+
use_auto_merge=use_auto_merge,
|
|
1182
|
+
use_normal_flip=use_normal_flip,
|
|
1183
|
+
center=center,
|
|
1184
|
+
axis=axis,
|
|
1185
|
+
vertex_mask=vertex_mask,
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1190
|
+
def vertices_smooth(
|
|
1191
|
+
mutates_obj: t.MeshObject,
|
|
1192
|
+
vertex_mask: np.ndarray | None = None,
|
|
1193
|
+
factor: float = 0.5,
|
|
1194
|
+
repeat: int = 1,
|
|
1195
|
+
xaxis: bool = True,
|
|
1196
|
+
yaxis: bool = True,
|
|
1197
|
+
zaxis: bool = True,
|
|
1198
|
+
) -> None:
|
|
1199
|
+
"""
|
|
1200
|
+
Flatten angles of selected vertices
|
|
1201
|
+
|
|
1202
|
+
Based on bpy.ops.mesh.vertices_smooth
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
vertex_mask: Boolean array selecting vertices to smooth. If None, operates on entire mesh.
|
|
1206
|
+
"""
|
|
1207
|
+
execute_mesh_op(
|
|
1208
|
+
bpy.ops.mesh.vertices_smooth,
|
|
1209
|
+
mutates_obj,
|
|
1210
|
+
factor=factor,
|
|
1211
|
+
repeat=repeat,
|
|
1212
|
+
xaxis=xaxis,
|
|
1213
|
+
yaxis=yaxis,
|
|
1214
|
+
zaxis=zaxis,
|
|
1215
|
+
wait_for_input=False,
|
|
1216
|
+
vertex_mask=vertex_mask,
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1221
|
+
def split_nonplanar_faces(
|
|
1222
|
+
mutates_obj: t.MeshObject,
|
|
1223
|
+
face_mask: np.ndarray | None = None,
|
|
1224
|
+
angle_limit_rad: float = 0.0872665,
|
|
1225
|
+
):
|
|
1226
|
+
"""
|
|
1227
|
+
Split nonplanar faces into new faces
|
|
1228
|
+
"""
|
|
1229
|
+
execute_mesh_op(
|
|
1230
|
+
bpy.ops.mesh.vert_connect_nonplanar,
|
|
1231
|
+
mutates_obj,
|
|
1232
|
+
face_mask=face_mask,
|
|
1233
|
+
angle_limit=angle_limit_rad,
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1238
|
+
def edges_select_sharp(
|
|
1239
|
+
mutates_obj: t.MeshObject,
|
|
1240
|
+
sharpness: float = 0.523599,
|
|
1241
|
+
) -> np.ndarray:
|
|
1242
|
+
"""
|
|
1243
|
+
Select all sharp enough edges
|
|
1244
|
+
|
|
1245
|
+
Based on bpy.ops.mesh.edges_select_sharp
|
|
1246
|
+
"""
|
|
1247
|
+
execute_mesh_op(
|
|
1248
|
+
bpy.ops.mesh.edges_select_sharp,
|
|
1249
|
+
mutates_obj,
|
|
1250
|
+
sharpness=sharpness,
|
|
1251
|
+
edge_mask=np.zeros(len(mutates_obj.item().data.edges), dtype=bool),
|
|
1252
|
+
empty_mask_mode="execute",
|
|
1253
|
+
)
|
|
1254
|
+
return extract_edge_mask(mutates_obj)
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1258
|
+
def select_nth(
|
|
1259
|
+
mutates_obj: t.MeshObject,
|
|
1260
|
+
domain: Literal["VERT", "EDGE", "FACE"] = "FACE",
|
|
1261
|
+
skip: int = 1,
|
|
1262
|
+
nth: int = 1,
|
|
1263
|
+
offset: int = 0,
|
|
1264
|
+
) -> np.ndarray:
|
|
1265
|
+
"""
|
|
1266
|
+
Deselect every Nth element starting from the active vertex, edge or face.
|
|
1267
|
+
|
|
1268
|
+
Based on bpy.ops.mesh.select_nth
|
|
1269
|
+
|
|
1270
|
+
Parameters:
|
|
1271
|
+
domain: Which element type to operate on.
|
|
1272
|
+
skip: Number of deselected elements in the repetitive sequence.
|
|
1273
|
+
nth: Number of selected elements in the repetitive sequence.
|
|
1274
|
+
offset: Offset from the starting point.
|
|
1275
|
+
|
|
1276
|
+
Returns:
|
|
1277
|
+
Boolean mask of the selected elements after the operation.
|
|
1278
|
+
"""
|
|
1279
|
+
if domain == "VERT":
|
|
1280
|
+
mask = np.ones(len(mutates_obj.item().data.vertices), dtype=bool)
|
|
1281
|
+
execute_mesh_op(
|
|
1282
|
+
bpy.ops.mesh.select_nth,
|
|
1283
|
+
mutates_obj,
|
|
1284
|
+
vertex_mask=mask,
|
|
1285
|
+
skip=skip,
|
|
1286
|
+
nth=nth,
|
|
1287
|
+
offset=offset,
|
|
1288
|
+
)
|
|
1289
|
+
return extract_vertex_mask(mutates_obj)
|
|
1290
|
+
elif domain == "EDGE":
|
|
1291
|
+
mask = np.ones(len(mutates_obj.item().data.edges), dtype=bool)
|
|
1292
|
+
execute_mesh_op(
|
|
1293
|
+
bpy.ops.mesh.select_nth,
|
|
1294
|
+
mutates_obj,
|
|
1295
|
+
edge_mask=mask,
|
|
1296
|
+
skip=skip,
|
|
1297
|
+
nth=nth,
|
|
1298
|
+
offset=offset,
|
|
1299
|
+
)
|
|
1300
|
+
return extract_edge_mask(mutates_obj)
|
|
1301
|
+
else:
|
|
1302
|
+
mask = np.ones(len(mutates_obj.item().data.polygons), dtype=bool)
|
|
1303
|
+
execute_mesh_op(
|
|
1304
|
+
bpy.ops.mesh.select_nth,
|
|
1305
|
+
mutates_obj,
|
|
1306
|
+
face_mask=mask,
|
|
1307
|
+
skip=skip,
|
|
1308
|
+
nth=nth,
|
|
1309
|
+
offset=offset,
|
|
1310
|
+
)
|
|
1311
|
+
return extract_face_mask(mutates_obj)
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1315
|
+
def edge_split(
|
|
1316
|
+
mutates_obj: t.MeshObject,
|
|
1317
|
+
edge_mask: np.ndarray | None = None,
|
|
1318
|
+
type: Literal["EDGE", "VERT"] = "EDGE",
|
|
1319
|
+
):
|
|
1320
|
+
execute_mesh_op(
|
|
1321
|
+
bpy.ops.mesh.edge_split,
|
|
1322
|
+
mutates_obj,
|
|
1323
|
+
edge_mask=edge_mask,
|
|
1324
|
+
type=type,
|
|
1325
|
+
)
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1329
|
+
def symmetrize(
|
|
1330
|
+
mutates_obj: t.MeshObject,
|
|
1331
|
+
vertex_mask: np.ndarray | None = None,
|
|
1332
|
+
edge_mask: np.ndarray | None = None,
|
|
1333
|
+
face_mask: np.ndarray | None = None,
|
|
1334
|
+
direction: Literal[
|
|
1335
|
+
"NEGATIVE_X",
|
|
1336
|
+
"POSITIVE_X",
|
|
1337
|
+
"NEGATIVE_Y",
|
|
1338
|
+
"POSITIVE_Y",
|
|
1339
|
+
"NEGATIVE_Z",
|
|
1340
|
+
"POSITIVE_Z",
|
|
1341
|
+
] = "NEGATIVE_X",
|
|
1342
|
+
threshold: int | float | None = 0.001,
|
|
1343
|
+
):
|
|
1344
|
+
execute_mesh_op(
|
|
1345
|
+
bpy.ops.mesh.symmetrize,
|
|
1346
|
+
mutates_obj,
|
|
1347
|
+
vertex_mask=vertex_mask,
|
|
1348
|
+
edge_mask=edge_mask,
|
|
1349
|
+
face_mask=face_mask,
|
|
1350
|
+
direction=direction,
|
|
1351
|
+
threshold=threshold,
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1356
|
+
def subdivide_edgering(
|
|
1357
|
+
mutates_obj: t.MeshObject,
|
|
1358
|
+
edge_mask: np.ndarray | None = None,
|
|
1359
|
+
number_cuts: int = 10,
|
|
1360
|
+
interpolation: Literal["PATH", "SMOOTH", "SPHERE", "CREASE", "FIRE"] = "PATH",
|
|
1361
|
+
smoothness: float = 1.0,
|
|
1362
|
+
profile_shape_factor: float = 0.0,
|
|
1363
|
+
profile_shape: Literal["SMOOTH", "SPHERE"] = "SMOOTH",
|
|
1364
|
+
):
|
|
1365
|
+
execute_mesh_op(
|
|
1366
|
+
bpy.ops.mesh.subdivide_edgering,
|
|
1367
|
+
mutates_obj,
|
|
1368
|
+
edge_mask=edge_mask,
|
|
1369
|
+
number_cuts=number_cuts,
|
|
1370
|
+
interpolation=interpolation,
|
|
1371
|
+
smoothness=smoothness,
|
|
1372
|
+
profile_shape_factor=profile_shape_factor,
|
|
1373
|
+
profile_shape=profile_shape,
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1378
|
+
def move(
|
|
1379
|
+
mutates_obj: t.MeshObject,
|
|
1380
|
+
value: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
1381
|
+
vertex_mask: np.ndarray | None = None,
|
|
1382
|
+
edge_mask: np.ndarray | None = None,
|
|
1383
|
+
face_mask: np.ndarray | None = None,
|
|
1384
|
+
orient_type: Literal[
|
|
1385
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
1386
|
+
] = "GLOBAL",
|
|
1387
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
1388
|
+
mirror: bool = False,
|
|
1389
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
1390
|
+
) -> None:
|
|
1391
|
+
"""
|
|
1392
|
+
Move selected geometry
|
|
1393
|
+
|
|
1394
|
+
Based on bpy.ops.transform.translate
|
|
1395
|
+
"""
|
|
1396
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
1397
|
+
execute_mesh_op(
|
|
1398
|
+
bpy.ops.transform.translate,
|
|
1399
|
+
mutates_obj,
|
|
1400
|
+
vertex_mask=vertex_mask,
|
|
1401
|
+
edge_mask=edge_mask,
|
|
1402
|
+
face_mask=face_mask,
|
|
1403
|
+
value=value,
|
|
1404
|
+
orient_type=orient_type,
|
|
1405
|
+
constraint_axis=constraint_axis,
|
|
1406
|
+
mirror=mirror,
|
|
1407
|
+
use_proportional_edit=proportional_edit.falloff is not None,
|
|
1408
|
+
proportional_edit_falloff=proportional_edit.falloff or "SMOOTH",
|
|
1409
|
+
proportional_size=proportional_edit.size,
|
|
1410
|
+
use_proportional_connected=proportional_edit.connected,
|
|
1411
|
+
use_proportional_projected=proportional_edit.projected,
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
|
|
1415
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1416
|
+
def rotate(
|
|
1417
|
+
mutates_obj: t.MeshObject,
|
|
1418
|
+
value: float = 0.0,
|
|
1419
|
+
vertex_mask: np.ndarray | None = None,
|
|
1420
|
+
edge_mask: np.ndarray | None = None,
|
|
1421
|
+
face_mask: np.ndarray | None = None,
|
|
1422
|
+
orient_axis: Literal["X", "Y", "Z"] = "Z",
|
|
1423
|
+
orient_type: Literal[
|
|
1424
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
1425
|
+
] = "GLOBAL",
|
|
1426
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
1427
|
+
mirror: bool = False,
|
|
1428
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
1429
|
+
) -> None:
|
|
1430
|
+
"""
|
|
1431
|
+
Rotate selected geometry
|
|
1432
|
+
|
|
1433
|
+
Based on bpy.ops.transform.rotate
|
|
1434
|
+
"""
|
|
1435
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
1436
|
+
execute_mesh_op(
|
|
1437
|
+
bpy.ops.transform.rotate,
|
|
1438
|
+
mutates_obj,
|
|
1439
|
+
vertex_mask=vertex_mask,
|
|
1440
|
+
edge_mask=edge_mask,
|
|
1441
|
+
face_mask=face_mask,
|
|
1442
|
+
value=value,
|
|
1443
|
+
orient_axis=orient_axis,
|
|
1444
|
+
orient_type=orient_type,
|
|
1445
|
+
constraint_axis=constraint_axis,
|
|
1446
|
+
mirror=mirror,
|
|
1447
|
+
use_proportional_edit=proportional_edit.falloff is not None,
|
|
1448
|
+
proportional_edit_falloff=proportional_edit.falloff or "SMOOTH",
|
|
1449
|
+
proportional_size=proportional_edit.size,
|
|
1450
|
+
use_proportional_connected=proportional_edit.connected,
|
|
1451
|
+
use_proportional_projected=proportional_edit.projected,
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1456
|
+
def resize(
|
|
1457
|
+
mutates_obj: t.MeshObject,
|
|
1458
|
+
value: Tuple[float, float, float] = (1.0, 1.0, 1.0),
|
|
1459
|
+
vertex_mask: np.ndarray | None = None,
|
|
1460
|
+
edge_mask: np.ndarray | None = None,
|
|
1461
|
+
face_mask: np.ndarray | None = None,
|
|
1462
|
+
orient_type: Literal[
|
|
1463
|
+
"GLOBAL", "LOCAL", "NORMAL", "GIMBAL", "VIEW", "CURSOR"
|
|
1464
|
+
] = "GLOBAL",
|
|
1465
|
+
constraint_axis: Tuple[bool, bool, bool] = (False, False, False),
|
|
1466
|
+
mirror: bool = False,
|
|
1467
|
+
**proportional_edit_kwargs: Unpack[ProportionalEditProperties],
|
|
1468
|
+
) -> None:
|
|
1469
|
+
"""
|
|
1470
|
+
Resize selected geometry
|
|
1471
|
+
|
|
1472
|
+
Based on bpy.ops.transform.resize
|
|
1473
|
+
"""
|
|
1474
|
+
proportional_edit = ProportionalEditProperties(**proportional_edit_kwargs)
|
|
1475
|
+
execute_mesh_op(
|
|
1476
|
+
bpy.ops.transform.resize,
|
|
1477
|
+
mutates_obj,
|
|
1478
|
+
vertex_mask=vertex_mask,
|
|
1479
|
+
edge_mask=edge_mask,
|
|
1480
|
+
face_mask=face_mask,
|
|
1481
|
+
value=value,
|
|
1482
|
+
orient_type=orient_type,
|
|
1483
|
+
constraint_axis=constraint_axis,
|
|
1484
|
+
mirror=mirror,
|
|
1485
|
+
use_proportional_edit=proportional_edit.falloff is not None,
|
|
1486
|
+
proportional_edit_falloff=proportional_edit.falloff or "SMOOTH",
|
|
1487
|
+
proportional_size=proportional_edit.size,
|
|
1488
|
+
use_proportional_connected=proportional_edit.connected,
|
|
1489
|
+
use_proportional_projected=proportional_edit.projected,
|
|
1490
|
+
)
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
@pf.tracer.primitive(mutates=["mutates_obj"])
|
|
1494
|
+
def dissolve_verts(
|
|
1495
|
+
mutates_obj: t.MeshObject,
|
|
1496
|
+
vertex_mask: np.ndarray | None = None,
|
|
1497
|
+
edge_mask: np.ndarray | None = None,
|
|
1498
|
+
face_mask: np.ndarray | None = None,
|
|
1499
|
+
use_face_split: bool = False,
|
|
1500
|
+
use_boundary_tear: bool = False,
|
|
1501
|
+
):
|
|
1502
|
+
execute_mesh_op(
|
|
1503
|
+
bpy.ops.mesh.dissolve_verts,
|
|
1504
|
+
mutates_obj,
|
|
1505
|
+
vertex_mask=vertex_mask,
|
|
1506
|
+
edge_mask=edge_mask,
|
|
1507
|
+
face_mask=face_mask,
|
|
1508
|
+
use_face_split=use_face_split,
|
|
1509
|
+
use_boundary_tear=use_boundary_tear,
|
|
1510
|
+
)
|