1 # SPDX-License-Identifier: GPL-2.0-or-later
6 from bpy
.props
import (
13 from mathutils
import (
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 (
27 from .carver_utils
import (
32 Selection_Save_Restore
,
53 from .carver_draw
import draw_callback_px
56 class CARVER_OT_operator(bpy
.types
.Operator
):
57 bl_idname
= "carver.operator"
59 bl_description
= "Cut or create Meshes in Object mode"
60 bl_options
= {'REGISTER', 'UNDO'}
64 # Carve mode: Cut, Object, Profile
66 self
.CreateMode
= False
67 self
.ObjectMode
= False
68 self
.ProfileMode
= False
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)
81 # Cut Rectangle coordinates
82 self
.rectangle_coord
= []
84 # Selected type of cut
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)]
108 self
.dont_apply_boolean
= context
.scene
.mesh_carver
.DontApply
109 self
.Auto_BevelUpdate
= True
112 self
.stepAngle
= [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
115 # Primitives Position
118 self
.InitPosition
= False
120 # Close polygonal shape
124 self
.snapCursor
= context
.scene
.mesh_carver
.DepthCursor
130 self
.OpsObj
= context
.active_object
132 # Rebool forced (cut line)
133 self
.ForceRebool
= False
135 self
.ViewVector
= Vector()
136 self
.CurrentObj
= None
139 self
.BrushSolidify
= False
140 self
.WidthSolidify
= False
141 self
.CarveDepth
= False
142 self
.BrushDepth
= False
143 self
.BrushDepthOffset
= 0.0
146 self
.ObjectScale
= False
148 #Init create circle primitive
152 self
.CurLoc
= Vector((0.0, 0.0, 0.0))
153 self
.SavCurLoc
= Vector((0.0, 0.0, 0.0))
156 self
.mouse_region
= -1, -1
157 self
.SavMousePos
= None
160 # Scale, rotate object
164 self
.quat_rot_axis
= 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
179 'rotation_quaternion' : None,
180 'rotation_euler' : None,
181 'display_type' : 'WIRE',
182 'show_in_front' : False
192 self
.GridScaleX
= False
193 self
.GridScaleY
= False
196 def poll(cls
, context
):
198 if len(context
.selected_objects
) > 0:
199 ob
= context
.selected_objects
[0]
200 # Test if selected object or none (for create mode)
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
):
208 region_types
= {'WINDOW', 'UI'}
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
:
220 # Change the snap increment value using the wheel mouse
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
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'}
244 self
.shift
= True if event
.shift
else False
247 self
.ctrl
= True if event
.ctrl
else False
252 # [Alt] press : Init position variable before moving the cut brush with LMB
254 if self
.InitPosition
is False:
257 self
.last_mouse_region_x
= event
.mouse_region_x
258 self
.last_mouse_region_y
= event
.mouse_region_y
259 self
.InitPosition
= 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
])
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')
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')
292 SelectObject(self
, self
.ObjectBrush
)
294 SelectObject(self
, self
.ProfileBrush
)
295 duplicateObject(self
)
299 # Save selected objects
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')
307 SelectObject(self
, self
.ObjectBrush
)
309 SelectObject(self
, self
.ProfileBrush
)
310 duplicateObject(self
)
314 # Save cursor position
315 self
.SavMousePos
= self
.CurLoc
317 if self
.CutMode
is False:
323 if self
.CutType
== self
.line
:
325 CreateCutLine(self
, context
)
328 self
.CreateGeometry()
329 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
331 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
333 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
335 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
343 #-----------------------------------------------------
345 #-----------------------------------------------------
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
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
363 self
.BoolOps
= self
.difference
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
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
381 self
.ObjectMode
= False
383 self
.BrushSolidify
= False
384 Selection_Save_Restore(self
)
387 createMeshFromData(self
)
388 self
.ProfileBrush
= bpy
.data
.objects
["CT_Profil"]
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
396 self
.ProfileBrush
.show_in_front
= True
398 solidify_modifier
= context
.object.modifiers
.new("CT_SOLIDIFY",
400 solidify_modifier
.thickness
= 0.1
402 Selection_Restore(self
)
404 self
.CList
= self
.CurrentSelection
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
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
)
429 if self
.SolidifyPossible
:
430 #Store active and selected objects
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
437 self
.ObjectBrush
.show_in_front
= True
438 solidify_modifier
= context
.object.modifiers
.new("CT_SOLIDIFY",
440 solidify_modifier
.thickness
= 0.1
442 #Restore selected and active object
443 Selection_Restore(self
)
446 if event
.type == self
.carver_prefs
.Key_Help
and event
.value
== 'PRESS':
447 self
.AskHelp
= not self
.AskHelp
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':
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
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
472 if event
.type == 'UP_ARROW' and event
.value
== 'PRESS':
474 update_grid(self
, context
)
476 # Array : Delete column
477 elif event
.type == 'DOWN_ARROW' and event
.value
== 'PRESS':
479 update_grid(self
, context
)
482 elif event
.type == 'RIGHT_ARROW' and event
.value
== 'PRESS':
484 update_grid(self
, context
)
487 elif event
.type == 'LEFT_ARROW' and event
.value
== 'PRESS':
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
510 if (self
.ObjectMode
or self
.ProfileMode
) and (self
.SolidifyPossible
):
514 z
= self
.ObjectBrush
.data
.vertices
[0].co
.z
516 for v
in self
.ObjectBrush
.data
.vertices
:
517 if abs(v
.co
.z
- z
) > ErrorMarge
:
519 self
.CarveDepth
= True
520 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
525 for mb
in self
.ObjectBrush
.modifiers
:
526 if mb
.type == 'SOLIDIFY':
527 AlreadySoldify
= True
529 for mb
in self
.ProfileBrush
.modifiers
:
530 if mb
.type == 'SOLIDIFY':
531 AlreadySoldify
= True
533 if AlreadySoldify
is False:
535 self
.BrushSolidify
= True
537 bpy
.ops
.object.select_all(action
='TOGGLE')
539 self
.ObjectBrush
.select_set(True)
540 context
.view_layer
.objects
.active
= self
.ObjectBrush
542 self
.ObjectBrush
.show_in_front
= True
544 self
.ProfileBrush
.select_set(True)
545 context
.view_layer
.objects
.active
= self
.ProfileBrush
547 self
.ProfileBrush
.show_in_front
= True
549 solidify_modifier
= context
.object.modifiers
.new("CT_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':
560 self
.CarveDepth
= False
562 self
.BrushDepth
= True
563 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
566 if event
.type == 'R' and event
.value
== 'PRESS':
567 self
.RandomRotation
= not self
.RandomRotation
570 if event
.type == 'Z' and event
.value
== 'PRESS':
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:] = []
579 if event
.type == 'MOUSEMOVE' :
580 if self
.ObjectMode
or self
.ProfileMode
:
584 if self
.WidthSolidify
:
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
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
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
636 ((int((event
.mouse_region_x
- self
.xSavMouse
) / 10.0) * PI
/ 4.0) * 25.0)
638 self
.aRotZ
-= event
.mouse_region_x
- self
.mouse_region
[0]
641 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
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
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
))
666 self
.CurLoc
= target_hit
668 self
.CurLoc
= target_hit
671 if self
.alt
is False:
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
)
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
)
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
702 self
.nRotZ
= int((self
.aRotZ
/ 25.0) / (PI
/ 4.0))
703 self
.aRotZ
= self
.nRotZ
* (PI
/ 4.0) * 25.0
708 elif event
.type == 'LEFTMOUSE' and event
.value
== 'RELEASE' and self
.in_view_3d
:
709 if self
.ObjectMode
or self
.ProfileMode
:
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
731 if self
.CutMode
is False:
733 Picking(context
, event
)
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
))
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
)
747 if self
.CutType
!= self
.line
:
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
)
758 self
.CreateGeometry()
759 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
761 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
763 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
765 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
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']
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
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'):
806 if self
.nProfil
>= self
.MaxProfil
:
808 createMeshFromData(self
)
809 # Circle subdivisions
810 if self
.CutType
== self
.circle
:
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'):
821 self
.nProfil
= self
.MaxProfil
- 1
822 createMeshFromData(self
)
823 # Circle subdivisions
824 if self
.CutType
== self
.circle
:
828 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
830 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
832 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
834 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
835 # Apply boolean operation
836 context
.scene
.mesh_carver
.DontApply
= self
.dont_apply_boolean
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
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()
876 return {'RUNNING_MODAL'}
879 print("\n[Carver MT ERROR]\n")
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)")
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")
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")
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
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
):
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
)
932 for v
in o
.data
.vertices
:
933 vertices
.append((v
.co
.x
, v
.co
.y
, v
.co
.z
))
936 for f
in o
.data
.polygons
:
945 Vector((o
.location
.x
, o
.location
.y
, o
.location
.z
)),
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
977 self
.SolidifyPossible
= True
978 for v
in self
.ObjectBrush
.data
.vertices
:
979 if abs(v
.co
.z
- z
) > ErrorMarge
:
980 self
.SolidifyPossible
= False
988 for obj
in context
.selected_objects
:
989 if obj
!= self
.ObjectBrush
:
990 self
.OB_List
.append(obj
)
997 self
.undo_limit
= context
.preferences
.edit
.undo_steps
1000 # Boolean operations type
1001 self
.BooleanType
= 0
1004 self
.UList_Index
= -1
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":
1017 elif region
.type == "UI":
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
1027 self
.in_view_3d
= False
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
1041 bpy
.ops
.view3d
.localview()
1043 if self
.ExclusiveCreateMode
:
1047 ActiveObj
= self
.CurrentSelection
[0]
1048 if ActiveObj
is not None:
1050 objBBDiagonal
= objDiagonal(ActiveObj
) / 4
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)
1088 bpy
.ops
.view3d
.localview()
1090 self
.CutMode
= False
1091 self
.mouse_path
.clear()
1092 self
.mouse_path
= [(0, 0), (0, 0)]
1095 context
= bpy
.context
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
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
= []
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
:
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')
1148 for o
in self
.CurrentSelection
:
1149 if o
!= self
.ObjectBrush
:
1150 selected_obj_list
.append(o
)
1151 self
.CurrentObj
= self
.ObjectBrush
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
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')
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')
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")
1190 boolean_operation(bool_type
="DIFFERENCE")
1192 boolean_operation(bool_type
="DIFFERENCE")
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
):
1200 bpy
.ops
.object.modifier_apply(modifier
=BMname
)
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')
1208 if self
.ObjectMode
or self
.ProfileMode
:
1209 for mb
in self
.CurrentObj
.modifiers
:
1210 if (mb
.type == 'SOLIDIFY') and (mb
.name
== "CT_SOLIDIFY"):
1212 bpy
.ops
.object.modifier_apply(modifier
="CT_SOLIDIFY")
1214 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1215 self
.report({'ERROR'}, str(exc_value
))
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')
1232 bpy
.ops
.object.delete(use_global
=False)
1234 context
.scene
.cursor
.location
= CursorLocation
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:
1243 BMname
= "CT_" + self
.CurrentObj
.name
1244 for mb
in ActiveObj
.modifiers
:
1245 if (mb
.type == 'BOOLEAN') and (mb
.name
== BMname
):
1247 bpy
.ops
.object.modifier_apply(modifier
=BMname
)
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:
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)
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
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')
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"
1303 bpy
.ops
.view3d
.localview()
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(
1320 OInstanciate
: BoolProperty(
1321 name
="Obj_Instantiate",
1324 ORandom
: BoolProperty(
1325 name
="Random_Rotation",
1328 DontApply
: BoolProperty(
1332 nProfile
: IntProperty(
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
)
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