1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 from gpu_extras
.batch
import batch_for_shader
12 from mathutils
import (
18 from mathutils
.geometry
import (
30 from bpy_extras
import view3d_utils
31 from bpy_extras
.view3d_utils
import (
32 region_2d_to_vector_3d
,
33 region_2d_to_location_3d
,
34 location_3d_to_region_2d
,
38 def CreateCutSquare(self
, context
):
39 """ Create a rectangle mesh """
43 # Get the mouse coordinates
44 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
47 me
= bpy
.data
.meshes
.new('CMT_Square')
51 # New object and link it to the scene
52 ob
= bpy
.data
.objects
.new('CMT_Square', me
)
54 context
.collection
.objects
.link(ob
)
57 region
= context
.region
58 rv3d
= context
.region_data
59 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
60 self
.ViewVector
= depth_location
62 # Get a point on a infinite plane and its direction
63 plane_normal
= depth_location
64 plane_direction
= plane_normal
.normalized()
67 plane_point
= context
.scene
.cursor
.location
69 plane_point
= self
.OpsObj
.location
if self
.OpsObj
is not None else Vector((0.0, 0.0, 0.0))
71 # Find the intersection of a line going thru each vertex and the infinite plane
72 for v_co
in self
.rectangle_coord
:
73 vec
= region_2d_to_vector_3d(region
, rv3d
, v_co
)
74 p0
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
)
75 p1
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
) + plane_direction
* far_limit
76 faces
.append(bm
.verts
.new(intersect_line_plane(p0
, p1
, plane_point
, plane_direction
)))
78 # Update vertices index
79 bm
.verts
.index_update()
81 t_face
= bm
.faces
.new(faces
)
87 def CreateCutLine(self
, context
):
88 """ Create a polygon mesh """
94 # Get the mouse coordinates
95 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
98 me
= bpy
.data
.meshes
.new('CMT_Line')
102 # New object and link it to the scene
103 ob
= bpy
.data
.objects
.new('CMT_Line', me
)
105 context
.collection
.objects
.link(ob
)
108 region
= context
.region
109 rv3d
= context
.region_data
110 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
111 self
.ViewVector
= depth_location
113 # Get a point on a infinite plane and its direction
114 plane_normal
= depth_location
115 plane_direction
= plane_normal
.normalized()
118 plane_point
= context
.scene
.cursor
.location
120 plane_point
= self
.OpsObj
.location
if self
.OpsObj
is not None else Vector((0.0, 0.0, 0.0))
122 # Use dict to remove doubles
123 # Find the intersection of a line going thru each vertex and the infinite plane
124 for idx
, v_co
in enumerate(list(dict.fromkeys(self
.mouse_path
))):
125 vec
= region_2d_to_vector_3d(region
, rv3d
, v_co
)
126 p0
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
)
127 p1
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
) + plane_direction
* far_limit
128 loc
.append(intersect_line_plane(p0
, p1
, plane_point
, plane_direction
))
129 vertices
.append(bm
.verts
.new(loc
[idx
]))
132 bm
.edges
.new([vertices
[idx
-1],vertices
[idx
]])
134 faces
.append(vertices
[idx
])
136 # Update vertices index
137 bm
.verts
.index_update()
139 # Nothing is selected, create close geometry
141 if self
.Closed
and len(vertices
) > 1:
142 bm
.edges
.new([vertices
[-1], vertices
[0]])
145 # Create faces if more than 2 vertices
146 if len(vertices
) > 1 :
147 bm
.edges
.new([vertices
[-1], vertices
[0]])
153 def CreateCutCircle(self
, context
):
154 """ Create a circle mesh """
158 # Get the mouse coordinates
159 mouse_pos_x
= self
.mouse_path
[0][0]
160 mouse_pos_y
= self
.mouse_path
[0][1]
161 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
164 region
= context
.region
165 rv3d
= context
.region_data
166 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
167 self
.ViewVector
= depth_location
169 # Get a point on a infinite plane and its direction
170 plane_point
= context
.scene
.cursor
.location
if self
.snapCursor
else Vector((0.0, 0.0, 0.0))
171 plane_normal
= depth_location
172 plane_direction
= plane_normal
.normalized()
175 me
= bpy
.data
.meshes
.new('CMT_Circle')
179 # New object and link it to the scene
180 ob
= bpy
.data
.objects
.new('CMT_Circle', me
)
182 context
.collection
.objects
.link(ob
)
184 # Create a circle using a tri fan
185 tris_fan
, indices
= draw_circle(self
, mouse_pos_x
, mouse_pos_y
)
187 # Remove the vertex in the center to get the outer line of the circle
190 # Find the intersection of a line going thru each vertex and the infinite plane
192 vec
= region_2d_to_vector_3d(region
, rv3d
, vert
)
193 p0
= region_2d_to_location_3d(region
, rv3d
, vert
, vec
)
194 p1
= p0
+ plane_direction
* far_limit
195 loc0
= intersect_line_plane(p0
, p1
, plane_point
, plane_direction
)
196 t_v0
= bm
.verts
.new(loc0
)
197 FacesList
.append(t_v0
)
199 bm
.verts
.index_update()
200 bm
.faces
.new(FacesList
)
204 def create_2d_circle(self
, step
, radius
, rotation
= 0):
205 """ Create the vertices of a 2d circle at (0,0) """
207 for angle
in range(0, 360, step
):
208 verts
.append(math
.cos(math
.radians(angle
+ rotation
)) * radius
)
209 verts
.append(math
.sin(math
.radians(angle
+ rotation
)) * radius
)
211 verts
.append(math
.cos(math
.radians(0.0 + rotation
)) * radius
)
212 verts
.append(math
.sin(math
.radians(0.0 + rotation
)) * radius
)
217 def draw_circle(self
, mouse_pos_x
, mouse_pos_y
):
218 """ Return the coordinates + indices of a circle using a triangle fan """
221 segments
= int(360 / self
.stepAngle
[self
.step
])
222 radius
= self
.mouse_path
[1][0] - self
.mouse_path
[0][0]
223 rotation
= (self
.mouse_path
[1][1] - self
.mouse_path
[0][1]) / 2
225 # Get the vertices of a 2d circle
226 verts
= create_2d_circle(self
, self
.stepAngle
[self
.step
], radius
, rotation
)
228 # Create the first vertex at mouse position for the center of the circle
229 tris_verts
.append(Vector((mouse_pos_x
+ self
.xpos
, mouse_pos_y
+ self
.ypos
)))
231 # For each vertex of the circle, add the mouse position and the translation
232 for idx
in range(int(len(verts
) / 3) - 1):
233 tris_verts
.append(Vector((verts
[idx
* 3] + mouse_pos_x
+ self
.xpos
, \
234 verts
[idx
* 3 + 1] + mouse_pos_y
+ self
.ypos
)))
236 i2
= idx
+2 if idx
+2 <= segments
else 1
237 indices
.append((0,i1
,i2
))
239 return(tris_verts
, indices
)
241 # Object dimensions (SCULPT Tools tips)
242 def objDiagonal(obj
):
243 return ((obj
.dimensions
[0]**2) + (obj
.dimensions
[1]**2) + (obj
.dimensions
[2]**2))**0.5
247 def update_bevel(context
):
248 selection
= context
.selected_objects
.copy()
249 active
= context
.active_object
251 if len(selection
) > 0:
252 for obj
in selection
:
253 bpy
.ops
.object.select_all(action
='DESELECT')
255 context
.view_layer
.objects
.active
= obj
258 # Subdive mode : Only bevel weight
259 if obj
.data
.name
.startswith("S_") or obj
.data
.name
.startswith("S "):
260 bpy
.ops
.object.mode_set(mode
='EDIT')
261 bpy
.ops
.mesh
.region_to_loop()
262 bpy
.ops
.transform
.edge_bevelweight(value
=1)
263 bpy
.ops
.object.mode_set(mode
='OBJECT')
266 # No subdiv mode : bevel weight + Crease + Sharp
267 CreateBevel(context
, obj
)
269 bpy
.ops
.object.select_all(action
='DESELECT')
271 for obj
in selection
:
273 context
.view_layer
.objects
.active
= active
276 def CreateBevel(context
, CurrentObject
):
278 SavActive
= context
.active_object
280 # Test if initial object has bevel
281 bevel_modifier
= False
282 for modifier
in SavActive
.modifiers
:
283 if modifier
.name
== 'Bevel':
284 bevel_modifier
= True
287 # Active "CurrentObject"
288 context
.view_layer
.objects
.active
= CurrentObject
290 bpy
.ops
.object.mode_set(mode
='EDIT')
293 bpy
.ops
.mesh
.select_mode(use_extend
=False, use_expand
=False, type='EDGE')
295 bpy
.ops
.mesh
.select_all(action
='SELECT')
296 bpy
.ops
.mesh
.mark_sharp(clear
=True)
297 bpy
.ops
.transform
.edge_crease(value
=-1)
298 bpy
.ops
.transform
.edge_bevelweight(value
=-1)
300 bpy
.ops
.mesh
.select_all(action
='DESELECT')
302 # Select (in radians) all 30° sharp edges
303 bpy
.ops
.mesh
.edges_select_sharp(sharpness
=0.523599)
304 # Apply bevel weight + Crease + Sharp to the selected edges
305 bpy
.ops
.mesh
.mark_sharp()
306 bpy
.ops
.transform
.edge_crease(value
=1)
307 bpy
.ops
.transform
.edge_bevelweight(value
=1)
309 bpy
.ops
.mesh
.select_all(action
='DESELECT')
311 bpy
.ops
.object.mode_set(mode
='OBJECT')
313 bevel_weights
= CurrentObject
.data
.attributes
["bevel_weight_edge"]
314 if not bevel_weights
:
315 bevel_weights
= CurrentObject
.data
.attributes
.new("bevel_weight_edge", 'FLOAT', 'EDGE')
316 if bevel_weights
.data_type
!= 'FLOAT' or bevel_weights
.domain
!= 'EDGE':
319 for i
in range(len(CurrentObject
.data
.edges
)):
320 if CurrentObject
.data
.edges
[i
].select
is True:
322 bevel_weights
.data
[i
] = 1.0
323 CurrentObject
.data
.edges
[i
].use_edge_sharp
= True
325 bevel_modifier
= False
326 for m
in CurrentObject
.modifiers
:
327 if m
.name
== 'Bevel':
328 bevel_modifier
= True
330 if bevel_modifier
is False:
331 mod
= context
.object.modifiers
.new("", 'BEVEL')
332 mod
.limit_method
= 'WEIGHT'
334 mod
.profile
= 0.699099
335 mod
.use_clamp_overlap
= False
337 mod
.loop_slide
= False
339 bpy
.ops
.object.shade_smooth()
341 context
.object.data
.set_sharp_from_angle(angle
=1.0471975)
343 # Restore the active object
344 context
.view_layer
.objects
.active
= SavActive
347 def MoveCursor(qRot
, location
, self
):
348 """ In brush mode : Draw a circle around the brush """
350 verts
= create_2d_circle(self
, 10, 1)
353 for idx
in range(int(len(verts
) / 3)):
354 vc
.x
= verts
[idx
* 3]
355 vc
.y
= verts
[idx
* 3 + 1]
356 vc
.z
= verts
[idx
* 3 + 2]
358 self
.CLR_C
.append(vc
.x
)
359 self
.CLR_C
.append(vc
.y
)
360 self
.CLR_C
.append(vc
.z
)
363 def rot_axis_quat(vector1
, vector2
):
364 """ Find the rotation (quaternion) from vector 1 to vector 2"""
365 vector1
= vector1
.normalized()
366 vector2
= vector2
.normalized()
367 cosTheta
= vector1
.dot(vector2
)
368 rotationAxis
= Vector((0.0, 0.0, 0.0))
369 if (cosTheta
< -1 + 0.001):
370 v
= Vector((0.0, 1.0, 0.0))
371 #Get the vector at the right angles to both
372 rotationAxis
= vector1
.cross(v
)
373 rotationAxis
= rotationAxis
.normalized()
380 rotationAxis
= vector1
.cross(vector2
)
381 s
= math
.sqrt((1.0 + cosTheta
) * 2.0)
385 q
.x
= rotationAxis
.x
* invs
386 q
.y
= rotationAxis
.y
* invs
387 q
.z
= rotationAxis
.z
* invs
392 def Picking(context
, event
):
393 """ Put the 3d cursor on the closest object"""
395 # get the context arguments
396 scene
= context
.scene
397 region
= context
.region
398 rv3d
= context
.region_data
399 coord
= event
.mouse_region_x
, event
.mouse_region_y
401 # get the ray from the viewport and mouse
402 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
403 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
404 ray_target
= ray_origin
+ view_vector
406 def visible_objects_and_duplis():
407 depsgraph
= context
.evaluated_depsgraph_get()
408 for dup
in depsgraph
.object_instances
:
409 if dup
.is_instance
: # Real dupli instance
410 obj
= dup
.instance_object
.original
411 yield (obj
, dup
.matrix
.copy())
413 obj
= dup
.object.original
414 yield (obj
, obj
.matrix_world
.copy())
416 def obj_ray_cast(obj
, matrix
):
417 # get the ray relative to the object
418 matrix_inv
= matrix
.inverted()
419 ray_origin_obj
= matrix_inv
@ ray_origin
420 ray_target_obj
= matrix_inv
@ ray_target
421 ray_direction_obj
= ray_target_obj
- ray_origin_obj
423 success
, location
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_direction_obj
)
425 return location
, normal
, face_index
426 return None, None, None
428 # cast rays and find the closest object
429 best_length_squared
= -1.0
432 # cast rays and find the closest object
433 for obj
, matrix
in visible_objects_and_duplis():
434 if obj
.type == 'MESH':
435 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
437 hit_world
= matrix
@ hit
438 length_squared
= (hit_world
- ray_origin
).length_squared
439 if best_obj
is None or length_squared
< best_length_squared
:
440 scene
.cursor
.location
= hit_world
441 best_length_squared
= length_squared
445 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
446 loc
= region_2d_to_location_3d(region
, rv3d
, coord
, depth_location
)
447 scene
.cursor
.location
= loc
450 def Pick(context
, event
, self
, ray_max
=10000.0):
451 region
= context
.region
452 rv3d
= context
.region_data
453 coord
= event
.mouse_region_x
, event
.mouse_region_y
454 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
455 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
456 ray_target
= ray_origin
+ (view_vector
* ray_max
)
458 def obj_ray_cast(obj
, matrix
):
459 matrix_inv
= matrix
.inverted()
460 ray_origin_obj
= matrix_inv
@ ray_origin
461 ray_target_obj
= matrix_inv
@ ray_target
462 success
, hit
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_target_obj
)
464 return hit
, normal
, face_index
465 return None, None, None
467 best_length_squared
= ray_max
* ray_max
469 for obj
in self
.CList
:
470 matrix
= obj
.matrix_world
471 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
472 rotation
= obj
.rotation_euler
.to_quaternion()
474 hit_world
= matrix
@ hit
475 length_squared
= (hit_world
- ray_origin
).length_squared
476 if length_squared
< best_length_squared
:
477 best_length_squared
= length_squared
483 if best_obj
is not None:
484 return hits
, ns
, rotation
486 return None, None, None
488 def SelectObject(self
, copyobj
):
489 copyobj
.select_set(True)
491 for child
in copyobj
.children
:
492 SelectObject(self
, child
)
494 if copyobj
.parent
is None:
495 bpy
.context
.view_layer
.objects
.active
= copyobj
503 def UndoAdd(self
, type, obj
):
504 """ Create a backup mesh before apply the action to the object """
508 if type != "DUPLICATE":
510 bm
.from_mesh(obj
.data
)
511 self
.UndoOps
.append((obj
, type, bm
))
513 self
.UndoOps
.append((obj
, type, None))
516 def UndoListUpdate(self
):
517 self
.UList
.append((self
.UndoOps
.copy()))
518 self
.UList_Index
+= 1
523 if self
.UList_Index
< 0:
526 for o
in self
.UList
[self
.UList_Index
]:
529 bm
.to_mesh(o
[0].data
)
531 SelectObjList
= bpy
.context
.selected_objects
.copy()
532 Active_Obj
= bpy
.context
.active_object
533 bpy
.ops
.object.select_all(action
='TOGGLE')
535 for o
in self
.UList
[self
.UList_Index
]:
537 o
[0].select_set(True)
538 o
[0].hide_viewport
= False
540 if o
[1] == "DUPLICATE":
541 o
[0].select_set(True)
542 o
[0].hide_viewport
= False
544 bpy
.ops
.object.delete(use_global
=False)
546 for so
in SelectObjList
:
547 bpy
.data
.objects
[so
.name
].select_set(True)
548 bpy
.context
.view_layer
.objects
.active
= Active_Obj
550 self
.UList_Index
-= 1
551 self
.UList
[self
.UList_Index
+ 1:] = []
554 def duplicateObject(self
):
556 bpy
.ops
.object.duplicate_move_linked(
557 OBJECT_OT_duplicate
={
559 "mode": 'TRANSLATION',
561 TRANSFORM_OT_translate
={
566 bpy
.ops
.object.duplicate_move(
567 OBJECT_OT_duplicate
={
569 "mode": 'TRANSLATION',
571 TRANSFORM_OT_translate
={
576 ob_new
= bpy
.context
.active_object
578 ob_new
.location
= self
.CurLoc
581 v
.z
= self
.BrushDepthOffset
582 ob_new
.location
+= self
.qRot
* v
585 ob_new
.scale
= self
.ObjectBrush
.scale
587 ob_new
.scale
= self
.ProfileBrush
.scale
591 e
.z
= self
.aRotZ
/ 25.0
593 # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
594 if (self
.alt
is True) and ((self
.nbcol
+ self
.nbrow
) < 3):
595 if self
.RandomRotation
:
596 e
.z
+= random
.random()
598 qe
= e
.to_quaternion()
599 qRot
= self
.qRot
* qe
600 ob_new
.rotation_mode
= 'QUATERNION'
601 ob_new
.rotation_quaternion
= qRot
602 ob_new
.rotation_mode
= 'XYZ'
604 if (ob_new
.display_type
== "WIRE") and (self
.BrushSolidify
is False):
605 ob_new
.hide_viewport
= True
607 if self
.BrushSolidify
:
608 ob_new
.display_type
= "SOLID"
609 ob_new
.show_in_front
= False
611 for o
in bpy
.context
.selected_objects
:
612 UndoAdd(self
, "DUPLICATE", o
)
614 if len(bpy
.context
.selected_objects
) > 0:
615 bpy
.ops
.object.select_all(action
='TOGGLE')
616 for o
in self
.all_sel_obj_list
:
619 bpy
.context
.view_layer
.objects
.active
= self
.OpsObj
622 def update_grid(self
, context
):
624 Thanks to batFINGER for his help :
625 source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
641 # Get the data from the profils or the object
643 brush
= bpy
.data
.objects
.new(
644 self
.Profils
[self
.nProfil
][0],
645 bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
647 obj
= bpy
.data
.objects
["CT_Profil"]
648 obfaces
= brush
.data
.polygons
649 obverts
= brush
.data
.vertices
650 lenverts
= len(obverts
)
652 brush
= bpy
.data
.objects
["CarverBrushCopy"]
653 obj
= context
.selected_objects
[0]
654 obverts
= brush
.data
.vertices
655 obfaces
= brush
.data
.polygons
656 lenverts
= len(brush
.data
.vertices
)
658 # Gap between each row / column
662 # Width of each row / column
663 widthx
= brush
.dimensions
.x
* self
.scale_x
664 widthy
= brush
.dimensions
.y
* self
.scale_y
666 # Compute the corners so the new object will be always at the center
667 left
= -((self
.nbcol
- 1) * (widthx
+ gapx
)) / 2
668 start
= -((self
.nbrow
- 1) * (widthy
+ gapy
)) / 2
670 for i
in range(self
.nbrow
* self
.nbcol
):
672 col
= i
// self
.nbrow
673 startx
= left
+ ((widthx
+ gapx
) * col
)
674 starty
= start
+ ((widthy
+ gapy
) * row
)
676 # Add random rotation
677 if (self
.RandomRotation
) and not (self
.GridScaleX
or self
.GridScaleY
):
678 rotmat
= Matrix
.Rotation(math
.radians(360 * random
.random()), 4, 'Z')
682 verts
.extend([((v
.co
.x
- startx
, v
.co
.y
- starty
, v
.co
.z
)) for v
in obverts
])
683 faces
.extend([[v
+ numface
* lenverts
for v
in p
.vertices
] for p
in obfaces
])
688 mymesh
= bpy
.data
.meshes
.new("CT_Profil")
690 mymesh
.from_pydata(verts
, edges
, faces
)
691 # Calculate the edges
692 mymesh
.update(calc_edges
=True)
695 # Make the object active to remove doubles
696 context
.view_layer
.objects
.active
= obj
699 def boolean_operation(bool_type
="DIFFERENCE"):
700 ActiveObj
= bpy
.context
.active_object
701 sel_index
= 0 if bpy
.context
.selected_objects
[0] != bpy
.context
.active_object
else 1
703 # bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
704 bool_name
= "CT_" + bpy
.context
.selected_objects
[sel_index
].name
705 BoolMod
= ActiveObj
.modifiers
.new(bool_name
, "BOOLEAN")
706 BoolMod
.object = bpy
.context
.selected_objects
[sel_index
]
707 BoolMod
.operation
= bool_type
708 bpy
.context
.selected_objects
[sel_index
].display_type
= 'WIRE'
709 while ActiveObj
.modifiers
.find(bool_name
) > 0:
710 bpy
.ops
.object.modifier_move_up(modifier
=bool_name
)
713 def Rebool(context
, self
):
715 target_obj
= context
.active_object
717 Brush
= context
.selected_objects
[1]
718 Brush
.display_type
= "WIRE"
721 bpy
.ops
.object.select_all(action
='TOGGLE')
723 target_obj
.display_type
= "SOLID"
724 target_obj
.select_set(True)
725 bpy
.ops
.object.duplicate()
727 rebool_obj
= context
.active_object
729 m
= rebool_obj
.modifiers
.new("CT_INTERSECT", "BOOLEAN")
730 m
.operation
= "INTERSECT"
733 m
= target_obj
.modifiers
.new("CT_DIFFERENCE", "BOOLEAN")
734 m
.operation
= "DIFFERENCE"
737 for mb
in target_obj
.modifiers
:
738 if mb
.type == 'BEVEL':
739 mb
.show_viewport
= False
741 if self
.ObjectBrush
or self
.ProfileBrush
:
742 rebool_obj
.show_in_front
= False
744 bpy
.ops
.object.modifier_apply(modifier
="CT_SOLIDIFY")
746 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
747 self
.report({'ERROR'}, str(exc_value
))
749 if self
.dont_apply_boolean
is False:
751 bpy
.ops
.object.modifier_apply(modifier
="CT_INTERSECT")
753 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
754 self
.report({'ERROR'}, str(exc_value
))
756 bpy
.ops
.object.select_all(action
='TOGGLE')
758 for mb
in target_obj
.modifiers
:
759 if mb
.type == 'BEVEL':
760 mb
.show_viewport
= True
762 context
.view_layer
.objects
.active
= target_obj
763 target_obj
.select_set(True)
764 if self
.dont_apply_boolean
is False:
766 bpy
.ops
.object.modifier_apply(modifier
="CT_DIFFERENCE")
768 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
769 self
.report({'ERROR'}, str(exc_value
))
771 bpy
.ops
.object.select_all(action
='TOGGLE')
773 rebool_obj
.select_set(True)
775 def createMeshFromData(self
):
776 if self
.Profils
[self
.nProfil
][0] not in bpy
.data
.meshes
:
777 # Create mesh and object
778 me
= bpy
.data
.meshes
.new(self
.Profils
[self
.nProfil
][0])
779 # Create mesh from given verts, faces.
780 me
.from_pydata(self
.Profils
[self
.nProfil
][2], [], self
.Profils
[self
.nProfil
][3])
781 me
.validate(verbose
=True, clean_customdata
=True)
782 # Update mesh with new data
785 if "CT_Profil" not in bpy
.data
.objects
:
786 ob
= bpy
.data
.objects
.new("CT_Profil", bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]])
787 ob
.location
= Vector((0.0, 0.0, 0.0))
789 # Link object to scene and make active
790 bpy
.context
.collection
.objects
.link(ob
)
791 bpy
.context
.view_layer
.update()
792 bpy
.context
.view_layer
.objects
.active
= ob
794 ob
.location
= Vector((10000.0, 0.0, 0.0))
795 ob
.display_type
= "WIRE"
797 self
.SolidifyPossible
= True
799 bpy
.data
.objects
["CT_Profil"].data
= bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
801 def Selection_Save_Restore(self
):
802 if "CT_Profil" in bpy
.data
.objects
:
804 bpy
.ops
.object.select_all(action
='DESELECT')
805 bpy
.data
.objects
["CT_Profil"].select_set(True)
806 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
["CT_Profil"]
807 if bpy
.data
.objects
["CT_Profil"] in self
.all_sel_obj_list
:
808 self
.all_sel_obj_list
.remove(bpy
.data
.objects
["CT_Profil"])
809 bpy
.ops
.object.delete(use_global
=False)
810 Selection_Restore(self
)
812 def Selection_Save(self
):
813 obj_name
= getattr(bpy
.context
.active_object
, "name", None)
814 self
.all_sel_obj_list
= bpy
.context
.selected_objects
.copy()
815 self
.save_active_obj
= obj_name
818 def Selection_Restore(self
):
819 for o
in self
.all_sel_obj_list
:
821 if self
.save_active_obj
:
822 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
.get(self
.save_active_obj
, None)
824 def Snap_Cursor(self
, context
, event
, mouse_pos
):
825 """ Find the closest position on the overlay grid and snap the mouse on it """
826 # Get the context arguments
827 region
= context
.region
828 rv3d
= context
.region_data
830 # Get the VIEW3D area
831 for i
, a
in enumerate(context
.screen
.areas
):
832 if a
.type == 'VIEW_3D':
833 space
= context
.screen
.areas
[i
].spaces
.active
835 # Get the grid overlay for the VIEW_3D
836 grid_scale
= space
.overlay
.grid_scale
837 grid_subdivisions
= space
.overlay
.grid_subdivisions
839 # Use the grid scale and subdivision to get the increment
840 increment
= (grid_scale
/ grid_subdivisions
)
841 half_increment
= increment
/ 2
843 # Convert the 2d location of the mouse in 3d
844 for index
, loc
in enumerate(reversed(mouse_pos
)):
845 mouse_loc_3d
= region_2d_to_location_3d(region
, rv3d
, loc
, (0, 0, 0))
847 # Get the remainder from the mouse location and the ratio
848 # Test if the remainder > to the half of the increment
850 modulo
= mouse_loc_3d
[i
] % increment
851 if modulo
< half_increment
:
854 modulo
= increment
- modulo
856 # Add the remainder to get the closest location on the grid
857 mouse_loc_3d
[i
] = mouse_loc_3d
[i
] + modulo
859 # Get the snapped 2d location
860 snap_loc_2d
= location_3d_to_region_2d(region
, rv3d
, mouse_loc_3d
)
862 # Replace the last mouse location by the snapped location
863 if len(self
.mouse_path
) > 0:
864 self
.mouse_path
[len(self
.mouse_path
) - (index
+ 1) ] = tuple(snap_loc_2d
)
866 def mini_grid(self
, context
, color
):
867 """ Draw a snap mini grid around the cursor based on the overlay grid"""
868 # Get the context arguments
869 region
= context
.region
870 rv3d
= context
.region_data
872 # Get the VIEW3D area
873 for i
, a
in enumerate(context
.screen
.areas
):
874 if a
.type == 'VIEW_3D':
875 space
= context
.screen
.areas
[i
].spaces
.active
876 screen_height
= context
.screen
.areas
[i
].height
877 screen_width
= context
.screen
.areas
[i
].width
879 #Draw the snap grid, only in ortho view
880 if not space
.region_3d
.is_perspective
:
881 grid_scale
= space
.overlay
.grid_scale
882 grid_subdivisions
= space
.overlay
.grid_subdivisions
883 increment
= (grid_scale
/ grid_subdivisions
)
885 # Get the 3d location of the mouse forced to a snap value in the operator
886 mouse_coord
= self
.mouse_path
[len(self
.mouse_path
) - 1]
888 snap_loc
= region_2d_to_location_3d(region
, rv3d
, mouse_coord
, (0, 0, 0))
890 # Add the increment to get the closest location on the grid
891 snap_loc
[0] += increment
892 snap_loc
[1] += increment
894 # Get the 2d location of the snap location
895 snap_loc
= location_3d_to_region_2d(region
, rv3d
, snap_loc
)
896 origin
= location_3d_to_region_2d(region
, rv3d
, (0,0,0))
898 # Get the increment value
899 snap_value
= snap_loc
[0] - mouse_coord
[0]
903 # Draw lines on X and Z axis from the cursor through the screen
905 (0, mouse_coord
[1]), (screen_width
, mouse_coord
[1]),
906 (mouse_coord
[0], 0), (mouse_coord
[0], screen_height
)
909 # Draw a mlini grid around the cursor to show the snap options
911 (mouse_coord
[0] + snap_value
, mouse_coord
[1] + 25 + snap_value
),
912 (mouse_coord
[0] + snap_value
, mouse_coord
[1] - 25 - snap_value
),
913 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] + snap_value
),
914 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] + snap_value
),
915 (mouse_coord
[0] - snap_value
, mouse_coord
[1] + 25 + snap_value
),
916 (mouse_coord
[0] - snap_value
, mouse_coord
[1] - 25 - snap_value
),
917 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] - snap_value
),
918 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] - snap_value
),
920 draw_shader(self
, color
, 0.3, 'LINES', grid_coords
, size
=2)
923 def draw_shader(self
, color
, alpha
, type, coords
, size
=1, indices
=None):
924 """ Create a batch for a draw type """
925 gpu
.state
.blend_set('ALPHA')
927 gpu
.state
.program_point_size_set(False)
928 gpu
.state
.point_size_set(size
)
929 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR')
931 shader
= gpu
.shader
.from_builtin('POLYLINE_UNIFORM_COLOR')
932 shader
.uniform_float("viewportSize", gpu
.state
.viewport_get()[2:])
933 shader
.uniform_float("lineWidth", 1.0)
936 shader
.uniform_float("color", (color
[0], color
[1], color
[2], alpha
))
937 batch
= batch_for_shader(shader
, type, {"pos": coords
}, indices
=indices
)
940 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
941 self
.report({'ERROR'}, str(exc_value
))
943 gpu
.state
.point_size_set(1.0)
944 gpu
.state
.blend_set('NONE')