-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathMeshSlicer.py
322 lines (268 loc) · 11.5 KB
/
MeshSlicer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
88
89
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
129
130
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import bpy
import math
bl_info = {
"name": "Mesh Slicer",
"author": "Tomasz Schelenz",
"version": (1, 0),
"blender": (2, 92, 0),
"location": "View3D > Tools panel 'T' > Mesh Slicer",
"description": "Slice a mesh with multiple planes at once. Planes can intersect with each other. Will not work correctly with other types of meshes as slicers",
"warning": "",
"wiki_url": "https://github.com/TomekSc/MeshSlicer",
"category": "",
}
class SlicerContainer:
def __init__(self, left=None, right=None):
self.left = left
self.right = right
class My_settings(bpy.types.PropertyGroup):
bool_deleteChunks: bpy.props.BoolProperty(
name='Delete chunks',
description="Deletes the partial results after completed. This almost always should be TRUE",
default=True,
options={'HIDDEN'}
)
bool_keepOriginal: bpy.props.BoolProperty(
name='Keep original',
description="Keeps the original object that is being sliced in the scene, but hides it. False - it will be deleted",
default=False,
options={'HIDDEN'}
)
bool_resetOrigin: bpy.props.BoolProperty(
name='Reset origins',
description="If TRUE, each indivudual result of slice operation will have origin set to center of its bounds",
default=True,
options={'HIDDEN'}
)
slicersAction : bpy.props.EnumProperty(
name="Slicing objects action",
description="Slicing objects will be",
items = [('Delete','Delete','Deletes the objects used for slicing','',0),
('Hide','Hide','Hides the objects used for slicing','',1),
('Keep','Keep','Keeps the objects used for slicing','',2)]
)
slicerSize: bpy.props.FloatProperty(
name="Size of slicers",
options= {'HIDDEN'},
description="Size of the generated slicing planes",
default = 4
)
class GENERATE_SLICERS_OT(bpy.types.Operator):
bl_idname = 'generate.operator'
bl_label = 'Generate slicers'
bl_options = {"REGISTER", "UNDO"}
bl_description = 'Generate slicing planes'
variant: bpy.props.IntProperty(name="Generation variant",options={'HIDDEN'})
slicerContainers = []
def execute(self, context):
slicers = []
slicerSize = context.scene.my_tool.slicerSize
bpy.ops.object.select_all(action='DESELECT')
if(self.variant == 0):
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
if(self.variant == 1):
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
ob.rotation_euler[1] = math.radians(90)
if(self.variant == 2):
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
ob.rotation_euler[1] = math.radians(90)
bpy.ops.mesh.primitive_plane_add(size=1)
ob = bpy.context.active_object
ob.scale = (slicerSize,slicerSize,slicerSize)
ob.name = "Slicer"
slicers.append(ob)
ob.rotation_euler[0] = math.radians(90)
for obj in slicers:
obj.select_set(True)
return {'FINISHED'}
class SLICE_OT(bpy.types.Operator):
bl_idname = "slice.operator"
bl_label = "Slice"
bl_options = {"REGISTER", "UNDO"}
bl_description = 'Slice selected object'
slicerContainers = []
#GENERETES TEMPORAL OBJECTS USED FOR BOOL OPERATION
def makeSlicers(self, size, srcObject):
#CREATE ACTUAL OBJECTS THAT WILL BE USED FOR BOOL OPERANDS ON BOTH SIDES OF THE PLANE
#LEFT
container = SlicerContainer()
bpy.ops.mesh.primitive_cube_add(size=1, location= srcObject.location)
ob = bpy.context.active_object
ob.parent = srcObject
ob.location = (0,0,0.5)
container.left = ob;
#RIGHT
bpy.ops.mesh.primitive_cube_add(size=1, location= srcObject.location)
ob = bpy.context.active_object
ob.parent = srcObject
ob.location = (0,0,-0.5)
container.right = ob;
self.slicerContainers.append(container)
#CLONES SOURCE OBJECT
def cloneObject(self, objectToClone):
ob = objectToClone.copy()
ob.data = objectToClone.data.copy()
bpy.data.collections["Collection"].objects.link(ob)
bpy.ops.object.select_all(action='DESELECT')
ob.select_set(True)
bpy.context.view_layer.objects.active = ob
ob.name = "Clone"
return ob
#PERFORMS THE BOOL OPERATION
def doBool(self, src, object, operation):
boolmod = src.modifiers.new("Bool", 'BOOLEAN')
boolmod.object = object
boolmod.solver = 'EXACT'
boolmod.operation = operation
bpy.ops.object.modifier_apply(modifier='Bool')
#BUTTON CALLBACK
def execute(self, context):
self.slicerContainers.clear()
selectedObjects = bpy.context.selected_objects
if(len(selectedObjects) < 2):
self.report({"ERROR"}, "You need to select at least two objects!")
return {"CANCELLED"}
activeObjects = []
objectsToRemove = []
activeObjects.append(bpy.context.active_object)
#REMOVE ACTIVE FROM SLICERS
selectedObjects.remove(activeObjects[0])
#CREATE THE DUMMY OBJECTS TO USE FOR BOOL
for src in selectedObjects:
self.makeSlicers(context.scene.my_tool.slicerSize, src)
loopCount = len(selectedObjects)-1
#MAIN LOOP
for index in range(len(selectedObjects)):
newActiveObjects = []
for active in activeObjects:
newObj = self.cloneObject(active)
self.doBool(newObj,self.slicerContainers[index].left,'DIFFERENCE')
if(len(newObj.data.vertices) == 0):
bpy.ops.object.delete()
else:
newActiveObjects.append(newObj)
#USE INDEX TO AVOID REMOVING THE RESULT OF LAST SLICING LOOP
if newObj not in objectsToRemove and index < loopCount:
objectsToRemove.append(newObj)
newObj = self.cloneObject(active)
self.doBool(newObj,self.slicerContainers[index].right,'DIFFERENCE')
if(len(newObj.data.vertices) == 0):
bpy.ops.object.delete()
else:
newActiveObjects.append(newObj)
if newObj not in objectsToRemove and index < loopCount:
objectsToRemove.append(newObj)
#HIDE ACTIVE OBJECT
if(context.scene.my_tool.bool_keepOriginal == False):
if active not in objectsToRemove:
objectsToRemove.append(active)
else:
active.hide_set(True)
#UPDATE THE LIST OF OBJECT TO SLICE WITH RESULTS OF RECENT SLICING
activeObjects = newActiveObjects
#CLEANUP
#DELETE BOOL CONTAINERS
for container in self.slicerContainers:
bpy.data.objects.remove(container.left, do_unlink=True)
bpy.data.objects.remove(container.right, do_unlink=True)
self.slicerContainers.clear()
#CLEANUP SLICER PLANES
for src in selectedObjects:
if(context.scene.my_tool.slicersAction == "Hide"):
src.hide_set(True)
else:
if(context.scene.my_tool.slicersAction == "Delete"):
bpy.data.objects.remove(src, do_unlink=True)
#RENAME
for i in range(len(activeObjects)):
activeObjects[i].name = "Bool result " + str(i)
if(context.scene.my_tool.bool_resetOrigin == True):
activeObjects[i].select_set(True)
bpy.context.view_layer.objects.active = activeObjects[i]
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
#REMOVE TEMP OBJECTS
if(context.scene.my_tool.bool_deleteChunks == True):
for obj in objectsToRemove:
bpy.data.objects.remove(obj, do_unlink=True)
objectsToRemove.clear()
return {'FINISHED'}
class SLICE_PT_Panel(bpy.types.Panel):
bl_label = "Mesh Slicer"
bl_idname = "SLICE_PT_PANEL"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
my_tool = context.scene.my_tool
#SLICER GENERATION
layout.label(text="Create slicers:")
box = layout.box()
box.prop(my_tool, "slicerSize")
sub = box.row()
sub.scale_y = 2.0
sub.operator(GENERATE_SLICERS_OT.bl_idname, text= "Single").variant = 0
sub.operator(GENERATE_SLICERS_OT.bl_idname, text= "Double").variant = 1
sub.operator(GENERATE_SLICERS_OT.bl_idname, text= "Triple").variant = 2
#SLICING OPTIONS
layout.label(text="Perform slicing:")
box = layout.box()
row = box.row()
row.prop(my_tool, "bool_deleteChunks")
row = box.row()
row.prop(my_tool, "bool_keepOriginal")
row = box.row()
row.prop(my_tool, "bool_resetOrigin")
row = box.row()
row.label(text= "What to do with slicers?", icon= 'SELECT_INTERSECT')
row = box.row()
row.prop(my_tool, "slicersAction", text= "")
layout.separator()
row = box.row(align=True)
sub = row.row()
sub.scale_y = 2.0
#SLICE BUTTON
objName = "Slice"
if(len(bpy.context.selected_objects) > 0):
objName = "Slice: " + str(bpy.context.active_object.name)
sub.operator(SLICE_OT.bl_idname, icon= 'MOD_BOOLEAN', text= objName)
if(len(bpy.context.selected_objects) > 1):
sub.enabled = True
else:
sub.enabled = False
def register():
bpy.utils.register_class(SLICE_PT_Panel)
bpy.utils.register_class(SLICE_OT)
bpy.utils.register_class(GENERATE_SLICERS_OT)
bpy.utils.register_class(My_settings)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=My_settings)
def unregister():
del bpy.types.Scene.my_tool
bpy.utils.unregister_class(SLICE_PT_Panel)
bpy.utils.unregister_class(SLICE_OT)
bpy.utils.unregister_class(GENERATE_SLICERS_OT)
bpy.utils.unregister_class(My_settings)
if __name__ == "__main__":
register()