4 from bpy
.props
import (
11 from mathutils
import (
15 from bpy_extras
.view3d_utils
import (
16 region_2d_to_vector_3d
,
17 region_2d_to_origin_3d
,
18 region_2d_to_location_3d
,
19 location_3d_to_region_2d
,
21 from .carver_profils
import (
25 from .carver_utils
import (
30 Selection_Save_Restore
,
51 from .carver_draw
import draw_callback_px
54 class CARVER_OT_operator(bpy
.types
.Operator
):
55 bl_idname
= "carver.operator"
57 bl_description
= "Cut or create Meshes in Object mode"
58 bl_options
= {'REGISTER', 'UNDO'}
62 # Carve mode: Cut, Object, Profile
64 self
.CreateMode
= False
65 self
.ObjectMode
= False
66 self
.ProfileMode
= False
69 self
.ExclusiveCreateMode
= False
70 if len(context
.selected_objects
) == 0:
71 self
.ExclusiveCreateMode
= True
72 self
.CreateMode
= True
74 # Cut type (Rectangle, Circle, Line)
79 # Cut Rectangle coordinates
80 self
.rectangle_coord
= []
82 # Selected type of cut
89 self
.BoolOps
= self
.difference
91 self
.CurrentSelection
= context
.selected_objects
.copy()
92 self
.CurrentActive
= context
.active_object
93 self
.all_sel_obj_list
= context
.selected_objects
.copy()
94 self
.save_active_obj
= None
96 args
= (self
, context
)
97 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(draw_callback_px
, args
, 'WINDOW', 'POST_PIXEL')
99 self
.mouse_path
= [(0, 0), (0, 0)]
106 self
.dont_apply_boolean
= context
.scene
.mesh_carver
.DontApply
107 self
.Auto_BevelUpdate
= True
110 self
.stepAngle
= [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
113 # Primitives Position
116 self
.InitPosition
= False
118 # Close polygonal shape
122 self
.snapCursor
= context
.scene
.mesh_carver
.DepthCursor
128 self
.OpsObj
= context
.active_object
130 # Rebool forced (cut line)
131 self
.ForceRebool
= False
133 self
.ViewVector
= Vector()
134 self
.CurrentObj
= None
137 self
.BrushSolidify
= False
138 self
.WidthSolidify
= False
139 self
.CarveDepth
= False
140 self
.BrushDepth
= False
141 self
.BrushDepthOffset
= 0.0
144 self
.ObjectScale
= False
146 #Init create circle primitive
150 self
.CurLoc
= Vector((0.0, 0.0, 0.0))
151 self
.SavCurLoc
= Vector((0.0, 0.0, 0.0))
154 self
.mouse_region
= -1, -1
155 self
.SavMousePos
= None
158 # Scale, rotate object
162 self
.quat_rot_axis
= None
165 self
.RandomRotation
= context
.scene
.mesh_carver
.ORandom
167 self
.ShowCursor
= True
169 self
.Instantiate
= context
.scene
.mesh_carver
.OInstanciate
171 self
.ProfileBrush
= None
172 self
.ObjectBrush
= None
177 'rotation_quaternion' : None,
178 'rotation_euler' : None,
179 'display_type' : 'WIRE',
180 'show_in_front' : False
190 self
.GridScaleX
= False
191 self
.GridScaleY
= False
194 def poll(cls
, context
):
196 if len(context
.selected_objects
) > 0:
197 ob
= context
.selected_objects
[0]
198 # Test if selected object or none (for create mode)
200 (ob
and ob
.type == 'MESH' and context
.mode
== 'OBJECT') or
201 (context
.mode
== 'OBJECT' and ob
is None) or
202 (context
.mode
== 'EDIT_MESH'))
204 def modal(self
, context
, event
):
206 region_types
= {'WINDOW', 'UI'}
209 # Find the limit of the view3d region
210 self
.check_region(context
,event
)
212 for area
in win
.screen
.areas
:
213 if area
.type == 'VIEW_3D':
214 for region
in area
.regions
:
215 if not region_types
or region
.type in region_types
:
218 # Change the snap increment value using the wheel mouse
220 if self
.alt
is False:
221 if self
.ctrl
and (self
.CutType
in (self
.line
, self
.rectangle
)):
222 # Get the VIEW3D area
223 for i
, a
in enumerate(context
.screen
.areas
):
224 if a
.type == 'VIEW_3D':
225 space
= context
.screen
.areas
[i
].spaces
.active
226 grid_scale
= space
.overlay
.grid_scale
227 grid_subdivisions
= space
.overlay
.grid_subdivisions
229 if event
.type == 'WHEELUPMOUSE':
230 space
.overlay
.grid_subdivisions
+= 1
231 elif event
.type == 'WHEELDOWNMOUSE':
232 space
.overlay
.grid_subdivisions
-= 1
235 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
236 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
237 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
238 return {'PASS_THROUGH'}
242 self
.shift
= True if event
.shift
else False
245 self
.ctrl
= True if event
.ctrl
else False
250 # [Alt] press : Init position variable before moving the cut brush with LMB
252 if self
.InitPosition
is False:
255 self
.last_mouse_region_x
= event
.mouse_region_x
256 self
.last_mouse_region_y
= event
.mouse_region_y
257 self
.InitPosition
= True
260 # [Alt] release : update the coordinates
261 if self
.InitPosition
and self
.alt
is False:
262 for i
in range(0, len(self
.mouse_path
)):
263 l
= list(self
.mouse_path
[i
])
266 self
.mouse_path
[i
] = tuple(l
)
268 self
.xpos
= self
.ypos
= 0
269 self
.InitPosition
= False
271 if event
.type == 'SPACE' and event
.value
== 'PRESS':
272 # If object or profile mode is TRUE : Confirm the cut
273 if self
.ObjectMode
or self
.ProfileMode
:
274 # If array, remove double with intersect meshes
275 if ((self
.nbcol
+ self
.nbrow
) > 3):
276 # Go in edit mode mode
277 bpy
.ops
.object.mode_set(mode
='EDIT')
278 # Remove duplicate vertices
279 bpy
.ops
.mesh
.remove_doubles()
280 # Return in object mode
281 bpy
.ops
.object.mode_set(mode
='OBJECT')
284 # Save selected objects
285 self
.all_sel_obj_list
= context
.selected_objects
.copy()
286 if len(context
.selected_objects
) > 0:
287 bpy
.ops
.object.select_all(action
='TOGGLE')
290 SelectObject(self
, self
.ObjectBrush
)
292 SelectObject(self
, self
.ProfileBrush
)
293 duplicateObject(self
)
297 # Save selected objects
299 if len(self
.ObjectBrush
.children
) > 0:
300 self
.all_sel_obj_list
= context
.selected_objects
.copy()
301 if len(context
.selected_objects
) > 0:
302 bpy
.ops
.object.select_all(action
='TOGGLE')
305 SelectObject(self
, self
.ObjectBrush
)
307 SelectObject(self
, self
.ProfileBrush
)
308 duplicateObject(self
)
312 # Save cursor position
313 self
.SavMousePos
= self
.CurLoc
315 if self
.CutMode
is False:
321 if self
.CutType
== self
.line
:
323 CreateCutLine(self
, context
)
326 self
.CreateGeometry()
327 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
329 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
331 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
333 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
341 #-----------------------------------------------------
343 #-----------------------------------------------------
347 if event
.type == self
.carver_prefs
.Key_Create
and event
.value
== 'PRESS':
348 if self
.ExclusiveCreateMode
is False:
349 self
.CreateMode
= not self
.CreateMode
352 if event
.type == self
.carver_prefs
.Key_Update
and event
.value
== 'PRESS':
353 self
.Auto_BevelUpdate
= not self
.Auto_BevelUpdate
355 # Boolean operation type
356 if event
.type == self
.carver_prefs
.Key_Bool
and event
.value
== 'PRESS':
357 if (self
.ProfileMode
is True) or (self
.ObjectMode
is True):
358 if self
.BoolOps
== self
.difference
:
359 self
.BoolOps
= self
.union
361 self
.BoolOps
= self
.difference
364 if event
.type == self
.carver_prefs
.Key_Brush
and event
.value
== 'PRESS':
365 self
.dont_apply_boolean
= False
366 if (self
.ProfileMode
is False) and (self
.ObjectMode
is False):
367 self
.ProfileMode
= True
369 self
.ProfileMode
= False
370 if self
.ObjectBrush
is not None:
371 if self
.ObjectMode
is False:
372 self
.ObjectMode
= True
373 self
.BrushSolidify
= False
374 self
.CList
= self
.OB_List
376 Selection_Save_Restore(self
)
377 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
379 self
.ObjectMode
= False
381 self
.BrushSolidify
= False
382 Selection_Save_Restore(self
)
385 createMeshFromData(self
)
386 self
.ProfileBrush
= bpy
.data
.objects
["CT_Profil"]
388 self
.BrushSolidify
= True
390 bpy
.ops
.object.select_all(action
='TOGGLE')
391 self
.ProfileBrush
.select_set(True)
392 context
.view_layer
.objects
.active
= self
.ProfileBrush
394 self
.ProfileBrush
.show_in_front
= True
396 bpy
.ops
.object.modifier_add(type='SOLIDIFY')
397 context
.object.modifiers
["Solidify"].name
= "CT_SOLIDIFY"
398 context
.object.modifiers
["CT_SOLIDIFY"].thickness
= 0.1
400 Selection_Restore(self
)
402 self
.CList
= self
.CurrentSelection
404 if self
.ObjectBrush
is not None:
405 if self
.ObjectMode
is False:
406 if self
.ObjectBrush
is not None:
407 self
.ObjectBrush
.location
= self
.InitBrush
['location']
408 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
409 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
410 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
411 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
412 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
414 #Store active and selected objects
417 #Remove Carver modifier
418 self
.BrushSolidify
= False
419 bpy
.ops
.object.select_all(action
='TOGGLE')
420 self
.ObjectBrush
.select_set(True)
421 context
.view_layer
.objects
.active
= self
.ObjectBrush
422 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
424 #Restore selected and active object
425 Selection_Restore(self
)
427 if self
.SolidifyPossible
:
428 #Store active and selected objects
430 self
.BrushSolidify
= True
431 bpy
.ops
.object.select_all(action
='TOGGLE')
432 self
.ObjectBrush
.select_set(True)
433 context
.view_layer
.objects
.active
= self
.ObjectBrush
435 self
.ObjectBrush
.show_in_front
= True
436 bpy
.ops
.object.modifier_add(type='SOLIDIFY')
437 context
.object.modifiers
["Solidify"].name
= "CT_SOLIDIFY"
438 context
.object.modifiers
["CT_SOLIDIFY"].thickness
= 0.1
440 #Restore selected and active object
441 Selection_Restore(self
)
444 if event
.type == self
.carver_prefs
.Key_Help
and event
.value
== 'PRESS':
445 self
.AskHelp
= not self
.AskHelp
448 if event
.type == self
.carver_prefs
.Key_Instant
and event
.value
== 'PRESS':
449 self
.Instantiate
= not self
.Instantiate
451 # Close polygonal shape
452 if event
.type == self
.carver_prefs
.Key_Close
and event
.value
== 'PRESS':
454 self
.Closed
= not self
.Closed
456 if event
.type == self
.carver_prefs
.Key_Apply
and event
.value
== 'PRESS':
457 self
.dont_apply_boolean
= not self
.dont_apply_boolean
460 if event
.type == self
.carver_prefs
.Key_Scale
and event
.value
== 'PRESS':
461 if self
.ObjectScale
is False:
462 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
463 self
.ObjectScale
= True
465 # Grid : Snap on grid
466 if event
.type == self
.carver_prefs
.Key_Snap
and event
.value
== 'PRESS':
467 self
.snap
= not self
.snap
470 if event
.type == 'UP_ARROW' and event
.value
== 'PRESS':
472 update_grid(self
, context
)
474 # Array : Delete column
475 elif event
.type == 'DOWN_ARROW' and event
.value
== 'PRESS':
477 update_grid(self
, context
)
480 elif event
.type == 'RIGHT_ARROW' and event
.value
== 'PRESS':
482 update_grid(self
, context
)
485 elif event
.type == 'LEFT_ARROW' and event
.value
== 'PRESS':
487 update_grid(self
, context
)
489 # Array : Scale gap between columns
490 if event
.type == self
.carver_prefs
.Key_Gapy
and event
.value
== 'PRESS':
491 if self
.GridScaleX
is False:
492 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
493 self
.GridScaleX
= True
495 # Array : Scale gap between rows
496 if event
.type == self
.carver_prefs
.Key_Gapx
and event
.value
== 'PRESS':
497 if self
.GridScaleY
is False:
498 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
499 self
.GridScaleY
= True
501 # Cursor depth or solidify pattern
502 if event
.type == self
.carver_prefs
.Key_Depth
and event
.value
== 'PRESS':
503 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
504 self
.snapCursor
= not self
.snapCursor
508 if (self
.ObjectMode
or self
.ProfileMode
) and (self
.SolidifyPossible
):
512 z
= self
.ObjectBrush
.data
.vertices
[0].co
.z
514 for v
in self
.ObjectBrush
.data
.vertices
:
515 if abs(v
.co
.z
- z
) > ErrorMarge
:
517 self
.CarveDepth
= True
518 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
523 for mb
in self
.ObjectBrush
.modifiers
:
524 if mb
.type == 'SOLIDIFY':
525 AlreadySoldify
= True
527 for mb
in self
.ProfileBrush
.modifiers
:
528 if mb
.type == 'SOLIDIFY':
529 AlreadySoldify
= True
531 if AlreadySoldify
is False:
533 self
.BrushSolidify
= True
535 bpy
.ops
.object.select_all(action
='TOGGLE')
537 self
.ObjectBrush
.select_set(True)
538 context
.view_layer
.objects
.active
= self
.ObjectBrush
540 self
.ObjectBrush
.show_in_front
= True
542 self
.ProfileBrush
.select_set(True)
543 context
.view_layer
.objects
.active
= self
.ProfileBrush
545 self
.ProfileBrush
.show_in_front
= True
547 bpy
.ops
.object.modifier_add(type='SOLIDIFY')
548 context
.object.modifiers
["Solidify"].name
= "CT_SOLIDIFY"
550 context
.object.modifiers
["CT_SOLIDIFY"].thickness
= 0.1
552 Selection_Restore(self
)
554 self
.WidthSolidify
= not self
.WidthSolidify
555 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
557 if event
.type == self
.carver_prefs
.Key_BrushDepth
and event
.value
== 'PRESS':
559 self
.CarveDepth
= False
561 self
.BrushDepth
= True
562 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
565 if event
.type == 'R' and event
.value
== 'PRESS':
566 self
.RandomRotation
= not self
.RandomRotation
569 if event
.type == 'Z' and event
.value
== 'PRESS':
571 if (self
.CutType
== self
.line
) and (self
.CutMode
):
572 if len(self
.mouse_path
) > 1:
573 self
.mouse_path
[len(self
.mouse_path
) - 1:] = []
578 if event
.type == 'MOUSEMOVE' :
579 if self
.ObjectMode
or self
.ProfileMode
:
583 if self
.WidthSolidify
:
585 bpy
.data
.objects
[self
.ObjectBrush
.name
].modifiers
[
586 "CT_SOLIDIFY"].thickness
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
587 elif self
.ProfileMode
:
588 bpy
.data
.objects
[self
.ProfileBrush
.name
].modifiers
[
589 "CT_SOLIDIFY"].thickness
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
590 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
591 elif self
.CarveDepth
:
592 for v
in self
.ObjectBrush
.data
.vertices
:
593 v
.co
.z
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
594 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
595 elif self
.BrushDepth
:
596 self
.BrushDepthOffset
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / fac
597 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
599 if (self
.GridScaleX
):
600 self
.gapx
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / 50
601 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
602 update_grid(self
, context
)
603 return {'RUNNING_MODAL'}
605 elif (self
.GridScaleY
):
606 self
.gapy
+= (event
.mouse_region_x
- self
.mouse_region
[0]) / 50
607 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
608 update_grid(self
, context
)
609 return {'RUNNING_MODAL'}
611 elif self
.ObjectScale
:
612 self
.ascale
= -(event
.mouse_region_x
- self
.mouse_region
[0])
613 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
616 self
.ObjectBrush
.scale
.x
-= float(self
.ascale
) / 150.0
617 if self
.ObjectBrush
.scale
.x
<= 0.0:
618 self
.ObjectBrush
.scale
.x
= 0.0
619 self
.ObjectBrush
.scale
.y
-= float(self
.ascale
) / 150.0
620 if self
.ObjectBrush
.scale
.y
<= 0.0:
621 self
.ObjectBrush
.scale
.y
= 0.0
622 self
.ObjectBrush
.scale
.z
-= float(self
.ascale
) / 150.0
623 if self
.ObjectBrush
.scale
.z
<= 0.0:
624 self
.ObjectBrush
.scale
.z
= 0.0
626 elif self
.ProfileMode
:
627 if self
.ProfileBrush
is not None:
628 self
.ProfileBrush
.scale
.x
-= float(self
.ascale
) / 150.0
629 self
.ProfileBrush
.scale
.y
-= float(self
.ascale
) / 150.0
630 self
.ProfileBrush
.scale
.z
-= float(self
.ascale
) / 150.0
635 ((int((event
.mouse_region_x
- self
.xSavMouse
) / 10.0) * PI
/ 4.0) * 25.0)
637 self
.aRotZ
-= event
.mouse_region_x
- self
.mouse_region
[0]
640 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
642 target_hit
, target_normal
, target_eul_rotation
= Pick(context
, event
, self
)
643 if target_hit
is not None:
644 self
.ShowCursor
= True
645 up_vector
= Vector((0.0, 0.0, 1.0))
646 quat_rot_axis
= rot_axis_quat(up_vector
, target_normal
)
647 self
.quat_rot
= target_eul_rotation
@ quat_rot_axis
648 MoveCursor(quat_rot_axis
, target_hit
, self
)
649 self
.SavCurLoc
= target_hit
651 if self
.SavMousePos
is not None:
652 xEcart
= abs(self
.SavMousePos
.x
- self
.SavCurLoc
.x
)
653 yEcart
= abs(self
.SavMousePos
.y
- self
.SavCurLoc
.y
)
654 zEcart
= abs(self
.SavMousePos
.z
- self
.SavCurLoc
.z
)
655 if (xEcart
> yEcart
) and (xEcart
> zEcart
):
656 self
.CurLoc
= Vector(
657 (target_hit
.x
, self
.SavMousePos
.y
, self
.SavMousePos
.z
))
658 if (yEcart
> xEcart
) and (yEcart
> zEcart
):
659 self
.CurLoc
= Vector(
660 (self
.SavMousePos
.x
, target_hit
.y
, self
.SavMousePos
.z
))
661 if (zEcart
> xEcart
) and (zEcart
> yEcart
):
662 self
.CurLoc
= Vector(
663 (self
.SavMousePos
.x
, self
.SavMousePos
.y
, target_hit
.z
))
665 self
.CurLoc
= target_hit
667 self
.CurLoc
= target_hit
670 if self
.alt
is False:
672 # Find the closest position on the overlay grid and snap the mouse on it
673 # Draw a mini grid around the cursor
674 mouse_pos
= [[event
.mouse_region_x
, event
.mouse_region_y
]]
675 Snap_Cursor(self
, context
, event
, mouse_pos
)
678 if len(self
.mouse_path
) > 0:
679 self
.mouse_path
[len(self
.mouse_path
) -
680 1] = (event
.mouse_region_x
, event
.mouse_region_y
)
682 # [ALT] press, update position
683 self
.xpos
+= (event
.mouse_region_x
- self
.last_mouse_region_x
)
684 self
.ypos
+= (event
.mouse_region_y
- self
.last_mouse_region_y
)
686 self
.last_mouse_region_x
= event
.mouse_region_x
687 self
.last_mouse_region_y
= event
.mouse_region_y
689 elif event
.type == 'LEFTMOUSE' and event
.value
== 'PRESS':
690 if self
.ObjectMode
or self
.ProfileMode
:
691 if self
.LMB
is False:
692 target_hit
, target_normal
, target_eul_rotation
= Pick(context
, event
, self
)
693 if target_hit
is not None:
694 up_vector
= Vector((0.0, 0.0, 1.0))
695 self
.quat_rot_axis
= rot_axis_quat(up_vector
, target_normal
)
696 self
.quat_rot
= target_eul_rotation
@ self
.quat_rot_axis
697 self
.mouse_region
= event
.mouse_region_x
, event
.mouse_region_y
698 self
.xSavMouse
= event
.mouse_region_x
701 self
.nRotZ
= int((self
.aRotZ
/ 25.0) / (PI
/ 4.0))
702 self
.aRotZ
= self
.nRotZ
* (PI
/ 4.0) * 25.0
707 elif event
.type == 'LEFTMOUSE' and event
.value
== 'RELEASE' and self
.in_view_3d
:
708 if self
.ObjectMode
or self
.ProfileMode
:
711 if self
.ObjectScale
is True:
712 self
.ObjectScale
= False
714 if self
.GridScaleX
is True:
715 self
.GridScaleX
= False
717 if self
.GridScaleY
is True:
718 self
.GridScaleY
= False
720 if self
.WidthSolidify
:
721 self
.WidthSolidify
= False
723 if self
.CarveDepth
is True:
724 self
.CarveDepth
= False
726 if self
.BrushDepth
is True:
727 self
.BrushDepth
= False
730 if self
.CutMode
is False:
732 Picking(context
, event
)
736 if self
.CutType
== self
.line
:
737 if self
.CutMode
is False:
738 self
.mouse_path
.clear()
739 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
740 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
742 self
.mouse_path
[0] = (event
.mouse_region_x
, event
.mouse_region_y
)
743 self
.mouse_path
[1] = (event
.mouse_region_x
, event
.mouse_region_y
)
746 if self
.CutType
!= self
.line
:
748 if self
.CutType
== self
.rectangle
:
749 CreateCutSquare(self
, context
)
750 if self
.CutType
== self
.circle
:
751 CreateCutCircle(self
, context
)
752 if self
.CutType
== self
.line
:
753 CreateCutLine(self
, context
)
757 self
.CreateGeometry()
758 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
760 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
762 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
764 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
766 context
.scene
.mesh_carver
.DontApply
= self
.dont_apply_boolean
768 # if Object mode, set initiale state
769 if self
.ObjectBrush
is not None:
770 self
.ObjectBrush
.location
= self
.InitBrush
['location']
771 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
772 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
773 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
774 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
775 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
779 self
.BrushSolidify
= False
781 bpy
.ops
.object.select_all(action
='TOGGLE')
782 self
.ObjectBrush
.select_set(True)
783 context
.view_layer
.objects
.active
= self
.ObjectBrush
785 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
787 Selection_Restore(self
)
789 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
797 self
.mouse_path
.append((event
.mouse_region_x
, event
.mouse_region_y
))
799 # Change brush profil or circle subdivisions
800 elif (event
.type == 'COMMA' and event
.value
== 'PRESS') or \
801 (event
.type == self
.carver_prefs
.Key_Subrem
and event
.value
== 'PRESS'):
805 if self
.nProfil
>= self
.MaxProfil
:
807 createMeshFromData(self
)
808 # Circle subdivisions
809 if self
.CutType
== self
.circle
:
811 if self
.step
>= len(self
.stepAngle
):
812 self
.step
= len(self
.stepAngle
) - 1
813 # Change brush profil or circle subdivisions
814 elif (event
.type == 'PERIOD' and event
.value
== 'PRESS') or \
815 (event
.type == self
.carver_prefs
.Key_Subadd
and event
.value
== 'PRESS'):
820 self
.nProfil
= self
.MaxProfil
- 1
821 createMeshFromData(self
)
822 # Circle subdivisions
823 if self
.CutType
== self
.circle
:
827 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
829 context
.scene
.mesh_carver
.DepthCursor
= self
.snapCursor
831 context
.scene
.mesh_carver
.OInstanciate
= self
.Instantiate
833 context
.scene
.mesh_carver
.ORandom
= self
.RandomRotation
834 # Apply boolean operation
835 context
.scene
.mesh_carver
.DontApply
= self
.dont_apply_boolean
838 if self
.ObjectBrush
is not None:
839 self
.ObjectBrush
.location
= self
.InitBrush
['location']
840 self
.ObjectBrush
.scale
= self
.InitBrush
['scale']
841 self
.ObjectBrush
.rotation_quaternion
= self
.InitBrush
['rotation_quaternion']
842 self
.ObjectBrush
.rotation_euler
= self
.InitBrush
['rotation_euler']
843 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
844 self
.ObjectBrush
.show_in_front
= self
.InitBrush
['show_in_front']
846 # Remove solidify modifier
848 self
.BrushSolidify
= False
850 bpy
.ops
.object.select_all(action
='TOGGLE')
851 self
.ObjectBrush
.select_set(True)
852 context
.view_layer
.objects
.active
= self
.ObjectBrush
854 bpy
.ops
.object.modifier_remove(modifier
="CT_SOLIDIFY")
855 bpy
.ops
.object.select_all(action
='TOGGLE')
857 Selection_Restore(self
)
859 Selection_Save_Restore(self
)
860 context
.view_layer
.objects
.active
= self
.CurrentActive
861 context
.scene
.mesh_carver
.nProfile
= self
.nProfil
863 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
865 # Remove Copy Object Brush
866 if bpy
.data
.objects
.get("CarverBrushCopy") is not None:
867 brush
= bpy
.data
.objects
["CarverBrushCopy"]
868 self
.ObjectBrush
.data
= bpy
.data
.meshes
[brush
.data
.name
]
869 bpy
.ops
.object.select_all(action
='DESELECT')
870 bpy
.data
.objects
["CarverBrushCopy"].select_set(True)
871 bpy
.ops
.object.delete()
875 return {'RUNNING_MODAL'}
878 print("\n[Carver MT ERROR]\n")
880 traceback
.print_exc()
882 context
.window
.cursor_modal_set("DEFAULT")
883 context
.area
.header_text_set(None)
884 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
886 self
.report({'WARNING'},
887 "Operation finished. Failure during Carving (Check the console for more info)")
891 def cancel(self
, context
):
892 # Note: used to prevent memory leaks on quitting Blender while the modal operator
893 # is still running, gets called on return {"CANCELLED"}
894 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
896 def invoke(self
, context
, event
):
897 if context
.area
.type != 'VIEW_3D':
898 self
.report({'WARNING'},
899 "View3D not found or not currently active. Operation Cancelled")
903 # test if some other object types are selected that are not meshes
904 for obj
in context
.selected_objects
:
905 if obj
.type != "MESH":
906 self
.report({'WARNING'},
907 "Some selected objects are not of the Mesh type. Operation Cancelled")
911 if context
.mode
== 'EDIT_MESH':
912 bpy
.ops
.object.mode_set(mode
='OBJECT')
914 #Load the Carver preferences
915 self
.carver_prefs
= bpy
.context
.preferences
.addons
[__package__
].preferences
917 # Get default patterns
920 self
.Profils
.append((p
[0], p
[1], p
[2], p
[3]))
922 for o
in context
.scene
.objects
:
923 if not o
.name
.startswith(self
.carver_prefs
.ProfilePrefix
):
925 # In-scene profiles may have changed, remove them to refresh
926 for m
in bpy
.data
.meshes
:
927 if m
.name
.startswith(self
.carver_prefs
.ProfilePrefix
):
928 bpy
.data
.meshes
.remove(m
)
931 for v
in o
.data
.vertices
:
932 vertices
.append((v
.co
.x
, v
.co
.y
, v
.co
.z
))
935 for f
in o
.data
.polygons
:
944 Vector((o
.location
.x
, o
.location
.y
, o
.location
.z
)),
948 self
.nProfil
= context
.scene
.mesh_carver
.nProfile
949 self
.MaxProfil
= len(self
.Profils
)
952 # reset selected profile if last profile exceeds length of array
953 if self
.nProfil
>= self
.MaxProfil
:
954 self
.nProfil
= context
.scene
.mesh_carver
.nProfile
= 0
956 if len(context
.selected_objects
) > 1:
957 self
.ObjectBrush
= context
.active_object
959 # Copy the brush object
960 ob
= bpy
.data
.objects
.new("CarverBrushCopy", context
.object.data
.copy())
961 ob
.location
= self
.ObjectBrush
.location
962 context
.collection
.objects
.link(ob
)
963 context
.view_layer
.update()
965 # Save default variables
966 self
.InitBrush
['location'] = self
.ObjectBrush
.location
.copy()
967 self
.InitBrush
['scale'] = self
.ObjectBrush
.scale
.copy()
968 self
.InitBrush
['rotation_quaternion'] = self
.ObjectBrush
.rotation_quaternion
.copy()
969 self
.InitBrush
['rotation_euler'] = self
.ObjectBrush
.rotation_euler
.copy()
970 self
.InitBrush
['display_type'] = self
.ObjectBrush
.display_type
971 self
.InitBrush
['show_in_front'] = self
.ObjectBrush
.show_in_front
973 # Test if flat object
974 z
= self
.ObjectBrush
.data
.vertices
[0].co
.z
976 self
.SolidifyPossible
= True
977 for v
in self
.ObjectBrush
.data
.vertices
:
978 if abs(v
.co
.z
- z
) > ErrorMarge
:
979 self
.SolidifyPossible
= False
987 for obj
in context
.selected_objects
:
988 if obj
!= self
.ObjectBrush
:
989 self
.OB_List
.append(obj
)
996 self
.undo_limit
= context
.preferences
.edit
.undo_steps
999 # Boolean operations type
1000 self
.BooleanType
= 0
1003 self
.UList_Index
= -1
1006 context
.window_manager
.modal_handler_add(self
)
1007 return {'RUNNING_MODAL'}
1009 #Get the region area where the operator is used
1010 def check_region(self
,context
,event
):
1011 if context
.area
!= None:
1012 if context
.area
.type == "VIEW_3D" :
1013 for region
in context
.area
.regions
:
1014 if region
.type == "TOOLS":
1016 elif region
.type == "UI":
1019 view_3d_region_x
= Vector((context
.area
.x
+ t_panel
.width
, context
.area
.x
+ context
.area
.width
- ui_panel
.width
))
1020 view_3d_region_y
= Vector((context
.region
.y
, context
.region
.y
+context
.region
.height
))
1022 if (event
.mouse_x
> view_3d_region_x
[0] and event
.mouse_x
< view_3d_region_x
[1] \
1023 and event
.mouse_y
> view_3d_region_y
[0] and event
.mouse_y
< view_3d_region_y
[1]):
1024 self
.in_view_3d
= True
1026 self
.in_view_3d
= False
1028 self
.in_view_3d
= False
1030 def CreateGeometry(self
):
1031 context
= bpy
.context
1032 in_local_view
= False
1034 for area
in context
.screen
.areas
:
1035 if area
.type == 'VIEW_3D':
1036 if area
.spaces
[0].local_view
is not None:
1037 in_local_view
= True
1040 bpy
.ops
.view3d
.localview()
1042 if self
.ExclusiveCreateMode
:
1046 ActiveObj
= self
.CurrentSelection
[0]
1047 if ActiveObj
is not None:
1049 objBBDiagonal
= objDiagonal(ActiveObj
) / 4
1052 if len(context
.selected_objects
) > 0:
1053 bpy
.ops
.object.select_all(action
='TOGGLE')
1055 context
.view_layer
.objects
.active
= self
.CurrentObj
1057 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1058 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1060 bpy
.ops
.object.mode_set(mode
='EDIT')
1061 bpy
.ops
.mesh
.select_all(action
='SELECT')
1062 bpy
.ops
.mesh
.select_mode(type="EDGE")
1063 if self
.snapCursor
is False:
1064 bpy
.ops
.transform
.translate(value
=self
.ViewVector
* objBBDiagonal
* subdivisions
)
1065 bpy
.ops
.mesh
.extrude_region_move(
1066 TRANSFORM_OT_translate
={"value": -self
.ViewVector
* objBBDiagonal
* subdivisions
* 2})
1068 bpy
.ops
.mesh
.select_all(action
='SELECT')
1069 bpy
.ops
.mesh
.normals_make_consistent()
1070 bpy
.ops
.object.mode_set(mode
='OBJECT')
1072 saved_location_0
= context
.scene
.cursor
.location
.copy()
1073 bpy
.ops
.view3d
.snap_cursor_to_active()
1074 saved_location
= context
.scene
.cursor
.location
.copy()
1075 bpy
.ops
.object.transform_apply(location
=True, rotation
=True, scale
=True)
1076 context
.scene
.cursor
.location
= saved_location
1077 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
1078 context
.scene
.cursor
.location
= saved_location_0
1080 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1081 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1083 for o
in self
.all_sel_obj_list
:
1084 bpy
.data
.objects
[o
.name
].select_set(True)
1087 bpy
.ops
.view3d
.localview()
1089 self
.CutMode
= False
1090 self
.mouse_path
.clear()
1091 self
.mouse_path
= [(0, 0), (0, 0)]
1094 context
= bpy
.context
1097 in_local_view
= False
1098 for area
in context
.screen
.areas
:
1099 if area
.type == 'VIEW_3D':
1100 if area
.spaces
[0].local_view
is not None:
1101 in_local_view
= True
1104 bpy
.ops
.view3d
.localview()
1106 # Save cursor position
1107 CursorLocation
= context
.scene
.cursor
.location
.copy()
1109 #List of selected objects
1110 selected_obj_list
= []
1113 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
1115 #Compute the bounding Box
1116 objBBDiagonal
= objDiagonal(self
.CurrentSelection
[0])
1117 if self
.dont_apply_boolean
:
1122 # Get selected objects
1123 selected_obj_list
= context
.selected_objects
.copy()
1125 bpy
.ops
.object.select_all(action
='TOGGLE')
1127 context
.view_layer
.objects
.active
= self
.CurrentObj
1129 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1130 bpy
.ops
.object.origin_set(type='ORIGIN_GEOMETRY')
1132 bpy
.ops
.object.mode_set(mode
='EDIT')
1133 bpy
.ops
.mesh
.select_all(action
='SELECT')
1134 bpy
.ops
.mesh
.select_mode(type="EDGE")
1135 #Translate the created mesh away from the view
1136 if (self
.snapCursor
is False) or (self
.ForceRebool
):
1137 bpy
.ops
.transform
.translate(value
=self
.ViewVector
* objBBDiagonal
* subdivisions
)
1138 #Extrude the mesh region and move the result
1139 bpy
.ops
.mesh
.extrude_region_move(
1140 TRANSFORM_OT_translate
={"value": -self
.ViewVector
* objBBDiagonal
* subdivisions
* 2})
1141 bpy
.ops
.mesh
.select_all(action
='SELECT')
1142 bpy
.ops
.mesh
.normals_make_consistent()
1143 bpy
.ops
.object.mode_set(mode
='OBJECT')
1147 for o
in self
.CurrentSelection
:
1148 if o
!= self
.ObjectBrush
:
1149 selected_obj_list
.append(o
)
1150 self
.CurrentObj
= self
.ObjectBrush
1152 selected_obj_list
= self
.CurrentSelection
1153 self
.CurrentObj
= self
.ProfileBrush
1155 for obj
in self
.CurrentSelection
:
1156 UndoAdd(self
, "MESH", obj
)
1158 # List objects create with rebool
1161 for ActiveObj
in selected_obj_list
:
1162 context
.scene
.cursor
.location
= CursorLocation
1164 if len(context
.selected_objects
) > 0:
1165 bpy
.ops
.object.select_all(action
='TOGGLE')
1168 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1169 context
.view_layer
.objects
.active
= self
.CurrentObj
1171 bpy
.ops
.object.mode_set(mode
='EDIT')
1172 bpy
.ops
.mesh
.select_all(action
='SELECT')
1173 bpy
.ops
.object.mode_set(mode
='OBJECT')
1175 # Select object to cut
1176 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1177 context
.view_layer
.objects
.active
= ActiveObj
1179 bpy
.ops
.object.mode_set(mode
='EDIT')
1180 bpy
.ops
.mesh
.select_all(action
='DESELECT')
1181 bpy
.ops
.object.mode_set(mode
='OBJECT')
1184 if (self
.shift
is False) and (self
.ForceRebool
is False):
1185 if self
.ObjectMode
or self
.ProfileMode
:
1186 if self
.BoolOps
== self
.union
:
1187 boolean_operation(bool_type
="UNION")
1189 boolean_operation(bool_type
="DIFFERENCE")
1191 boolean_operation(bool_type
="DIFFERENCE")
1194 if self
.dont_apply_boolean
is False:
1195 BMname
= "CT_" + self
.CurrentObj
.name
1196 for mb
in ActiveObj
.modifiers
:
1197 if (mb
.type == 'BOOLEAN') and (mb
.name
== BMname
):
1199 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
=BMname
)
1201 bpy
.ops
.object.modifier_remove(modifier
=BMname
)
1202 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1203 self
.report({'ERROR'}, str(exc_value
))
1205 bpy
.ops
.object.select_all(action
='TOGGLE')
1207 if self
.ObjectMode
or self
.ProfileMode
:
1208 for mb
in self
.CurrentObj
.modifiers
:
1209 if (mb
.type == 'SOLIDIFY') and (mb
.name
== "CT_SOLIDIFY"):
1211 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
="CT_SOLIDIFY")
1213 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1214 self
.report({'ERROR'}, str(exc_value
))
1217 Rebool(context
, self
)
1219 # Test if not empty object
1220 if context
.selected_objects
[0]:
1221 rebool_RT
= context
.selected_objects
[0]
1222 if len(rebool_RT
.data
.vertices
) > 0:
1223 # Create Bevel for new objects
1224 CreateBevel(context
, context
.selected_objects
[0])
1226 UndoAdd(self
, "REBOOL", context
.selected_objects
[0])
1228 context
.scene
.cursor
.location
= ActiveObj
.location
1229 bpy
.ops
.object.origin_set(type='ORIGIN_CURSOR')
1231 bpy
.ops
.object.delete(use_global
=False)
1233 context
.scene
.cursor
.location
= CursorLocation
1236 context
.view_layer
.objects
.active
= self
.ObjectBrush
1237 if self
.ProfileMode
:
1238 context
.view_layer
.objects
.active
= self
.ProfileBrush
1240 if self
.dont_apply_boolean
is False:
1242 BMname
= "CT_" + self
.CurrentObj
.name
1243 for mb
in ActiveObj
.modifiers
:
1244 if (mb
.type == 'BOOLEAN') and (mb
.name
== BMname
):
1246 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
=BMname
)
1248 bpy
.ops
.object.modifier_remove(modifier
=BMname
)
1249 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
1250 self
.report({'ERROR'}, str(exc_value
))
1251 # Get new objects created with rebool operations
1252 if len(context
.selected_objects
) > 0:
1253 if self
.shift
is True:
1254 # Get the last object selected
1255 lastSelected
.append(context
.selected_objects
[0])
1257 context
.scene
.cursor
.location
= CursorLocation
1259 if self
.dont_apply_boolean
is False:
1261 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
1262 if len(context
.selected_objects
) > 0:
1263 bpy
.ops
.object.select_all(action
='TOGGLE')
1264 bpy
.data
.objects
[self
.CurrentObj
.name
].select_set(True)
1265 bpy
.ops
.object.delete(use_global
=False)
1268 self
.ObjectBrush
.display_type
= self
.InitBrush
['display_type']
1270 if len(context
.selected_objects
) > 0:
1271 bpy
.ops
.object.select_all(action
='TOGGLE')
1273 # Select cut objects
1274 for obj
in lastSelected
:
1275 bpy
.data
.objects
[obj
.name
].select_set(True)
1277 for ActiveObj
in selected_obj_list
:
1278 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1279 context
.view_layer
.objects
.active
= ActiveObj
1281 list_act_obj
= context
.selected_objects
.copy()
1282 if self
.Auto_BevelUpdate
:
1283 update_bevel(context
)
1285 # Re-select initial objects
1286 bpy
.ops
.object.select_all(action
='TOGGLE')
1289 self
.ObjectBrush
.select_set(True)
1290 for ActiveObj
in selected_obj_list
:
1291 bpy
.data
.objects
[ActiveObj
.name
].select_set(True)
1292 context
.view_layer
.objects
.active
= ActiveObj
1294 # If object has children, set "Wire" draw type
1295 if self
.ObjectBrush
is not None:
1296 if len(self
.ObjectBrush
.children
) > 0:
1297 self
.ObjectBrush
.display_type
= "WIRE"
1298 if self
.ProfileMode
:
1299 self
.ProfileBrush
.display_type
= "WIRE"
1302 bpy
.ops
.view3d
.localview()
1305 self
.CutMode
= False
1306 self
.mouse_path
.clear()
1307 self
.mouse_path
= [(0, 0), (0, 0)]
1309 self
.ForceRebool
= False
1311 # bpy.ops.mesh.customdata_custom_splitnormals_clear()
1314 class CarverProperties(bpy
.types
.PropertyGroup
):
1315 DepthCursor
: BoolProperty(
1319 OInstanciate
: BoolProperty(
1320 name
="Obj_Instantiate",
1323 ORandom
: BoolProperty(
1324 name
="Random_Rotation",
1327 DontApply
: BoolProperty(
1331 nProfile
: IntProperty(
1338 from bpy
.utils
import register_class
1339 bpy
.utils
.register_class(CARVER_OT_operator
)
1340 bpy
.utils
.register_class(CarverProperties
)
1341 bpy
.types
.Scene
.mesh_carver
= bpy
.props
.PointerProperty(type=CarverProperties
)
1344 from bpy
.utils
import unregister_class
1345 bpy
.utils
.unregister_class(CarverProperties
)
1346 bpy
.utils
.unregister_class(CARVER_OT_operator
)
1347 del bpy
.types
.Scene
.mesh_carver