1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 from bpy
.props
import (
15 from mathutils
import (
19 from bpy_extras
.view3d_utils
import (
20 region_2d_to_vector_3d
,
21 region_2d_to_origin_3d
,
22 region_2d_to_location_3d
,
23 location_3d_to_region_2d
,
25 from .carver_profils
import (
29 from .carver_utils
import (
34 Selection_Save_Restore
,
55 from .carver_draw
import draw_callback_px
58 class CARVER_OT_operator(bpy
.types
.Operator
):
59 bl_idname
= "carver.operator"
61 bl_description
= "Cut or create Meshes in Object mode"
62 bl_options
= {'REGISTER', 'UNDO'}
66 # Carve mode: Cut, Object, Profile
68 self
.CreateMode
= False
69 self
.ObjectMode
= False
70 self
.ProfileMode
= False
73 self
.ExclusiveCreateMode
= False
74 if len(context
.selected_objects
) == 0:
75 self
.ExclusiveCreateMode
= True
76 self
.CreateMode
= True
78 # Cut type (Rectangle, Circle, Line)
83 # Cut Rectangle coordinates
84 self
.rectangle_coord
= []
86 # Selected type of cut
93 self
.BoolOps
= self
.difference
95 self
.CurrentSelection
= context
.selected_objects
.copy()
96 self
.CurrentActive
= context
.active_object
97 self
.all_sel_obj_list
= context
.selected_objects
.copy()
98 self
.save_active_obj
= None
100 args
= (self
, context
)
101 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(draw_callback_px
, args
, 'WINDOW', 'POST_PIXEL')
103 self
.mouse_path
= [(0, 0), (0, 0)]
110 self
.dont_apply_boolean
= context
.scene
.mesh_carver
.DontApply
111 self
.Auto_BevelUpdate
= True
114 self
.stepAngle
= [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
117 # Primitives Position
120 self
.InitPosition
= False
122 # Close polygonal shape
126 self
.snapCursor
= context
.scene
.mesh_carver
.DepthCursor
132 self
.OpsObj
= context
.active_object
134 # Rebool forced (cut line)
135 self
.ForceRebool
= False
137 self
.ViewVector
= Vector()
138 self
.CurrentObj
= None
141 self
.BrushSolidify
= False
142 self
.WidthSolidify
= False
143 self
.CarveDepth
= False
144 self
.BrushDepth
= False
145 self
.BrushDepthOffset
= 0.0
148 self
.ObjectScale
= False
150 #Init create circle primitive
154 self
.CurLoc
= Vector((0.0, 0.0, 0.0))
155 self
.SavCurLoc
= Vector((0.0, 0.0, 0.0))
158 self
.mouse_region
= -1, -1
159 self
.SavMousePos
= None
162 # Scale, rotate object
166 self
.quat_rot_axis
= None
169 self
.RandomRotation
= context
.scene
.mesh_carver
.ORandom
171 self
.ShowCursor
= True
173 self
.Instantiate
= context
.scene
.mesh_carver
.OInstanciate
175 self
.ProfileBrush
= None
176 self
.ObjectBrush
= None
181 'rotation_quaternion' : None,
182 'rotation_euler' : None,
183 'display_type' : 'WIRE',
184 'show_in_front' : False
194 self
.GridScaleX
= False
195 self
.GridScaleY
= False
198 def poll(cls
, context
):
200 if len(context
.selected_objects
) > 0:
201 ob
= context
.selected_objects
[0]
202 # Test if selected object or none (for create mode)
204 (ob
and ob
.type == 'MESH' and context
.mode
== 'OBJECT') or
205 (context
.mode
== 'OBJECT' and ob
is None) or
206 (context
.mode
== 'EDIT_MESH'))
208 def modal(self
, context
, event
):
210 region_types
= {'WINDOW', 'UI'}
213 # Find the limit of the view3d region
214 self
.check_region(context
,event
)
216 for area
in win
.screen
.areas
:
217 if area
.type == 'VIEW_3D':
218 for region
in area
.regions
:
219 if not region_types
or region
.type in region_types
:
222 # Change the snap increment value using the wheel mouse
224 if self
.alt
is False:
225 if self
.ctrl
and (self
.CutType
in (self
.line
, self
.rectangle
)):
226 # Get the VIEW3D area
227 for i
, a
in enumerate(context
.screen
.areas
):
228 if a
.type == 'VIEW_3D':
229 space
= context
.screen
.areas
[i
].spaces
.active
230 grid_scale
= space
.overlay
.grid_scale
231 grid_subdivisions
= space
.overlay
.grid_subdivisions
233 if event
.type == 'WHEELUPMOUSE':
234 space
.overlay
.grid_subdivisions
+= 1
235 elif event
.type == 'WHEELDOWNMOUSE':
236 space
.overlay
.grid_subdivisions
-= 1
239 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
240 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
241 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
242 return {'PASS_THROUGH'}
246 self
.shift
= True if event
.shift
else False
249 self
.ctrl
= True if event
.ctrl
else False
254 # [Alt] press : Init position variable before moving the cut brush with LMB
256 if self
.InitPosition
is False:
259 self
.last_mouse_region_x
= event
.mouse_region_x
260 self
.last_mouse_region_y
= event
.mouse_region_y
261 self
.InitPosition
= True
264 # [Alt] release : update the coordinates
265 if self
.InitPosition
and self
.alt
is False:
266 for i
in range(0, len(self
.mouse_path
)):
267 l
= list(self
.mouse_path
[i
])
270 self
.mouse_path
[i
] = tuple(l
)
272 self
.xpos
= self
.ypos
= 0
273 self
.InitPosition
= False
275 if event
.type == 'SPACE' and event
.value
== 'PRESS':
276 # If object or profile mode is TRUE : Confirm the cut
277 if self
.ObjectMode
or self
.ProfileMode
:
278 # If array, remove double with intersect meshes
279 if ((self
.nbcol
+ self
.nbrow
) > 3):
280 # Go in edit mode mode
281 bpy
.ops
.object.mode_set(mode
='EDIT')
282 # Remove duplicate vertices
283 bpy
.ops
.mesh
.remove_doubles()
284 # Return in object mode
285 bpy
.ops
.object.mode_set(mode
='OBJECT')
288 # Save selected objects
289 self
.all_sel_obj_list
= context
.selected_objects
.copy()
290 if len(context
.selected_objects
) > 0:
291 bpy
.ops
.object.select_all(action
='TOGGLE')
294 SelectObject(self
, self
.ObjectBrush
)
296 SelectObject(self
, self
.ProfileBrush
)
297 duplicateObject(self
)
301 # Save selected objects
303 if len(self
.ObjectBrush
.children
) > 0:
304 self
.all_sel_obj_list
= context
.selected_objects
.copy()
305 if len(context
.selected_objects
) > 0:
306 bpy
.ops
.object.select_all(action
='TOGGLE')
309 SelectObject(self
, self
.ObjectBrush
)
311 SelectObject(self
, self
.ProfileBrush
)
312 duplicateObject(self
)
316 # Save cursor position
317 self
.SavMousePos
= self
.CurLoc
319 if self
.CutMode
is False:
325 if self
.CutType
== self
.line
:
327 CreateCutLine(self
, context
)
330 self
.CreateGeometry()
331 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
333 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
335 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
337 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
345 #-----------------------------------------------------
347 #-----------------------------------------------------
351 if event
.type == self
.carver_prefs
.Key_Create
and event
.value
== 'PRESS':
352 if self
.ExclusiveCreateMode
is False:
353 self
.CreateMode
= not self
.CreateMode
356 if event
.type == self
.carver_prefs
.Key_Update
and event
.value
== 'PRESS':
357 self
.Auto_BevelUpdate
= not self
.Auto_BevelUpdate
359 # Boolean operation type
360 if event
.type == self
.carver_prefs
.Key_Bool
and event
.value
== 'PRESS':
361 if (self
.ProfileMode
is True) or (self
.ObjectMode
is True):
362 if self
.BoolOps
== self
.difference
:
363 self
.BoolOps
= self
.union
365 self
.BoolOps
= self
.difference
368 if event
.type == self
.carver_prefs
.Key_Brush
and event
.value
== 'PRESS':
369 self
.dont_apply_boolean
= False
370 if (self
.ProfileMode
is False) and (self
.ObjectMode
is False):
371 self
.ProfileMode
= True
373 self
.ProfileMode
= False
374 if self
.ObjectBrush
is not None:
375 if self
.ObjectMode
is False:
376 self
.ObjectMode
= True
377 self
.BrushSolidify
= False
378 self
.CList
= self
.OB_List
380 Selection_Save_Restore(self
)
381 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
383 self
.ObjectMode
= False
385 self
.BrushSolidify
= False
386 Selection_Save_Restore(self
)
389 createMeshFromData(self
)
390 self
.ProfileBrush
= bpy
.data
.objects
["CT_Profil"]
392 self
.BrushSolidify
= True
394 bpy
.ops
.object.select_all(action
='TOGGLE')
395 self
.ProfileBrush
.select_set(True)
396 context
.view_layer
.objects
.active
= self
.ProfileBrush
398 self
.ProfileBrush
.show_in_front
= True
400 solidify_modifier
= context
.object.modifiers
.new("CT_SOLIDIFY",
402 solidify_modifier
.thickness
= 0.1
404 Selection_Restore(self
)
406 self
.CList
= self
.CurrentSelection
408 if self
.ObjectBrush
is not None:
409 if self
.ObjectMode
is False:
410 if self
.ObjectBrush
is not None:
411 self
.ObjectBrush
.location
= self
.InitBrush
['location']
412 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
413 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
414 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
415 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
416 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
418 #Store active and selected objects
421 #Remove Carver modifier
422 self
.BrushSolidify
= False
423 bpy
.ops
.object.select_all(action
='TOGGLE')
424 self
.ObjectBrush
.select_set(True)
425 context
.view_layer
.objects
.active
= self
.ObjectBrush
426 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
428 #Restore selected and active object
429 Selection_Restore(self
)
431 if self
.SolidifyPossible
:
432 #Store active and selected objects
434 self
.BrushSolidify
= True
435 bpy
.ops
.object.select_all(action
='TOGGLE')
436 self
.ObjectBrush
.select_set(True)
437 context
.view_layer
.objects
.active
= self
.ObjectBrush
439 self
.ObjectBrush
.show_in_front
= True
440 solidify_modifier
= context
.object.modifiers
.new("CT_SOLIDIFY",
442 solidify_modifier
.thickness
= 0.1
444 #Restore selected and active object
445 Selection_Restore(self
)
448 if event
.type == self
.carver_prefs
.Key_Help
and event
.value
== 'PRESS':
449 self
.AskHelp
= not self
.AskHelp
452 if event
.type == self
.carver_prefs
.Key_Instant
and event
.value
== 'PRESS':
453 self
.Instantiate
= not self
.Instantiate
455 # Close polygonal shape
456 if event
.type == self
.carver_prefs
.Key_Close
and event
.value
== 'PRESS':
458 self
.Closed
= not self
.Closed
460 if event
.type == self
.carver_prefs
.Key_Apply
and event
.value
== 'PRESS':
461 self
.dont_apply_boolean
= not self
.dont_apply_boolean
464 if event
.type == self
.carver_prefs
.Key_Scale
and event
.value
== 'PRESS':
465 if self
.ObjectScale
is False:
466 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
467 self
.ObjectScale
= True
469 # Grid : Snap on grid
470 if event
.type == self
.carver_prefs
.Key_Snap
and event
.value
== 'PRESS':
471 self
.snap
= not self
.snap
474 if event
.type == 'UP_ARROW' and event
.value
== 'PRESS':
476 update_grid(self
, context
)
478 # Array : Delete column
479 elif event
.type == 'DOWN_ARROW' and event
.value
== 'PRESS':
481 update_grid(self
, context
)
484 elif event
.type == 'RIGHT_ARROW' and event
.value
== 'PRESS':
486 update_grid(self
, context
)
489 elif event
.type == 'LEFT_ARROW' and event
.value
== 'PRESS':
491 update_grid(self
, context
)
493 # Array : Scale gap between columns
494 if event
.type == self
.carver_prefs
.Key_Gapy
and event
.value
== 'PRESS':
495 if self
.GridScaleX
is False:
496 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
497 self
.GridScaleX
= True
499 # Array : Scale gap between rows
500 if event
.type == self
.carver_prefs
.Key_Gapx
and event
.value
== 'PRESS':
501 if self
.GridScaleY
is False:
502 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
503 self
.GridScaleY
= True
505 # Cursor depth or solidify pattern
506 if event
.type == self
.carver_prefs
.Key_Depth
and event
.value
== 'PRESS':
507 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
508 self
.snapCursor
= not self
.snapCursor
512 if (self
.ObjectMode
or self
.ProfileMode
) and (self
.SolidifyPossible
):
516 z
= self
.ObjectBrush
.data
.vertices
[0].co
.z
518 for v
in self
.ObjectBrush
.data
.vertices
:
519 if abs(v
.co
.z
- z
) > ErrorMarge
:
521 self
.CarveDepth
= True
522 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
527 for mb
in self
.ObjectBrush
.modifiers
:
528 if mb
.type == 'SOLIDIFY':
529 AlreadySoldify
= True
531 for mb
in self
.ProfileBrush
.modifiers
:
532 if mb
.type == 'SOLIDIFY':
533 AlreadySoldify
= True
535 if AlreadySoldify
is False:
537 self
.BrushSolidify
= True
539 bpy
.ops
.object.select_all(action
='TOGGLE')
541 self
.ObjectBrush
.select_set(True)
542 context
.view_layer
.objects
.active
= self
.ObjectBrush
544 self
.ObjectBrush
.show_in_front
= True
546 self
.ProfileBrush
.select_set(True)
547 context
.view_layer
.objects
.active
= self
.ProfileBrush
549 self
.ProfileBrush
.show_in_front
= True
551 solidify_modifier
= context
.object.modifiers
.new("CT_SOLIDIFY",
553 solidify_modifier
.thickness
= 0.1
555 Selection_Restore(self
)
557 self
.WidthSolidify
= not self
.WidthSolidify
558 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
560 if event
.type == self
.carver_prefs
.Key_BrushDepth
and event
.value
== 'PRESS':
562 self
.CarveDepth
= False
564 self
.BrushDepth
= True
565 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
568 if event
.type == 'R' and event
.value
== 'PRESS':
569 self
.RandomRotation
= not self
.RandomRotation
572 if event
.type == 'Z' and event
.value
== 'PRESS':
574 if (self
.CutType
== self
.line
) and (self
.CutMode
):
575 if len(self
.mouse_path
) > 1:
576 self
.mouse_path
[len(self
.mouse_path
) - 1:] = []
581 if event
.type == 'MOUSEMOVE' :
582 if self
.ObjectMode
or self
.ProfileMode
:
586 if self
.WidthSolidify
:
588 bpy
.data
.objects
[self
.ObjectBrush
.name
].modifiers
[
589 "CT_SOLIDIFY"].thickness
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
590 elif self
.ProfileMode
:
591 bpy
.data
.objects
[self
.ProfileBrush
.name
].modifiers
[
592 "CT_SOLIDIFY"].thickness
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
593 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
594 elif self
.CarveDepth
:
595 for v
in self
.ObjectBrush
.data
.vertices
:
596 v
.co
.z
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
597 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
598 elif self
.BrushDepth
:
599 self
.BrushDepthOffset
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
600 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
602 if (self
.GridScaleX
):
603 self
.gapx
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / 50
604 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
605 update_grid(self
, context
)
606 return {'RUNNING_MODAL'}
608 elif (self
.GridScaleY
):
609 self
.gapy
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / 50
610 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
611 update_grid(self
, context
)
612 return {'RUNNING_MODAL'}
614 elif self
.ObjectScale
:
615 self
.ascale
= -(event
.mouse_region_x
- self
.mouse_region
[0])
616 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
619 self
.ObjectBrush
.scale
.x
-= float(self
.ascale
) / 150.0
620 if self
.ObjectBrush
.scale
.x
<= 0.0:
621 self
.ObjectBrush
.scale
.x
= 0.0
622 self
.ObjectBrush
.scale
.y
-= float(self
.ascale
) / 150.0
623 if self
.ObjectBrush
.scale
.y
<= 0.0:
624 self
.ObjectBrush
.scale
.y
= 0.0
625 self
.ObjectBrush
.scale
.z
-= float(self
.ascale
) / 150.0
626 if self
.ObjectBrush
.scale
.z
<= 0.0:
627 self
.ObjectBrush
.scale
.z
= 0.0
629 elif self
.ProfileMode
:
630 if self
.ProfileBrush
is not None:
631 self
.ProfileBrush
.scale
.x
-= float(self
.ascale
) / 150.0
632 self
.ProfileBrush
.scale
.y
-= float(self
.ascale
) / 150.0
633 self
.ProfileBrush
.scale
.z
-= float(self
.ascale
) / 150.0
638 ((int((event
.mouse_region_x
- self
.xSavMouse
) / 10.0) * PI
/ 4.0) * 25.0)
640 self
.aRotZ
-= event
.mouse_region_x
- self
.mouse_region
[0]
643 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
645 target_hit
, target_normal
, target_eul_rotation
= Pick(context
, event
, self
)
646 if target_hit
is not None:
647 self
.ShowCursor
= True
648 up_vector
= Vector((0.0, 0.0, 1.0))
649 quat_rot_axis
= rot_axis_quat(up_vector
, target_normal
)
650 self
.quat_rot
= target_eul_rotation
@ quat_rot_axis
651 MoveCursor(quat_rot_axis
, target_hit
, self
)
652 self
.SavCurLoc
= target_hit
654 if self
.SavMousePos
is not None:
655 xEcart
= abs(self
.SavMousePos
.x
- self
.SavCurLoc
.x
)
656 yEcart
= abs(self
.SavMousePos
.y
- self
.SavCurLoc
.y
)
657 zEcart
= abs(self
.SavMousePos
.z
- self
.SavCurLoc
.z
)
658 if (xEcart
> yEcart
) and (xEcart
> zEcart
):
659 self
.CurLoc
= Vector(
660 (target_hit
.x
, self
.SavMousePos
.y
, self
.SavMousePos
.z
))
661 if (yEcart
> xEcart
) and (yEcart
> zEcart
):
662 self
.CurLoc
= Vector(
663 (self
.SavMousePos
.x
, target_hit
.y
, self
.SavMousePos
.z
))
664 if (zEcart
> xEcart
) and (zEcart
> yEcart
):
665 self
.CurLoc
= Vector(
666 (self
.SavMousePos
.x
, self
.SavMousePos
.y
, target_hit
.z
))
668 self
.CurLoc
= target_hit
670 self
.CurLoc
= target_hit
673 if self
.alt
is False:
675 # Find the closest position on the overlay grid and snap the mouse on it
676 # Draw a mini grid around the cursor
677 mouse_pos
= [[event
.mouse_region_x
, event
.mouse_region_y
]]
678 Snap_Cursor(self
, context
, event
, mouse_pos
)
681 if len(self
.mouse_path
) > 0:
682 self
.mouse_path
[len(self
.mouse_path
) -
683 1] = (event
.mouse_region_x
, event
.mouse_region_y
)
685 # [ALT] press, update position
686 self
.xpos
+= (event
.mouse_region_x
- self
.last_mouse_region_x
)
687 self
.ypos
+= (event
.mouse_region_y
- self
.last_mouse_region_y
)
689 self
.last_mouse_region_x
= event
.mouse_region_x
690 self
.last_mouse_region_y
= event
.mouse_region_y
692 elif event
.type == 'LEFTMOUSE' and event
.value
== 'PRESS':
693 if self
.ObjectMode
or self
.ProfileMode
:
694 if self
.LMB
is False:
695 target_hit
, target_normal
, target_eul_rotation
= Pick(context
, event
, self
)
696 if target_hit
is not None:
697 up_vector
= Vector((0.0, 0.0, 1.0))
698 self
.quat_rot_axis
= rot_axis_quat(up_vector
, target_normal
)
699 self
.quat_rot
= target_eul_rotation
@ self
.quat_rot_axis
700 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
701 self
.xSavMouse
= event
.mouse_region_x
704 self
.nRotZ
= int((self
.aRotZ
/ 25.0) / (PI
/ 4.0))
705 self
.aRotZ
= self
.nRotZ
* (PI
/ 4.0) * 25.0
710 elif event
.type == 'LEFTMOUSE' and event
.value
== 'RELEASE' and self
.in_view_3d
:
711 if self
.ObjectMode
or self
.ProfileMode
:
714 if self
.ObjectScale
is True:
715 self
.ObjectScale
= False
717 if self
.GridScaleX
is True:
718 self
.GridScaleX
= False
720 if self
.GridScaleY
is True:
721 self
.GridScaleY
= False
723 if self
.WidthSolidify
:
724 self
.WidthSolidify
= False
726 if self
.CarveDepth
is True:
727 self
.CarveDepth
= False
729 if self
.BrushDepth
is True:
730 self
.BrushDepth
= False
733 if self
.CutMode
is False:
735 Picking(context
, event
)
739 if self
.CutType
== self
.line
:
740 if self
.CutMode
is False:
741 self
.mouse_path
.clear()
742 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
743 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
745 self
.mouse_path
[0] = (event
.mouse_region_x
, event
.mouse_region_y
)
746 self
.mouse_path
[1] = (event
.mouse_region_x
, event
.mouse_region_y
)
749 if self
.CutType
!= self
.line
:
751 if self
.CutType
== self
.rectangle
:
752 CreateCutSquare(self
, context
)
753 if self
.CutType
== self
.circle
:
754 CreateCutCircle(self
, context
)
755 if self
.CutType
== self
.line
:
756 CreateCutLine(self
, context
)
760 self
.CreateGeometry()
761 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
763 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
765 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
767 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
769 context
.scene
.mesh_carver
.DontApply
= self
.dont_apply_boolean
771 # if Object mode, set initiale state
772 if self
.ObjectBrush
is not None:
773 self
.ObjectBrush
.location
= self
.InitBrush
['location']
774 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
775 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
776 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
777 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
778 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
782 self
.BrushSolidify
= False
784 bpy
.ops
.object.select_all(action
='TOGGLE')
785 self
.ObjectBrush
.select_set(True)
786 context
.view_layer
.objects
.active
= self
.ObjectBrush
788 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
790 Selection_Restore(self
)
792 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
800 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
802 # Change brush profil or circle subdivisions
803 elif (event
.type == 'COMMA' and event
.value
== 'PRESS') or \
804 (event
.type == self
.carver_prefs
.Key_Subrem
and event
.value
== 'PRESS'):
808 if self
.nProfil
>= self
.MaxProfil
:
810 createMeshFromData(self
)
811 # Circle subdivisions
812 if self
.CutType
== self
.circle
:
814 if self
.step
>= len(self
.stepAngle
):
815 self
.step
= len(self
.stepAngle
) - 1
816 # Change brush profil or circle subdivisions
817 elif (event
.type == 'PERIOD' and event
.value
== 'PRESS') or \
818 (event
.type == self
.carver_prefs
.Key_Subadd
and event
.value
== 'PRESS'):
823 self
.nProfil
= self
.MaxProfil
- 1
824 createMeshFromData(self
)
825 # Circle subdivisions
826 if self
.CutType
== self
.circle
:
830 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
832 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
834 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
836 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
837 # Apply boolean operation
838 context
.scene
.mesh_carver
.DontApply
= self
.dont_apply_boolean
841 if self
.ObjectBrush
is not None:
842 self
.ObjectBrush
.location
= self
.InitBrush
['location']
843 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
844 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
845 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
846 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
847 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
849 # Remove solidify modifier
851 self
.BrushSolidify
= False
853 bpy
.ops
.object.select_all(action
='TOGGLE')
854 self
.ObjectBrush
.select_set(True)
855 context
.view_layer
.objects
.active
= self
.ObjectBrush
857 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
858 bpy
.ops
.object.select_all(action
='TOGGLE')
860 Selection_Restore(self
)
862 Selection_Save_Restore(self
)
863 context
.view_layer
.objects
.active
= self
.CurrentActive
864 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
866 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
868 # Remove Copy Object Brush
869 if bpy
.data
.objects
.get("CarverBrushCopy") is not None:
870 brush
= bpy
.data
.objects
["CarverBrushCopy"]
871 self
.ObjectBrush
.data
= bpy
.data
.meshes
[brush
.data
.name
]
872 bpy
.ops
.object.select_all(action
='DESELECT')
873 bpy
.data
.objects
["CarverBrushCopy"].select_set(True)
874 bpy
.ops
.object.delete()
878 return {'RUNNING_MODAL'}
881 print("\n[Carver MT ERROR]\n")
883 traceback
.print_exc()
885 context
.window
.cursor_modal_set("DEFAULT")
886 context
.area
.header_text_set(None)
887 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
889 self
.report({'WARNING'},
890 "Operation finished. Failure during Carving (Check the console for more info)")
894 def cancel(self
, context
):
895 # Note: used to prevent memory leaks on quitting Blender while the modal operator
896 # is still running, gets called on return {"CANCELLED"}
897 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
899 def invoke(self
, context
, event
):
900 if context
.area
.type != 'VIEW_3D':
901 self
.report({'WARNING'},
902 "View3D not found or not currently active. Operation Cancelled")
906 # test if some other object types are selected that are not meshes
907 for obj
in context
.selected_objects
:
908 if obj
.type != "MESH":
909 self
.report({'WARNING'},
910 "Some selected objects are not of the Mesh type. Operation Cancelled")
914 if context
.mode
== 'EDIT_MESH':
915 bpy
.ops
.object.mode_set(mode
='OBJECT')
917 #Load the Carver preferences
918 self
.carver_prefs
= bpy
.context
.preferences
.addons
[__package__
].preferences
920 # Get default patterns
923 self
.Profils
.append((p
[0], p
[1], p
[2], p
[3]))
925 for o
in context
.scene
.objects
:
926 if not o
.name
.startswith(self
.carver_prefs
.ProfilePrefix
):
928 # In-scene profiles may have changed, remove them to refresh
929 for m
in bpy
.data
.meshes
:
930 if m
.name
.startswith(self
.carver_prefs
.ProfilePrefix
):
931 bpy
.data
.meshes
.remove(m
)
934 for v
in o
.data
.vertices
:
935 vertices
.append((v
.co
.x
, v
.co
.y
, v
.co
.z
))
938 for f
in o
.data
.polygons
:
947 Vector((o
.location
.x
, o
.location
.y
, o
.location
.z
)),
951 self
.nProfil
= context
.scene
.mesh_carver
.nProfile
952 self
.MaxProfil
= len(self
.Profils
)
955 # reset selected profile if last profile exceeds length of array
956 if self
.nProfil
>= self
.MaxProfil
:
957 self
.nProfil
= context
.scene
.mesh_carver
.nProfile
= 0
959 if len(context
.selected_objects
) > 1:
960 self
.ObjectBrush
= context
.active_object
962 # Copy the brush object
963 ob
= bpy
.data
.objects
.new("CarverBrushCopy", context
.object.data
.copy())
964 ob
.location
= self
.ObjectBrush
.location
965 context
.collection
.objects
.link(ob
)
966 context
.view_layer
.update()
968 # Save default variables
969 self
.InitBrush
['location'] = self
.ObjectBrush
.location
.copy()
970 self
.InitBrush
['scale'] = self
.ObjectBrush
.scale
.copy()
971 self
.InitBrush
['rotation_quaternion'] = self
.ObjectBrush
.rotation_quaternion
.copy()
972 self
.InitBrush
['rotation_euler'] = self
.ObjectBrush
.rotation_euler
.copy()
973 self
.InitBrush
['display_type'] = self
.ObjectBrush
.display_type
974 self
.InitBrush
['show_in_front'] = self
.ObjectBrush
.show_in_front
976 # Test if flat object
977 z
= self
.ObjectBrush
.data
.vertices
[0].co
.z
979 self
.SolidifyPossible
= True
980 for v
in self
.ObjectBrush
.data
.vertices
:
981 if abs(v
.co
.z
- z
) > ErrorMarge
:
982 self
.SolidifyPossible
= False
990 for obj
in context
.selected_objects
:
991 if obj
!= self
.ObjectBrush
:
992 self
.OB_List
.append(obj
)
999 self
.undo_limit
= context
.preferences
.edit
.undo_steps
1002 # Boolean operations type
1003 self
.BooleanType
= 0
1006 self
.UList_Index
= -1
1009 context
.window_manager
.modal_handler_add(self
)
1010 return {'RUNNING_MODAL'}
1012 #Get the region area where the operator is used
1013 def check_region(self
,context
,event
):
1014 if context
.area
!= None:
1015 if context
.area
.type == "VIEW_3D" :
1016 for region
in context
.area
.regions
:
1017 if region
.type == "TOOLS":
1019 elif region
.type == "UI":
1022 view_3d_region_x
= Vector((context
.area
.x
+ t_panel
.width
, context
.area
.x
+ context
.area
.width
- ui_panel
.width
))
1023 view_3d_region_y
= Vector((context
.region
.y
, context
.region
.y
+context
.region
.height
))
1025 if (event
.mouse_x
> view_3d_region_x
[0] and event
.mouse_x
< view_3d_region_x
[1] \
1026 and event
.mouse_y
> view_3d_region_y
[0] and event
.mouse_y
< view_3d_region_y
[1]):
1027 self
.in_view_3d
= True
1029 self
.in_view_3d
= False
1031 self
.in_view_3d
= False
1033 def CreateGeometry(self
):
1034 context
= bpy
.context
1035 in_local_view
= False
1037 for area
in context
.screen
.areas
:
1038 if area
.type == 'VIEW_3D':
1039 if area
.spaces
[0].local_view
is not None:
1040 in_local_view
= True
1043 bpy
.ops
.view3d
.localview()
1045 if self
.ExclusiveCreateMode
:
1049 ActiveObj
= self
.CurrentSelection
[0]
1050 if ActiveObj
is not None:
1052 objBBDiagonal
= objDiagonal(ActiveObj
) / 4
1055 if len(context
.selected_objects
) > 0:
1056 bpy
.ops
.object.select_all(action
='TOGGLE')
1058 context
.view_layer
.objects
.active
= self
.CurrentObj
1060 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1061 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1063 bpy
.ops
.object.mode_set(mode
='EDIT')
1064 bpy
.ops
.mesh
.select_all(action
='SELECT')
1065 bpy
.ops
.mesh
.select_mode(type="EDGE")
1066 if self
.snapCursor
is False:
1067 bpy
.ops
.transform
.translate(value
=self
.ViewVector
* objBBDiagonal
* subdivisions
)
1068 bpy
.ops
.mesh
.extrude_region_move(
1069 TRANSFORM_OT_translate
={"value": -self
.ViewVector
* objBBDiagonal
* subdivisions
* 2})
1071 bpy
.ops
.mesh
.select_all(action
='SELECT')
1072 bpy
.ops
.mesh
.normals_make_consistent()
1073 bpy
.ops
.object.mode_set(mode
='OBJECT')
1075 saved_location_0
= context
.scene
.cursor
.location
.copy()
1076 bpy
.ops
.view3d
.snap_cursor_to_active()
1077 saved_location
= context
.scene
.cursor
.location
.copy()
1078 bpy
.ops
.object.transform_apply(location
=True, rotation
=True, scale
=True)
1079 context
.scene
.cursor
.location
= saved_location
1080 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
1081 context
.scene
.cursor
.location
= saved_location_0
1083 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1084 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1086 for o
in self
.all_sel_obj_list
:
1087 bpy
.data
.objects
[o
.name
].select_set(True)
1090 bpy
.ops
.view3d
.localview()
1092 self
.CutMode
= False
1093 self
.mouse_path
.clear()
1094 self
.mouse_path
= [(0, 0), (0, 0)]
1097 context
= bpy
.context
1100 in_local_view
= False
1101 for area
in context
.screen
.areas
:
1102 if area
.type == 'VIEW_3D':
1103 if area
.spaces
[0].local_view
is not None:
1104 in_local_view
= True
1107 bpy
.ops
.view3d
.localview()
1109 # Save cursor position
1110 CursorLocation
= context
.scene
.cursor
.location
.copy()
1112 #List of selected objects
1113 selected_obj_list
= []
1116 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
1118 #Compute the bounding Box
1119 objBBDiagonal
= objDiagonal(self
.CurrentSelection
[0])
1120 if self
.dont_apply_boolean
:
1125 # Get selected objects
1126 selected_obj_list
= context
.selected_objects
.copy()
1128 bpy
.ops
.object.select_all(action
='TOGGLE')
1130 context
.view_layer
.objects
.active
= self
.CurrentObj
1132 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1133 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1135 bpy
.ops
.object.mode_set(mode
='EDIT')
1136 bpy
.ops
.mesh
.select_all(action
='SELECT')
1137 bpy
.ops
.mesh
.select_mode(type="EDGE")
1138 #Translate the created mesh away from the view
1139 if (self
.snapCursor
is False) or (self
.ForceRebool
):
1140 bpy
.ops
.transform
.translate(value
=self
.ViewVector
* objBBDiagonal
* subdivisions
)
1141 #Extrude the mesh region and move the result
1142 bpy
.ops
.mesh
.extrude_region_move(
1143 TRANSFORM_OT_translate
={"value": -self
.ViewVector
* objBBDiagonal
* subdivisions
* 2})
1144 bpy
.ops
.mesh
.select_all(action
='SELECT')
1145 bpy
.ops
.mesh
.normals_make_consistent()
1146 bpy
.ops
.object.mode_set(mode
='OBJECT')
1150 for o
in self
.CurrentSelection
:
1151 if o
!= self
.ObjectBrush
:
1152 selected_obj_list
.append(o
)
1153 self
.CurrentObj
= self
.ObjectBrush
1155 selected_obj_list
= self
.CurrentSelection
1156 self
.CurrentObj
= self
.ProfileBrush
1158 for obj
in self
.CurrentSelection
:
1159 UndoAdd(self
, "MESH", obj
)
1161 # List objects create with rebool
1164 for ActiveObj
in selected_obj_list
:
1165 context
.scene
.cursor
.location
= CursorLocation
1167 if len(context
.selected_objects
) > 0:
1168 bpy
.ops
.object.select_all(action
='TOGGLE')
1171 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1172 context
.view_layer
.objects
.active
= self
.CurrentObj
1174 bpy
.ops
.object.mode_set(mode
='EDIT')
1175 bpy
.ops
.mesh
.select_all(action
='SELECT')
1176 bpy
.ops
.object.mode_set(mode
='OBJECT')
1178 # Select object to cut
1179 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1180 context
.view_layer
.objects
.active
= ActiveObj
1182 bpy
.ops
.object.mode_set(mode
='EDIT')
1183 bpy
.ops
.mesh
.select_all(action
='DESELECT')
1184 bpy
.ops
.object.mode_set(mode
='OBJECT')
1187 if (self
.shift
is False) and (self
.ForceRebool
is False):
1188 if self
.ObjectMode
or self
.ProfileMode
:
1189 if self
.BoolOps
== self
.union
:
1190 boolean_operation(bool_type
="UNION")
1192 boolean_operation(bool_type
="DIFFERENCE")
1194 boolean_operation(bool_type
="DIFFERENCE")
1197 if self
.dont_apply_boolean
is False:
1198 BMname
= "CT_" + self
.CurrentObj
.name
1199 for mb
in ActiveObj
.modifiers
:
1200 if (mb
.type == 'BOOLEAN') and (mb
.name
== BMname
):
1202 bpy
.ops
.object.modifier_apply(modifier
=BMname
)
1204 bpy
.ops
.object.modifier_remove(modifier
=BMname
)
1205 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1206 self
.report({'ERROR'}, str(exc_value
))
1208 bpy
.ops
.object.select_all(action
='TOGGLE')
1210 if self
.ObjectMode
or self
.ProfileMode
:
1211 for mb
in self
.CurrentObj
.modifiers
:
1212 if (mb
.type == 'SOLIDIFY') and (mb
.name
== "CT_SOLIDIFY"):
1214 bpy
.ops
.object.modifier_apply(modifier
="CT_SOLIDIFY")
1216 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1217 self
.report({'ERROR'}, str(exc_value
))
1220 Rebool(context
, self
)
1222 # Test if not empty object
1223 if context
.selected_objects
[0]:
1224 rebool_RT
= context
.selected_objects
[0]
1225 if len(rebool_RT
.data
.vertices
) > 0:
1226 # Create Bevel for new objects
1227 CreateBevel(context
, context
.selected_objects
[0])
1229 UndoAdd(self
, "REBOOL", context
.selected_objects
[0])
1231 context
.scene
.cursor
.location
= ActiveObj
.location
1232 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
1234 bpy
.ops
.object.delete(use_global
=False)
1236 context
.scene
.cursor
.location
= CursorLocation
1239 context
.view_layer
.objects
.active
= self
.ObjectBrush
1240 if self
.ProfileMode
:
1241 context
.view_layer
.objects
.active
= self
.ProfileBrush
1243 if self
.dont_apply_boolean
is False:
1245 BMname
= "CT_" + self
.CurrentObj
.name
1246 for mb
in ActiveObj
.modifiers
:
1247 if (mb
.type == 'BOOLEAN') and (mb
.name
== BMname
):
1249 bpy
.ops
.object.modifier_apply(modifier
=BMname
)
1251 bpy
.ops
.object.modifier_remove(modifier
=BMname
)
1252 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1253 self
.report({'ERROR'}, str(exc_value
))
1254 # Get new objects created with rebool operations
1255 if len(context
.selected_objects
) > 0:
1256 if self
.shift
is True:
1257 # Get the last object selected
1258 lastSelected
.append(context
.selected_objects
[0])
1260 context
.scene
.cursor
.location
= CursorLocation
1262 if self
.dont_apply_boolean
is False:
1264 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
1265 if len(context
.selected_objects
) > 0:
1266 bpy
.ops
.object.select_all(action
='TOGGLE')
1267 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1268 bpy
.ops
.object.delete(use_global
=False)
1271 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
1273 if len(context
.selected_objects
) > 0:
1274 bpy
.ops
.object.select_all(action
='TOGGLE')
1276 # Select cut objects
1277 for obj
in lastSelected
:
1278 bpy
.data
.objects
[obj
.name
].select_set(True)
1280 for ActiveObj
in selected_obj_list
:
1281 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1282 context
.view_layer
.objects
.active
= ActiveObj
1284 list_act_obj
= context
.selected_objects
.copy()
1285 if self
.Auto_BevelUpdate
:
1286 update_bevel(context
)
1288 # Re-select initial objects
1289 bpy
.ops
.object.select_all(action
='TOGGLE')
1292 self
.ObjectBrush
.select_set(True)
1293 for ActiveObj
in selected_obj_list
:
1294 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1295 context
.view_layer
.objects
.active
= ActiveObj
1297 # If object has children, set "Wire" draw type
1298 if self
.ObjectBrush
is not None:
1299 if len(self
.ObjectBrush
.children
) > 0:
1300 self
.ObjectBrush
.display_type
= "WIRE"
1301 if self
.ProfileMode
:
1302 self
.ProfileBrush
.display_type
= "WIRE"
1305 bpy
.ops
.view3d
.localview()
1308 self
.CutMode
= False
1309 self
.mouse_path
.clear()
1310 self
.mouse_path
= [(0, 0), (0, 0)]
1312 self
.ForceRebool
= False
1314 # bpy.ops.mesh.customdata_custom_splitnormals_clear()
1317 class CarverProperties(bpy
.types
.PropertyGroup
):
1318 DepthCursor
: BoolProperty(
1322 OInstanciate
: BoolProperty(
1323 name
="Obj_Instantiate",
1326 ORandom
: BoolProperty(
1327 name
="Random_Rotation",
1330 DontApply
: BoolProperty(
1334 nProfile
: IntProperty(
1341 from bpy
.utils
import register_class
1342 bpy
.utils
.register_class(CARVER_OT_operator
)
1343 bpy
.utils
.register_class(CarverProperties
)
1344 bpy
.types
.Scene
.mesh_carver
= bpy
.props
.PointerProperty(type=CarverProperties
)
1347 from bpy
.utils
import unregister_class
1348 bpy
.utils
.unregister_class(CarverProperties
)
1349 bpy
.utils
.unregister_class(CARVER_OT_operator
)
1350 del bpy
.types
.Scene
.mesh_carver