Skinify: fix shape generation
[blender-addons.git] / object_carver / carver_operator.py
blobbcc5e77c04923fe47e0dd5dbf3d6da9857ffde08
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 import bpy_extras
5 import sys
6 from bpy.props import (
7 BoolProperty,
8 IntProperty,
9 PointerProperty,
10 StringProperty,
11 EnumProperty,
13 from mathutils import (
14 Vector,
17 from bpy_extras.view3d_utils import (
18 region_2d_to_vector_3d,
19 region_2d_to_origin_3d,
20 region_2d_to_location_3d,
21 location_3d_to_region_2d,
23 from .carver_profils import (
24 Profils
27 from .carver_utils import (
28 duplicateObject,
29 UndoListUpdate,
30 createMeshFromData,
31 SelectObject,
32 Selection_Save_Restore,
33 Selection_Save,
34 Selection_Restore,
35 update_grid,
36 objDiagonal,
37 Undo,
38 UndoAdd,
39 Pick,
40 rot_axis_quat,
41 MoveCursor,
42 Picking,
43 CreateCutSquare,
44 CreateCutCircle,
45 CreateCutLine,
46 boolean_operation,
47 update_bevel,
48 CreateBevel,
49 Rebool,
50 Snap_Cursor,
53 from .carver_draw import draw_callback_px
55 # Modal Operator
56 class CARVER_OT_operator(bpy.types.Operator):
57 bl_idname = "carver.operator"
58 bl_label = "Carver"
59 bl_description = "Cut or create Meshes in Object mode"
60 bl_options = {'REGISTER', 'UNDO'}
62 def __init__(self):
63 context = bpy.context
64 # Carve mode: Cut, Object, Profile
65 self.CutMode = False
66 self.CreateMode = False
67 self.ObjectMode = False
68 self.ProfileMode = False
70 # Create mode
71 self.ExclusiveCreateMode = False
72 if len(context.selected_objects) == 0:
73 self.ExclusiveCreateMode = True
74 self.CreateMode = True
76 # Cut type (Rectangle, Circle, Line)
77 self.rectangle = 0
78 self.line = 1
79 self.circle = 2
81 # Cut Rectangle coordinates
82 self.rectangle_coord = []
84 # Selected type of cut
85 self.CutType = 0
87 # Boolean operation
88 self.difference = 0
89 self.union = 1
91 self.BoolOps = self.difference
93 self.CurrentSelection = context.selected_objects.copy()
94 self.CurrentActive = context.active_object
95 self.all_sel_obj_list = context.selected_objects.copy()
96 self.save_active_obj = None
98 args = (self, context)
99 self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
101 self.mouse_path = [(0, 0), (0, 0)]
103 # Keyboard event
104 self.shift = False
105 self.ctrl = False
106 self.alt = False
108 self.dont_apply_boolean = context.scene.mesh_carver.DontApply
109 self.Auto_BevelUpdate = True
111 # Circle variables
112 self.stepAngle = [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
113 self.step = 4
115 # Primitives Position
116 self.xpos = 0
117 self.ypos = 0
118 self.InitPosition = False
120 # Close polygonal shape
121 self.Closed = False
123 # Depth Cursor
124 self.snapCursor = context.scene.mesh_carver.DepthCursor
126 # Help
127 self.AskHelp = False
129 # Working object
130 self.OpsObj = context.active_object
132 # Rebool forced (cut line)
133 self.ForceRebool = False
135 self.ViewVector = Vector()
136 self.CurrentObj = None
138 # Brush
139 self.BrushSolidify = False
140 self.WidthSolidify = False
141 self.CarveDepth = False
142 self.BrushDepth = False
143 self.BrushDepthOffset = 0.0
144 self.snap = False
146 self.ObjectScale = False
148 #Init create circle primitive
149 self.CLR_C = []
151 # Cursor location
152 self.CurLoc = Vector((0.0, 0.0, 0.0))
153 self.SavCurLoc = Vector((0.0, 0.0, 0.0))
155 # Mouse region
156 self.mouse_region = -1, -1
157 self.SavMousePos = None
158 self.xSavMouse = 0
160 # Scale, rotate object
161 self.ascale = 0
162 self.aRotZ = 0
163 self.nRotZ = 0
164 self.quat_rot_axis = None
165 self.quat_rot = None
167 self.RandomRotation = context.scene.mesh_carver.ORandom
169 self.ShowCursor = True
171 self.Instantiate = context.scene.mesh_carver.OInstanciate
173 self.ProfileBrush = None
174 self.ObjectBrush = None
176 self.InitBrush = {
177 'location' : None,
178 'scale' : None,
179 'rotation_quaternion' : None,
180 'rotation_euler' : None,
181 'display_type' : 'WIRE',
182 'show_in_front' : False
185 # Array variables
186 self.nbcol = 1
187 self.nbrow = 1
188 self.gapx = 0
189 self.gapy = 0
190 self.scale_x = 1
191 self.scale_y = 1
192 self.GridScaleX = False
193 self.GridScaleY = False
195 @classmethod
196 def poll(cls, context):
197 ob = None
198 if len(context.selected_objects) > 0:
199 ob = context.selected_objects[0]
200 # Test if selected object or none (for create mode)
201 return (
202 (ob and ob.type == 'MESH' and context.mode == 'OBJECT') or
203 (context.mode == 'OBJECT' and ob is None) or
204 (context.mode == 'EDIT_MESH'))
206 def modal(self, context, event):
207 PI = 3.14156
208 region_types = {'WINDOW', 'UI'}
209 win = context.window
211 # Find the limit of the view3d region
212 self.check_region(context,event)
214 for area in win.screen.areas:
215 if area.type == 'VIEW_3D':
216 for region in area.regions:
217 if not region_types or region.type in region_types:
218 region.tag_redraw()
220 # Change the snap increment value using the wheel mouse
221 if self.CutMode:
222 if self.alt is False:
223 if self.ctrl and (self.CutType in (self.line, self.rectangle)):
224 # Get the VIEW3D area
225 for i, a in enumerate(context.screen.areas):
226 if a.type == 'VIEW_3D':
227 space = context.screen.areas[i].spaces.active
228 grid_scale = space.overlay.grid_scale
229 grid_subdivisions = space.overlay.grid_subdivisions
231 if event.type == 'WHEELUPMOUSE':
232 space.overlay.grid_subdivisions += 1
233 elif event.type == 'WHEELDOWNMOUSE':
234 space.overlay.grid_subdivisions -= 1
236 if event.type in {
237 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
238 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
239 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
240 return {'PASS_THROUGH'}
242 try:
243 # [Shift]
244 self.shift = True if event.shift else False
246 # [Ctrl]
247 self.ctrl = True if event.ctrl else False
249 # [Alt]
250 self.alt = False
252 # [Alt] press : Init position variable before moving the cut brush with LMB
253 if event.alt:
254 if self.InitPosition is False:
255 self.xpos = 0
256 self.ypos = 0
257 self.last_mouse_region_x = event.mouse_region_x
258 self.last_mouse_region_y = event.mouse_region_y
259 self.InitPosition = True
260 self.alt = True
262 # [Alt] release : update the coordinates
263 if self.InitPosition and self.alt is False:
264 for i in range(0, len(self.mouse_path)):
265 l = list(self.mouse_path[i])
266 l[0] += self.xpos
267 l[1] += self.ypos
268 self.mouse_path[i] = tuple(l)
270 self.xpos = self.ypos = 0
271 self.InitPosition = False
273 if event.type == 'SPACE' and event.value == 'PRESS':
274 # If object or profile mode is TRUE : Confirm the cut
275 if self.ObjectMode or self.ProfileMode:
276 # If array, remove double with intersect meshes
277 if ((self.nbcol + self.nbrow) > 3):
278 # Go in edit mode mode
279 bpy.ops.object.mode_set(mode='EDIT')
280 # Remove duplicate vertices
281 bpy.ops.mesh.remove_doubles()
282 # Return in object mode
283 bpy.ops.object.mode_set(mode='OBJECT')
285 if self.alt:
286 # Save selected objects
287 self.all_sel_obj_list = context.selected_objects.copy()
288 if len(context.selected_objects) > 0:
289 bpy.ops.object.select_all(action='TOGGLE')
291 if self.ObjectMode:
292 SelectObject(self, self.ObjectBrush)
293 else:
294 SelectObject(self, self.ProfileBrush)
295 duplicateObject(self)
296 else:
297 # Brush Cut
298 self.Cut()
299 # Save selected objects
300 if self.ObjectMode:
301 if len(self.ObjectBrush.children) > 0:
302 self.all_sel_obj_list = context.selected_objects.copy()
303 if len(context.selected_objects) > 0:
304 bpy.ops.object.select_all(action='TOGGLE')
306 if self.ObjectMode:
307 SelectObject(self, self.ObjectBrush)
308 else:
309 SelectObject(self, self.ProfileBrush)
310 duplicateObject(self)
312 UndoListUpdate(self)
314 # Save cursor position
315 self.SavMousePos = self.CurLoc
316 else:
317 if self.CutMode is False:
318 # Cut Mode
319 self.CutType += 1
320 if self.CutType > 2:
321 self.CutType = 0
322 else:
323 if self.CutType == self.line:
324 # Cuts creation
325 CreateCutLine(self, context)
326 if self.CreateMode:
327 # Object creation
328 self.CreateGeometry()
329 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
330 # Cursor Snap
331 context.scene.mesh_carver.DepthCursor = self.snapCursor
332 # Object Instantiate
333 context.scene.mesh_carver.OInstanciate = self.Instantiate
334 # Random rotation
335 context.scene.mesh_carver.ORandom = self.RandomRotation
337 return {'FINISHED'}
338 else:
339 self.Cut()
340 UndoListUpdate(self)
343 #-----------------------------------------------------
344 # Object creation
345 #-----------------------------------------------------
348 # Object creation
349 if event.type == self.carver_prefs.Key_Create and event.value == 'PRESS':
350 if self.ExclusiveCreateMode is False:
351 self.CreateMode = not self.CreateMode
353 # Auto Bevel Update
354 if event.type == self.carver_prefs.Key_Update and event.value == 'PRESS':
355 self.Auto_BevelUpdate = not self.Auto_BevelUpdate
357 # Boolean operation type
358 if event.type == self.carver_prefs.Key_Bool and event.value == 'PRESS':
359 if (self.ProfileMode is True) or (self.ObjectMode is True):
360 if self.BoolOps == self.difference:
361 self.BoolOps = self.union
362 else:
363 self.BoolOps = self.difference
365 # Brush Mode
366 if event.type == self.carver_prefs.Key_Brush and event.value == 'PRESS':
367 self.dont_apply_boolean = False
368 if (self.ProfileMode is False) and (self.ObjectMode is False):
369 self.ProfileMode = True
370 else:
371 self.ProfileMode = False
372 if self.ObjectBrush is not None:
373 if self.ObjectMode is False:
374 self.ObjectMode = True
375 self.BrushSolidify = False
376 self.CList = self.OB_List
378 Selection_Save_Restore(self)
379 context.scene.mesh_carver.nProfile = self.nProfil
380 else:
381 self.ObjectMode = False
382 else:
383 self.BrushSolidify = False
384 Selection_Save_Restore(self)
386 if self.ProfileMode:
387 createMeshFromData(self)
388 self.ProfileBrush = bpy.data.objects["CT_Profil"]
389 Selection_Save(self)
390 self.BrushSolidify = True
392 bpy.ops.object.select_all(action='TOGGLE')
393 self.ProfileBrush.select_set(True)
394 context.view_layer.objects.active = self.ProfileBrush
395 # Set xRay
396 self.ProfileBrush.show_in_front = True
398 solidify_modifier = context.object.modifiers.new("CT_SOLIDIFY",
399 'SOLIDIFY')
400 solidify_modifier.thickness = 0.1
402 Selection_Restore(self)
404 self.CList = self.CurrentSelection
405 else:
406 if self.ObjectBrush is not None:
407 if self.ObjectMode is False:
408 if self.ObjectBrush is not None:
409 self.ObjectBrush.location = self.InitBrush['location']
410 self.ObjectBrush.scale = self.InitBrush['scale']
411 self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
412 self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
413 self.ObjectBrush.display_type = self.InitBrush['display_type']
414 self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
416 #Store active and selected objects
417 Selection_Save(self)
419 #Remove Carver modifier
420 self.BrushSolidify = False
421 bpy.ops.object.select_all(action='TOGGLE')
422 self.ObjectBrush.select_set(True)
423 context.view_layer.objects.active = self.ObjectBrush
424 bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
426 #Restore selected and active object
427 Selection_Restore(self)
428 else:
429 if self.SolidifyPossible:
430 #Store active and selected objects
431 Selection_Save(self)
432 self.BrushSolidify = True
433 bpy.ops.object.select_all(action='TOGGLE')
434 self.ObjectBrush.select_set(True)
435 context.view_layer.objects.active = self.ObjectBrush
436 # Set xRay
437 self.ObjectBrush.show_in_front = True
438 solidify_modifier = context.object.modifiers.new("CT_SOLIDIFY",
439 'SOLIDIFY')
440 solidify_modifier.thickness = 0.1
442 #Restore selected and active object
443 Selection_Restore(self)
445 # Help display
446 if event.type == self.carver_prefs.Key_Help and event.value == 'PRESS':
447 self.AskHelp = not self.AskHelp
449 # Instantiate object
450 if event.type == self.carver_prefs.Key_Instant and event.value == 'PRESS':
451 self.Instantiate = not self.Instantiate
453 # Close polygonal shape
454 if event.type == self.carver_prefs.Key_Close and event.value == 'PRESS':
455 if self.CreateMode:
456 self.Closed = not self.Closed
458 if event.type == self.carver_prefs.Key_Apply and event.value == 'PRESS':
459 self.dont_apply_boolean = not self.dont_apply_boolean
461 # Scale object
462 if event.type == self.carver_prefs.Key_Scale and event.value == 'PRESS':
463 if self.ObjectScale is False:
464 self.mouse_region = event.mouse_region_x, event.mouse_region_y
465 self.ObjectScale = True
467 # Grid : Snap on grid
468 if event.type == self.carver_prefs.Key_Snap and event.value == 'PRESS':
469 self.snap = not self.snap
471 # Array : Add column
472 if event.type == 'UP_ARROW' and event.value == 'PRESS':
473 self.nbrow += 1
474 update_grid(self, context)
476 # Array : Delete column
477 elif event.type == 'DOWN_ARROW' and event.value == 'PRESS':
478 self.nbrow -= 1
479 update_grid(self, context)
481 # Array : Add row
482 elif event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
483 self.nbcol += 1
484 update_grid(self, context)
486 # Array : Delete row
487 elif event.type == 'LEFT_ARROW' and event.value == 'PRESS':
488 self.nbcol -= 1
489 update_grid(self, context)
491 # Array : Scale gap between columns
492 if event.type == self.carver_prefs.Key_Gapy and event.value == 'PRESS':
493 if self.GridScaleX is False:
494 self.mouse_region = event.mouse_region_x, event.mouse_region_y
495 self.GridScaleX = True
497 # Array : Scale gap between rows
498 if event.type == self.carver_prefs.Key_Gapx and event.value == 'PRESS':
499 if self.GridScaleY is False:
500 self.mouse_region = event.mouse_region_x, event.mouse_region_y
501 self.GridScaleY = True
503 # Cursor depth or solidify pattern
504 if event.type == self.carver_prefs.Key_Depth and event.value == 'PRESS':
505 if (self.ObjectMode is False) and (self.ProfileMode is False):
506 self.snapCursor = not self.snapCursor
507 else:
508 # Solidify
510 if (self.ObjectMode or self.ProfileMode) and (self.SolidifyPossible):
511 solidify = True
513 if self.ObjectMode:
514 z = self.ObjectBrush.data.vertices[0].co.z
515 ErrorMarge = 0.01
516 for v in self.ObjectBrush.data.vertices:
517 if abs(v.co.z - z) > ErrorMarge:
518 solidify = False
519 self.CarveDepth = True
520 self.mouse_region = event.mouse_region_x, event.mouse_region_y
521 break
523 if solidify:
524 if self.ObjectMode:
525 for mb in self.ObjectBrush.modifiers:
526 if mb.type == 'SOLIDIFY':
527 AlreadySoldify = True
528 else:
529 for mb in self.ProfileBrush.modifiers:
530 if mb.type == 'SOLIDIFY':
531 AlreadySoldify = True
533 if AlreadySoldify is False:
534 Selection_Save(self)
535 self.BrushSolidify = True
537 bpy.ops.object.select_all(action='TOGGLE')
538 if self.ObjectMode:
539 self.ObjectBrush.select_set(True)
540 context.view_layer.objects.active = self.ObjectBrush
541 # Active le xray
542 self.ObjectBrush.show_in_front = True
543 else:
544 self.ProfileBrush.select_set(True)
545 context.view_layer.objects.active = self.ProfileBrush
546 # Active le xray
547 self.ProfileBrush.show_in_front = True
549 solidify_modifier = context.object.modifiers.new("CT_SOLIDIFY",
550 'SOLIDIFY')
551 solidify_modifier.thickness = 0.1
553 Selection_Restore(self)
555 self.WidthSolidify = not self.WidthSolidify
556 self.mouse_region = event.mouse_region_x, event.mouse_region_y
558 if event.type == self.carver_prefs.Key_BrushDepth and event.value == 'PRESS':
559 if self.ObjectMode:
560 self.CarveDepth = False
562 self.BrushDepth = True
563 self.mouse_region = event.mouse_region_x, event.mouse_region_y
565 # Random rotation
566 if event.type == 'R' and event.value == 'PRESS':
567 self.RandomRotation = not self.RandomRotation
569 # Undo
570 if event.type == 'Z' and event.value == 'PRESS':
571 if self.ctrl:
572 if (self.CutType == self.line) and (self.CutMode):
573 if len(self.mouse_path) > 1:
574 self.mouse_path[len(self.mouse_path) - 1:] = []
575 else:
576 Undo(self)
578 # Mouse move
579 if event.type == 'MOUSEMOVE' :
580 if self.ObjectMode or self.ProfileMode:
581 fac = 50.0
582 if self.shift:
583 fac = 500.0
584 if self.WidthSolidify:
585 if self.ObjectMode:
586 bpy.data.objects[self.ObjectBrush.name].modifiers[
587 "CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
588 elif self.ProfileMode:
589 bpy.data.objects[self.ProfileBrush.name].modifiers[
590 "CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
591 self.mouse_region = event.mouse_region_x, event.mouse_region_y
592 elif self.CarveDepth:
593 for v in self.ObjectBrush.data.vertices:
594 v.co.z += (event.mouse_region_x - self.mouse_region[0]) / fac
595 self.mouse_region = event.mouse_region_x, event.mouse_region_y
596 elif self.BrushDepth:
597 self.BrushDepthOffset += (event.mouse_region_x - self.mouse_region[0]) / fac
598 self.mouse_region = event.mouse_region_x, event.mouse_region_y
599 else:
600 if (self.GridScaleX):
601 self.gapx += (event.mouse_region_x - self.mouse_region[0]) / 50
602 self.mouse_region = event.mouse_region_x, event.mouse_region_y
603 update_grid(self, context)
604 return {'RUNNING_MODAL'}
606 elif (self.GridScaleY):
607 self.gapy += (event.mouse_region_x - self.mouse_region[0]) / 50
608 self.mouse_region = event.mouse_region_x, event.mouse_region_y
609 update_grid(self, context)
610 return {'RUNNING_MODAL'}
612 elif self.ObjectScale:
613 self.ascale = -(event.mouse_region_x - self.mouse_region[0])
614 self.mouse_region = event.mouse_region_x, event.mouse_region_y
616 if self.ObjectMode:
617 self.ObjectBrush.scale.x -= float(self.ascale) / 150.0
618 if self.ObjectBrush.scale.x <= 0.0:
619 self.ObjectBrush.scale.x = 0.0
620 self.ObjectBrush.scale.y -= float(self.ascale) / 150.0
621 if self.ObjectBrush.scale.y <= 0.0:
622 self.ObjectBrush.scale.y = 0.0
623 self.ObjectBrush.scale.z -= float(self.ascale) / 150.0
624 if self.ObjectBrush.scale.z <= 0.0:
625 self.ObjectBrush.scale.z = 0.0
627 elif self.ProfileMode:
628 if self.ProfileBrush is not None:
629 self.ProfileBrush.scale.x -= float(self.ascale) / 150.0
630 self.ProfileBrush.scale.y -= float(self.ascale) / 150.0
631 self.ProfileBrush.scale.z -= float(self.ascale) / 150.0
632 else:
633 if self.LMB:
634 if self.ctrl:
635 self.aRotZ = - \
636 ((int((event.mouse_region_x - self.xSavMouse) / 10.0) * PI / 4.0) * 25.0)
637 else:
638 self.aRotZ -= event.mouse_region_x - self.mouse_region[0]
639 self.ascale = 0.0
641 self.mouse_region = event.mouse_region_x, event.mouse_region_y
642 else:
643 target_hit, target_normal, target_eul_rotation = Pick(context, event, self)
644 if target_hit is not None:
645 self.ShowCursor = True
646 up_vector = Vector((0.0, 0.0, 1.0))
647 quat_rot_axis = rot_axis_quat(up_vector, target_normal)
648 self.quat_rot = target_eul_rotation @ quat_rot_axis
649 MoveCursor(quat_rot_axis, target_hit, self)
650 self.SavCurLoc = target_hit
651 if self.ctrl:
652 if self.SavMousePos is not None:
653 xEcart = abs(self.SavMousePos.x - self.SavCurLoc.x)
654 yEcart = abs(self.SavMousePos.y - self.SavCurLoc.y)
655 zEcart = abs(self.SavMousePos.z - self.SavCurLoc.z)
656 if (xEcart > yEcart) and (xEcart > zEcart):
657 self.CurLoc = Vector(
658 (target_hit.x, self.SavMousePos.y, self.SavMousePos.z))
659 if (yEcart > xEcart) and (yEcart > zEcart):
660 self.CurLoc = Vector(
661 (self.SavMousePos.x, target_hit.y, self.SavMousePos.z))
662 if (zEcart > xEcart) and (zEcart > yEcart):
663 self.CurLoc = Vector(
664 (self.SavMousePos.x, self.SavMousePos.y, target_hit.z))
665 else:
666 self.CurLoc = target_hit
667 else:
668 self.CurLoc = target_hit
669 else:
670 if self.CutMode:
671 if self.alt is False:
672 if self.ctrl :
673 # Find the closest position on the overlay grid and snap the mouse on it
674 # Draw a mini grid around the cursor
675 mouse_pos = [[event.mouse_region_x, event.mouse_region_y]]
676 Snap_Cursor(self, context, event, mouse_pos)
678 else:
679 if len(self.mouse_path) > 0:
680 self.mouse_path[len(self.mouse_path) -
681 1] = (event.mouse_region_x, event.mouse_region_y)
682 else:
683 # [ALT] press, update position
684 self.xpos += (event.mouse_region_x - self.last_mouse_region_x)
685 self.ypos += (event.mouse_region_y - self.last_mouse_region_y)
687 self.last_mouse_region_x = event.mouse_region_x
688 self.last_mouse_region_y = event.mouse_region_y
690 elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
691 if self.ObjectMode or self.ProfileMode:
692 if self.LMB is False:
693 target_hit, target_normal, target_eul_rotation = Pick(context, event, self)
694 if target_hit is not None:
695 up_vector = Vector((0.0, 0.0, 1.0))
696 self.quat_rot_axis = rot_axis_quat(up_vector, target_normal)
697 self.quat_rot = target_eul_rotation @ self.quat_rot_axis
698 self.mouse_region = event.mouse_region_x, event.mouse_region_y
699 self.xSavMouse = event.mouse_region_x
701 if self.ctrl:
702 self.nRotZ = int((self.aRotZ / 25.0) / (PI / 4.0))
703 self.aRotZ = self.nRotZ * (PI / 4.0) * 25.0
705 self.LMB = True
707 # LEFTMOUSE
708 elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE' and self.in_view_3d:
709 if self.ObjectMode or self.ProfileMode:
710 # Rotation and scale
711 self.LMB = False
712 if self.ObjectScale is True:
713 self.ObjectScale = False
715 if self.GridScaleX is True:
716 self.GridScaleX = False
718 if self.GridScaleY is True:
719 self.GridScaleY = False
721 if self.WidthSolidify:
722 self.WidthSolidify = False
724 if self.CarveDepth is True:
725 self.CarveDepth = False
727 if self.BrushDepth is True:
728 self.BrushDepth = False
730 else:
731 if self.CutMode is False:
732 if self.ctrl:
733 Picking(context, event)
735 else:
737 if self.CutType == self.line:
738 if self.CutMode is False:
739 self.mouse_path.clear()
740 self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
741 self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
742 else:
743 self.mouse_path[0] = (event.mouse_region_x, event.mouse_region_y)
744 self.mouse_path[1] = (event.mouse_region_x, event.mouse_region_y)
745 self.CutMode = True
746 else:
747 if self.CutType != self.line:
748 # Cut creation
749 if self.CutType == self.rectangle:
750 CreateCutSquare(self, context)
751 if self.CutType == self.circle:
752 CreateCutCircle(self, context)
753 if self.CutType == self.line:
754 CreateCutLine(self, context)
756 if self.CreateMode:
757 # Object creation
758 self.CreateGeometry()
759 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
760 # Depth Cursor
761 context.scene.mesh_carver.DepthCursor = self.snapCursor
762 # Instantiate object
763 context.scene.mesh_carver.OInstanciate = self.Instantiate
764 # Random rotation
765 context.scene.mesh_carver.ORandom = self.RandomRotation
766 # Apply operation
767 context.scene.mesh_carver.DontApply = self.dont_apply_boolean
769 # if Object mode, set initiale state
770 if self.ObjectBrush is not None:
771 self.ObjectBrush.location = self.InitBrush['location']
772 self.ObjectBrush.scale = self.InitBrush['scale']
773 self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
774 self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
775 self.ObjectBrush.display_type = self.InitBrush['display_type']
776 self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
778 # remove solidify
779 Selection_Save(self)
780 self.BrushSolidify = False
782 bpy.ops.object.select_all(action='TOGGLE')
783 self.ObjectBrush.select_set(True)
784 context.view_layer.objects.active = self.ObjectBrush
786 bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
788 Selection_Restore(self)
790 context.scene.mesh_carver.nProfile = self.nProfil
792 return {'FINISHED'}
793 else:
794 self.Cut()
795 UndoListUpdate(self)
796 else:
797 # Line
798 self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
800 # Change brush profil or circle subdivisions
801 elif (event.type == 'COMMA' and event.value == 'PRESS') or \
802 (event.type == self.carver_prefs.Key_Subrem and event.value == 'PRESS'):
803 # Brush profil
804 if self.ProfileMode:
805 self.nProfil += 1
806 if self.nProfil >= self.MaxProfil:
807 self.nProfil = 0
808 createMeshFromData(self)
809 # Circle subdivisions
810 if self.CutType == self.circle:
811 self.step += 1
812 if self.step >= len(self.stepAngle):
813 self.step = len(self.stepAngle) - 1
814 # Change brush profil or circle subdivisions
815 elif (event.type == 'PERIOD' and event.value == 'PRESS') or \
816 (event.type == self.carver_prefs.Key_Subadd and event.value == 'PRESS'):
817 # Brush profil
818 if self.ProfileMode:
819 self.nProfil -= 1
820 if self.nProfil < 0:
821 self.nProfil = self.MaxProfil - 1
822 createMeshFromData(self)
823 # Circle subdivisions
824 if self.CutType == self.circle:
825 if self.step > 0:
826 self.step -= 1
827 # Quit
828 elif event.type in {'RIGHTMOUSE', 'ESC'}:
829 # Depth Cursor
830 context.scene.mesh_carver.DepthCursor = self.snapCursor
831 # Instantiate object
832 context.scene.mesh_carver.OInstanciate = self.Instantiate
833 # Random Rotation
834 context.scene.mesh_carver.ORandom = self.RandomRotation
835 # Apply boolean operation
836 context.scene.mesh_carver.DontApply = self.dont_apply_boolean
838 # Reset Object
839 if self.ObjectBrush is not None:
840 self.ObjectBrush.location = self.InitBrush['location']
841 self.ObjectBrush.scale = self.InitBrush['scale']
842 self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
843 self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
844 self.ObjectBrush.display_type = self.InitBrush['display_type']
845 self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
847 # Remove solidify modifier
848 Selection_Save(self)
849 self.BrushSolidify = False
851 bpy.ops.object.select_all(action='TOGGLE')
852 self.ObjectBrush.select_set(True)
853 context.view_layer.objects.active = self.ObjectBrush
855 bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
856 bpy.ops.object.select_all(action='TOGGLE')
858 Selection_Restore(self)
860 Selection_Save_Restore(self)
861 context.view_layer.objects.active = self.CurrentActive
862 context.scene.mesh_carver.nProfile = self.nProfil
864 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
866 # Remove Copy Object Brush
867 if bpy.data.objects.get("CarverBrushCopy") is not None:
868 brush = bpy.data.objects["CarverBrushCopy"]
869 self.ObjectBrush.data = bpy.data.meshes[brush.data.name]
870 bpy.ops.object.select_all(action='DESELECT')
871 bpy.data.objects["CarverBrushCopy"].select_set(True)
872 bpy.ops.object.delete()
874 return {'FINISHED'}
876 return {'RUNNING_MODAL'}
878 except:
879 print("\n[Carver MT ERROR]\n")
880 import traceback
881 traceback.print_exc()
883 context.window.cursor_modal_set("DEFAULT")
884 context.area.header_text_set(None)
885 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
887 self.report({'WARNING'},
888 "Operation finished. Failure during Carving (Check the console for more info)")
890 return {'FINISHED'}
892 def cancel(self, context):
893 # Note: used to prevent memory leaks on quitting Blender while the modal operator
894 # is still running, gets called on return {"CANCELLED"}
895 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
897 def invoke(self, context, event):
898 if context.area.type != 'VIEW_3D':
899 self.report({'WARNING'},
900 "View3D not found or not currently active. Operation Cancelled")
901 self.cancel(context)
902 return {'CANCELLED'}
904 # test if some other object types are selected that are not meshes
905 for obj in context.selected_objects:
906 if obj.type != "MESH":
907 self.report({'WARNING'},
908 "Some selected objects are not of the Mesh type. Operation Cancelled")
909 self.cancel(context)
910 return {'CANCELLED'}
912 if context.mode == 'EDIT_MESH':
913 bpy.ops.object.mode_set(mode='OBJECT')
915 #Load the Carver preferences
916 self.carver_prefs = bpy.context.preferences.addons[__package__].preferences
918 # Get default patterns
919 self.Profils = []
920 for p in Profils:
921 self.Profils.append((p[0], p[1], p[2], p[3]))
923 for o in context.scene.objects:
924 if not o.name.startswith(self.carver_prefs.ProfilePrefix):
925 continue
926 # In-scene profiles may have changed, remove them to refresh
927 for m in bpy.data.meshes:
928 if m.name.startswith(self.carver_prefs.ProfilePrefix):
929 bpy.data.meshes.remove(m)
931 vertices = []
932 for v in o.data.vertices:
933 vertices.append((v.co.x, v.co.y, v.co.z))
935 faces = []
936 for f in o.data.polygons:
937 face = []
938 for v in f.vertices:
939 face.append(v)
941 faces.append(face)
943 self.Profils.append(
944 (o.name,
945 Vector((o.location.x, o.location.y, o.location.z)),
946 vertices, faces)
949 self.nProfil = context.scene.mesh_carver.nProfile
950 self.MaxProfil = len(self.Profils)
953 # reset selected profile if last profile exceeds length of array
954 if self.nProfil >= self.MaxProfil:
955 self.nProfil = context.scene.mesh_carver.nProfile = 0
957 if len(context.selected_objects) > 1:
958 self.ObjectBrush = context.active_object
960 # Copy the brush object
961 ob = bpy.data.objects.new("CarverBrushCopy", context.object.data.copy())
962 ob.location = self.ObjectBrush.location
963 context.collection.objects.link(ob)
964 context.view_layer.update()
966 # Save default variables
967 self.InitBrush['location'] = self.ObjectBrush.location.copy()
968 self.InitBrush['scale'] = self.ObjectBrush.scale.copy()
969 self.InitBrush['rotation_quaternion'] = self.ObjectBrush.rotation_quaternion.copy()
970 self.InitBrush['rotation_euler'] = self.ObjectBrush.rotation_euler.copy()
971 self.InitBrush['display_type'] = self.ObjectBrush.display_type
972 self.InitBrush['show_in_front'] = self.ObjectBrush.show_in_front
974 # Test if flat object
975 z = self.ObjectBrush.data.vertices[0].co.z
976 ErrorMarge = 0.01
977 self.SolidifyPossible = True
978 for v in self.ObjectBrush.data.vertices:
979 if abs(v.co.z - z) > ErrorMarge:
980 self.SolidifyPossible = False
981 break
983 self.CList = []
984 self.OPList = []
985 self.RList = []
986 self.OB_List = []
988 for obj in context.selected_objects:
989 if obj != self.ObjectBrush:
990 self.OB_List.append(obj)
992 # Left button
993 self.LMB = False
995 # Undo Variables
996 self.undo_index = 0
997 self.undo_limit = context.preferences.edit.undo_steps
998 self.undo_list = []
1000 # Boolean operations type
1001 self.BooleanType = 0
1003 self.UList = []
1004 self.UList_Index = -1
1005 self.UndoOps = []
1007 context.window_manager.modal_handler_add(self)
1008 return {'RUNNING_MODAL'}
1010 #Get the region area where the operator is used
1011 def check_region(self,context,event):
1012 if context.area != None:
1013 if context.area.type == "VIEW_3D" :
1014 for region in context.area.regions:
1015 if region.type == "TOOLS":
1016 t_panel = region
1017 elif region.type == "UI":
1018 ui_panel = region
1020 view_3d_region_x = Vector((context.area.x + t_panel.width, context.area.x + context.area.width - ui_panel.width))
1021 view_3d_region_y = Vector((context.region.y, context.region.y+context.region.height))
1023 if (event.mouse_x > view_3d_region_x[0] and event.mouse_x < view_3d_region_x[1] \
1024 and event.mouse_y > view_3d_region_y[0] and event.mouse_y < view_3d_region_y[1]):
1025 self.in_view_3d = True
1026 else:
1027 self.in_view_3d = False
1028 else:
1029 self.in_view_3d = False
1031 def CreateGeometry(self):
1032 context = bpy.context
1033 in_local_view = False
1035 for area in context.screen.areas:
1036 if area.type == 'VIEW_3D':
1037 if area.spaces[0].local_view is not None:
1038 in_local_view = True
1040 if in_local_view:
1041 bpy.ops.view3d.localview()
1043 if self.ExclusiveCreateMode:
1044 # Default width
1045 objBBDiagonal = 0.5
1046 else:
1047 ActiveObj = self.CurrentSelection[0]
1048 if ActiveObj is not None:
1049 # Object dimensions
1050 objBBDiagonal = objDiagonal(ActiveObj) / 4
1051 subdivisions = 2
1053 if len(context.selected_objects) > 0:
1054 bpy.ops.object.select_all(action='TOGGLE')
1056 context.view_layer.objects.active = self.CurrentObj
1058 bpy.data.objects[self.CurrentObj.name].select_set(True)
1059 bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
1061 bpy.ops.object.mode_set(mode='EDIT')
1062 bpy.ops.mesh.select_all(action='SELECT')
1063 bpy.ops.mesh.select_mode(type="EDGE")
1064 if self.snapCursor is False:
1065 bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
1066 bpy.ops.mesh.extrude_region_move(
1067 TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})
1069 bpy.ops.mesh.select_all(action='SELECT')
1070 bpy.ops.mesh.normals_make_consistent()
1071 bpy.ops.object.mode_set(mode='OBJECT')
1073 saved_location_0 = context.scene.cursor.location.copy()
1074 bpy.ops.view3d.snap_cursor_to_active()
1075 saved_location = context.scene.cursor.location.copy()
1076 bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
1077 context.scene.cursor.location = saved_location
1078 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
1079 context.scene.cursor.location = saved_location_0
1081 bpy.data.objects[self.CurrentObj.name].select_set(True)
1082 bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
1084 for o in self.all_sel_obj_list:
1085 bpy.data.objects[o.name].select_set(True)
1087 if in_local_view:
1088 bpy.ops.view3d.localview()
1090 self.CutMode = False
1091 self.mouse_path.clear()
1092 self.mouse_path = [(0, 0), (0, 0)]
1094 def Cut(self):
1095 context = bpy.context
1097 # Local view ?
1098 in_local_view = False
1099 for area in context.screen.areas:
1100 if area.type == 'VIEW_3D':
1101 if area.spaces[0].local_view is not None:
1102 in_local_view = True
1104 if in_local_view:
1105 bpy.ops.view3d.localview()
1107 # Save cursor position
1108 CursorLocation = context.scene.cursor.location.copy()
1110 #List of selected objects
1111 selected_obj_list = []
1113 #Cut Mode with line
1114 if (self.ObjectMode is False) and (self.ProfileMode is False):
1116 #Compute the bounding Box
1117 objBBDiagonal = objDiagonal(self.CurrentSelection[0])
1118 if self.dont_apply_boolean:
1119 subdivisions = 1
1120 else:
1121 subdivisions = 32
1123 # Get selected objects
1124 selected_obj_list = context.selected_objects.copy()
1126 bpy.ops.object.select_all(action='TOGGLE')
1128 context.view_layer.objects.active = self.CurrentObj
1130 bpy.data.objects[self.CurrentObj.name].select_set(True)
1131 bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
1133 bpy.ops.object.mode_set(mode='EDIT')
1134 bpy.ops.mesh.select_all(action='SELECT')
1135 bpy.ops.mesh.select_mode(type="EDGE")
1136 #Translate the created mesh away from the view
1137 if (self.snapCursor is False) or (self.ForceRebool):
1138 bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
1139 #Extrude the mesh region and move the result
1140 bpy.ops.mesh.extrude_region_move(
1141 TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})
1142 bpy.ops.mesh.select_all(action='SELECT')
1143 bpy.ops.mesh.normals_make_consistent()
1144 bpy.ops.object.mode_set(mode='OBJECT')
1145 else:
1146 # Create list
1147 if self.ObjectMode:
1148 for o in self.CurrentSelection:
1149 if o != self.ObjectBrush:
1150 selected_obj_list.append(o)
1151 self.CurrentObj = self.ObjectBrush
1152 else:
1153 selected_obj_list = self.CurrentSelection
1154 self.CurrentObj = self.ProfileBrush
1156 for obj in self.CurrentSelection:
1157 UndoAdd(self, "MESH", obj)
1159 # List objects create with rebool
1160 lastSelected = []
1162 for ActiveObj in selected_obj_list:
1163 context.scene.cursor.location = CursorLocation
1165 if len(context.selected_objects) > 0:
1166 bpy.ops.object.select_all(action='TOGGLE')
1168 # Select cut object
1169 bpy.data.objects[self.CurrentObj.name].select_set(True)
1170 context.view_layer.objects.active = self.CurrentObj
1172 bpy.ops.object.mode_set(mode='EDIT')
1173 bpy.ops.mesh.select_all(action='SELECT')
1174 bpy.ops.object.mode_set(mode='OBJECT')
1176 # Select object to cut
1177 bpy.data.objects[ActiveObj.name].select_set(True)
1178 context.view_layer.objects.active = ActiveObj
1180 bpy.ops.object.mode_set(mode='EDIT')
1181 bpy.ops.mesh.select_all(action='DESELECT')
1182 bpy.ops.object.mode_set(mode='OBJECT')
1184 # Boolean operation
1185 if (self.shift is False) and (self.ForceRebool is False):
1186 if self.ObjectMode or self.ProfileMode:
1187 if self.BoolOps == self.union:
1188 boolean_operation(bool_type="UNION")
1189 else:
1190 boolean_operation(bool_type="DIFFERENCE")
1191 else:
1192 boolean_operation(bool_type="DIFFERENCE")
1194 # Apply booleans
1195 if self.dont_apply_boolean is False:
1196 BMname = "CT_" + self.CurrentObj.name
1197 for mb in ActiveObj.modifiers:
1198 if (mb.type == 'BOOLEAN') and (mb.name == BMname):
1199 try:
1200 bpy.ops.object.modifier_apply(modifier=BMname)
1201 except:
1202 bpy.ops.object.modifier_remove(modifier=BMname)
1203 exc_type, exc_value, exc_traceback = sys.exc_info()
1204 self.report({'ERROR'}, str(exc_value))
1206 bpy.ops.object.select_all(action='TOGGLE')
1207 else:
1208 if self.ObjectMode or self.ProfileMode:
1209 for mb in self.CurrentObj.modifiers:
1210 if (mb.type == 'SOLIDIFY') and (mb.name == "CT_SOLIDIFY"):
1211 try:
1212 bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
1213 except:
1214 exc_type, exc_value, exc_traceback = sys.exc_info()
1215 self.report({'ERROR'}, str(exc_value))
1217 # Rebool
1218 Rebool(context, self)
1220 # Test if not empty object
1221 if context.selected_objects[0]:
1222 rebool_RT = context.selected_objects[0]
1223 if len(rebool_RT.data.vertices) > 0:
1224 # Create Bevel for new objects
1225 CreateBevel(context, context.selected_objects[0])
1227 UndoAdd(self, "REBOOL", context.selected_objects[0])
1229 context.scene.cursor.location = ActiveObj.location
1230 bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
1231 else:
1232 bpy.ops.object.delete(use_global=False)
1234 context.scene.cursor.location = CursorLocation
1236 if self.ObjectMode:
1237 context.view_layer.objects.active = self.ObjectBrush
1238 if self.ProfileMode:
1239 context.view_layer.objects.active = self.ProfileBrush
1241 if self.dont_apply_boolean is False:
1242 # Apply booleans
1243 BMname = "CT_" + self.CurrentObj.name
1244 for mb in ActiveObj.modifiers:
1245 if (mb.type == 'BOOLEAN') and (mb.name == BMname):
1246 try:
1247 bpy.ops.object.modifier_apply(modifier=BMname)
1248 except:
1249 bpy.ops.object.modifier_remove(modifier=BMname)
1250 exc_type, exc_value, exc_traceback = sys.exc_info()
1251 self.report({'ERROR'}, str(exc_value))
1252 # Get new objects created with rebool operations
1253 if len(context.selected_objects) > 0:
1254 if self.shift is True:
1255 # Get the last object selected
1256 lastSelected.append(context.selected_objects[0])
1258 context.scene.cursor.location = CursorLocation
1260 if self.dont_apply_boolean is False:
1261 # Remove cut object
1262 if (self.ObjectMode is False) and (self.ProfileMode is False):
1263 if len(context.selected_objects) > 0:
1264 bpy.ops.object.select_all(action='TOGGLE')
1265 bpy.data.objects[self.CurrentObj.name].select_set(True)
1266 bpy.ops.object.delete(use_global=False)
1267 else:
1268 if self.ObjectMode:
1269 self.ObjectBrush.display_type = self.InitBrush['display_type']
1271 if len(context.selected_objects) > 0:
1272 bpy.ops.object.select_all(action='TOGGLE')
1274 # Select cut objects
1275 for obj in lastSelected:
1276 bpy.data.objects[obj.name].select_set(True)
1278 for ActiveObj in selected_obj_list:
1279 bpy.data.objects[ActiveObj.name].select_set(True)
1280 context.view_layer.objects.active = ActiveObj
1281 # Update bevel
1282 list_act_obj = context.selected_objects.copy()
1283 if self.Auto_BevelUpdate:
1284 update_bevel(context)
1286 # Re-select initial objects
1287 bpy.ops.object.select_all(action='TOGGLE')
1288 if self.ObjectMode:
1289 # Re-select brush
1290 self.ObjectBrush.select_set(True)
1291 for ActiveObj in selected_obj_list:
1292 bpy.data.objects[ActiveObj.name].select_set(True)
1293 context.view_layer.objects.active = ActiveObj
1295 # If object has children, set "Wire" draw type
1296 if self.ObjectBrush is not None:
1297 if len(self.ObjectBrush.children) > 0:
1298 self.ObjectBrush.display_type = "WIRE"
1299 if self.ProfileMode:
1300 self.ProfileBrush.display_type = "WIRE"
1302 if in_local_view:
1303 bpy.ops.view3d.localview()
1305 # Reset variables
1306 self.CutMode = False
1307 self.mouse_path.clear()
1308 self.mouse_path = [(0, 0), (0, 0)]
1310 self.ForceRebool = False
1312 # bpy.ops.mesh.customdata_custom_splitnormals_clear()
1315 class CarverProperties(bpy.types.PropertyGroup):
1316 DepthCursor: BoolProperty(
1317 name="DepthCursor",
1318 default=False
1320 OInstanciate: BoolProperty(
1321 name="Obj_Instantiate",
1322 default=False
1324 ORandom: BoolProperty(
1325 name="Random_Rotation",
1326 default=False
1328 DontApply: BoolProperty(
1329 name="Dont_Apply",
1330 default=False
1332 nProfile: IntProperty(
1333 name="Num_Profile",
1334 default=0
1338 def register():
1339 from bpy.utils import register_class
1340 bpy.utils.register_class(CARVER_OT_operator)
1341 bpy.utils.register_class(CarverProperties)
1342 bpy.types.Scene.mesh_carver = bpy.props.PointerProperty(type=CarverProperties)
1344 def unregister():
1345 from bpy.utils import unregister_class
1346 bpy.utils.unregister_class(CarverProperties)
1347 bpy.utils.unregister_class(CARVER_OT_operator)
1348 del bpy.types.Scene.mesh_carver