5 from gpu_extras
.batch
import batch_for_shader
10 from mathutils
import (
16 from mathutils
.geometry
import (
28 from bpy_extras
import view3d_utils
29 from bpy_extras
.view3d_utils
import (
30 region_2d_to_vector_3d
,
31 region_2d_to_location_3d
,
32 location_3d_to_region_2d
,
36 def CreateCutSquare(self
, context
):
37 """ Create a rectangle mesh """
41 # Get the mouse coordinates
42 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
45 me
= bpy
.data
.meshes
.new('CMT_Square')
49 # New object and link it to the scene
50 ob
= bpy
.data
.objects
.new('CMT_Square', me
)
52 context
.collection
.objects
.link(ob
)
55 region
= context
.region
56 rv3d
= context
.region_data
57 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
58 self
.ViewVector
= depth_location
60 # Get a point on a infinite plane and its direction
61 plane_normal
= depth_location
62 plane_direction
= plane_normal
.normalized()
65 plane_point
= context
.scene
.cursor
.location
67 plane_point
= self
.OpsObj
.location
if self
.OpsObj
is not None else Vector((0.0, 0.0, 0.0))
69 # Find the intersection of a line going thru each vertex and the infinite plane
70 for v_co
in self
.rectangle_coord
:
71 vec
= region_2d_to_vector_3d(region
, rv3d
, v_co
)
72 p0
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
)
73 p1
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
) + plane_direction
* far_limit
74 faces
.append(bm
.verts
.new(intersect_line_plane(p0
, p1
, plane_point
, plane_direction
)))
76 # Update vertices index
77 bm
.verts
.index_update()
79 t_face
= bm
.faces
.new(faces
)
85 def CreateCutLine(self
, context
):
86 """ Create a polygon mesh """
92 # Get the mouse coordinates
93 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
96 me
= bpy
.data
.meshes
.new('CMT_Line')
100 # New object and link it to the scene
101 ob
= bpy
.data
.objects
.new('CMT_Line', me
)
103 context
.collection
.objects
.link(ob
)
106 region
= context
.region
107 rv3d
= context
.region_data
108 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
109 self
.ViewVector
= depth_location
111 # Get a point on a infinite plane and its direction
112 plane_normal
= depth_location
113 plane_direction
= plane_normal
.normalized()
116 plane_point
= context
.scene
.cursor
.location
118 plane_point
= self
.OpsObj
.location
if self
.OpsObj
is not None else Vector((0.0, 0.0, 0.0))
120 # Use dict to remove doubles
121 # Find the intersection of a line going thru each vertex and the infinite plane
122 for idx
, v_co
in enumerate(list(dict.fromkeys(self
.mouse_path
))):
123 vec
= region_2d_to_vector_3d(region
, rv3d
, v_co
)
124 p0
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
)
125 p1
= region_2d_to_location_3d(region
, rv3d
,v_co
, vec
) + plane_direction
* far_limit
126 loc
.append(intersect_line_plane(p0
, p1
, plane_point
, plane_direction
))
127 vertices
.append(bm
.verts
.new(loc
[idx
]))
130 bm
.edges
.new([vertices
[idx
-1],vertices
[idx
]])
132 faces
.append(vertices
[idx
])
134 # Update vertices index
135 bm
.verts
.index_update()
137 # Nothing is selected, create close geometry
139 if self
.Closed
and len(vertices
) > 1:
140 bm
.edges
.new([vertices
[-1], vertices
[0]])
143 # Create faces if more than 2 vertices
144 if len(vertices
) > 1 :
145 bm
.edges
.new([vertices
[-1], vertices
[0]])
151 def CreateCutCircle(self
, context
):
152 """ Create a circle mesh """
156 # Get the mouse coordinates
157 mouse_pos_x
= self
.mouse_path
[0][0]
158 mouse_pos_y
= self
.mouse_path
[0][1]
159 coord
= self
.mouse_path
[0][0], self
.mouse_path
[0][1]
162 region
= context
.region
163 rv3d
= context
.region_data
164 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
165 self
.ViewVector
= depth_location
167 # Get a point on a infinite plane and its direction
168 plane_point
= context
.scene
.cursor
.location
if self
.snapCursor
else Vector((0.0, 0.0, 0.0))
169 plane_normal
= depth_location
170 plane_direction
= plane_normal
.normalized()
173 me
= bpy
.data
.meshes
.new('CMT_Circle')
177 # New object and link it to the scene
178 ob
= bpy
.data
.objects
.new('CMT_Circle', me
)
180 context
.collection
.objects
.link(ob
)
182 # Create a circle using a tri fan
183 tris_fan
, indices
= draw_circle(self
, mouse_pos_x
, mouse_pos_y
)
185 # Remove the vertex in the center to get the outer line of the circle
188 # Find the intersection of a line going thru each vertex and the infinite plane
190 vec
= region_2d_to_vector_3d(region
, rv3d
, vert
)
191 p0
= region_2d_to_location_3d(region
, rv3d
, vert
, vec
)
192 p1
= p0
+ plane_direction
* far_limit
193 loc0
= intersect_line_plane(p0
, p1
, plane_point
, plane_direction
)
194 t_v0
= bm
.verts
.new(loc0
)
195 FacesList
.append(t_v0
)
197 bm
.verts
.index_update()
198 bm
.faces
.new(FacesList
)
202 def create_2d_circle(self
, step
, radius
, rotation
= 0):
203 """ Create the vertices of a 2d circle at (0,0) """
205 for angle
in range(0, 360, step
):
206 verts
.append(math
.cos(math
.radians(angle
+ rotation
)) * radius
)
207 verts
.append(math
.sin(math
.radians(angle
+ rotation
)) * radius
)
209 verts
.append(math
.cos(math
.radians(0.0 + rotation
)) * radius
)
210 verts
.append(math
.sin(math
.radians(0.0 + rotation
)) * radius
)
215 def draw_circle(self
, mouse_pos_x
, mouse_pos_y
):
216 """ Return the coordinates + indices of a circle using a triangle fan """
219 segments
= int(360 / self
.stepAngle
[self
.step
])
220 radius
= self
.mouse_path
[1][0] - self
.mouse_path
[0][0]
221 rotation
= (self
.mouse_path
[1][1] - self
.mouse_path
[0][1]) / 2
223 # Get the vertices of a 2d circle
224 verts
= create_2d_circle(self
, self
.stepAngle
[self
.step
], radius
, rotation
)
226 # Create the first vertex at mouse position for the center of the circle
227 tris_verts
.append(Vector((mouse_pos_x
+ self
.xpos
, mouse_pos_y
+ self
.ypos
)))
229 # For each vertex of the circle, add the mouse position and the translation
230 for idx
in range(int(len(verts
) / 3) - 1):
231 tris_verts
.append(Vector((verts
[idx
* 3] + mouse_pos_x
+ self
.xpos
, \
232 verts
[idx
* 3 + 1] + mouse_pos_y
+ self
.ypos
)))
234 i2
= idx
+2 if idx
+2 <= segments
else 1
235 indices
.append((0,i1
,i2
))
237 return(tris_verts
, indices
)
239 # Object dimensions (SCULPT Tools tips)
240 def objDiagonal(obj
):
241 return ((obj
.dimensions
[0]**2) + (obj
.dimensions
[1]**2) + (obj
.dimensions
[2]**2))**0.5
245 def update_bevel(context
):
246 selection
= context
.selected_objects
.copy()
247 active
= context
.active_object
249 if len(selection
) > 0:
250 for obj
in selection
:
251 bpy
.ops
.object.select_all(action
='DESELECT')
253 context
.view_layer
.objects
.active
= obj
256 # Subdive mode : Only bevel weight
257 if obj
.data
.name
.startswith("S_") or obj
.data
.name
.startswith("S "):
258 bpy
.ops
.object.mode_set(mode
='EDIT')
259 bpy
.ops
.mesh
.region_to_loop()
260 bpy
.ops
.transform
.edge_bevelweight(value
=1)
261 bpy
.ops
.object.mode_set(mode
='OBJECT')
264 # No subdiv mode : bevel weight + Crease + Sharp
265 CreateBevel(context
, obj
)
267 bpy
.ops
.object.select_all(action
='DESELECT')
269 for obj
in selection
:
271 context
.view_layer
.objects
.active
= active
274 def CreateBevel(context
, CurrentObject
):
276 SavActive
= context
.active_object
278 # Test if initial object has bevel
279 bevel_modifier
= False
280 for modifier
in SavActive
.modifiers
:
281 if modifier
.name
== 'Bevel':
282 bevel_modifier
= True
285 # Active "CurrentObject"
286 context
.view_layer
.objects
.active
= CurrentObject
288 bpy
.ops
.object.mode_set(mode
='EDIT')
291 bpy
.ops
.mesh
.select_mode(use_extend
=False, use_expand
=False, type='EDGE')
293 bpy
.ops
.mesh
.select_all(action
='SELECT')
294 bpy
.ops
.mesh
.mark_sharp(clear
=True)
295 bpy
.ops
.transform
.edge_crease(value
=-1)
296 bpy
.ops
.transform
.edge_bevelweight(value
=-1)
298 bpy
.ops
.mesh
.select_all(action
='DESELECT')
300 # Select (in radians) all 30° sharp edges
301 bpy
.ops
.mesh
.edges_select_sharp(sharpness
=0.523599)
302 # Apply bevel weight + Crease + Sharp to the selected edges
303 bpy
.ops
.mesh
.mark_sharp()
304 bpy
.ops
.transform
.edge_crease(value
=1)
305 bpy
.ops
.transform
.edge_bevelweight(value
=1)
307 bpy
.ops
.mesh
.select_all(action
='DESELECT')
309 bpy
.ops
.object.mode_set(mode
='OBJECT')
311 CurrentObject
.data
.use_customdata_edge_bevel
= True
313 for i
in range(len(CurrentObject
.data
.edges
)):
314 if CurrentObject
.data
.edges
[i
].select
is True:
315 CurrentObject
.data
.edges
[i
].bevel_weight
= 1.0
316 CurrentObject
.data
.edges
[i
].use_edge_sharp
= True
318 bevel_modifier
= False
319 for m
in CurrentObject
.modifiers
:
320 if m
.name
== 'Bevel':
321 bevel_modifier
= True
323 if bevel_modifier
is False:
324 bpy
.ops
.object.modifier_add(type='BEVEL')
325 mod
= context
.object.modifiers
[-1]
326 mod
.limit_method
= 'WEIGHT'
328 mod
.profile
= 0.699099
329 mod
.use_clight_overlap
= False
331 mod
.loop_slide
= False
333 bpy
.ops
.object.shade_smooth()
335 context
.object.data
.use_auto_smooth
= True
336 context
.object.data
.auto_smooth_angle
= 1.0471975
338 # Restore the active object
339 context
.view_layer
.objects
.active
= SavActive
342 def MoveCursor(qRot
, location
, self
):
343 """ In brush mode : Draw a circle around the brush """
345 verts
= create_2d_circle(self
, 10, 1)
348 for idx
in range(int(len(verts
) / 3)):
349 vc
.x
= verts
[idx
* 3]
350 vc
.y
= verts
[idx
* 3 + 1]
351 vc
.z
= verts
[idx
* 3 + 2]
353 self
.CLR_C
.append(vc
.x
)
354 self
.CLR_C
.append(vc
.y
)
355 self
.CLR_C
.append(vc
.z
)
358 def rot_axis_quat(vector1
, vector2
):
359 """ Find the rotation (quaternion) from vector 1 to vector 2"""
360 vector1
= vector1
.normalized()
361 vector2
= vector2
.normalized()
362 cosTheta
= vector1
.dot(vector2
)
363 rotationAxis
= Vector((0.0, 0.0, 0.0))
364 if (cosTheta
< -1 + 0.001):
365 v
= Vector((0.0, 1.0, 0.0))
366 #Get the vector at the right angles to both
367 rotationAxis
= vector1
.cross(v
)
368 rotationAxis
= rotationAxis
.normalized()
375 rotationAxis
= vector1
.cross(vector2
)
376 s
= math
.sqrt((1.0 + cosTheta
) * 2.0)
380 q
.x
= rotationAxis
.x
* invs
381 q
.y
= rotationAxis
.y
* invs
382 q
.z
= rotationAxis
.z
* invs
387 def Picking(context
, event
):
388 """ Put the 3d cursor on the closest object"""
390 # get the context arguments
391 scene
= context
.scene
392 region
= context
.region
393 rv3d
= context
.region_data
394 coord
= event
.mouse_region_x
, event
.mouse_region_y
396 # get the ray from the viewport and mouse
397 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
398 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
399 ray_target
= ray_origin
+ view_vector
401 def visible_objects_and_duplis():
402 depsgraph
= context
.evaluated_depsgraph_get()
403 for dup
in depsgraph
.object_instances
:
404 if dup
.is_instance
: # Real dupli instance
405 obj
= dup
.instance_object
.original
406 yield (obj
, dup
.matrix
.copy())
408 obj
= dup
.object.original
409 yield (obj
, obj
.matrix_world
.copy())
411 def obj_ray_cast(obj
, matrix
):
412 # get the ray relative to the object
413 matrix_inv
= matrix
.inverted()
414 ray_origin_obj
= matrix_inv
@ ray_origin
415 ray_target_obj
= matrix_inv
@ ray_target
416 ray_direction_obj
= ray_target_obj
- ray_origin_obj
418 success
, location
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_direction_obj
)
420 return location
, normal
, face_index
421 return None, None, None
423 # cast rays and find the closest object
424 best_length_squared
= -1.0
427 # cast rays and find the closest object
428 for obj
, matrix
in visible_objects_and_duplis():
429 if obj
.type == 'MESH':
430 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
432 hit_world
= matrix
@ hit
433 length_squared
= (hit_world
- ray_origin
).length_squared
434 if best_obj
is None or length_squared
< best_length_squared
:
435 scene
.cursor
.location
= hit_world
436 best_length_squared
= length_squared
440 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
441 loc
= region_2d_to_location_3d(region
, rv3d
, coord
, depth_location
)
442 scene
.cursor
.location
= loc
445 def Pick(context
, event
, self
, ray_max
=10000.0):
446 region
= context
.region
447 rv3d
= context
.region_data
448 coord
= event
.mouse_region_x
, event
.mouse_region_y
449 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
450 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
451 ray_target
= ray_origin
+ (view_vector
* ray_max
)
453 def obj_ray_cast(obj
, matrix
):
454 matrix_inv
= matrix
.inverted()
455 ray_origin_obj
= matrix_inv
@ ray_origin
456 ray_target_obj
= matrix_inv
@ ray_target
457 success
, hit
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_target_obj
)
459 return hit
, normal
, face_index
460 return None, None, None
462 best_length_squared
= ray_max
* ray_max
464 for obj
in self
.CList
:
465 matrix
= obj
.matrix_world
466 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
467 rotation
= obj
.rotation_euler
.to_quaternion()
469 hit_world
= matrix
@ hit
470 length_squared
= (hit_world
- ray_origin
).length_squared
471 if length_squared
< best_length_squared
:
472 best_length_squared
= length_squared
478 if best_obj
is not None:
479 return hits
, ns
, rotation
481 return None, None, None
483 def SelectObject(self
, copyobj
):
484 copyobj
.select_set(True)
486 for child
in copyobj
.children
:
487 SelectObject(self
, child
)
489 if copyobj
.parent
is None:
490 bpy
.context
.view_layer
.objects
.active
= copyobj
498 def UndoAdd(self
, type, obj
):
499 """ Create a backup mesh before apply the action to the object """
503 if type != "DUPLICATE":
505 bm
.from_mesh(obj
.data
)
506 self
.UndoOps
.append((obj
, type, bm
))
508 self
.UndoOps
.append((obj
, type, None))
511 def UndoListUpdate(self
):
512 self
.UList
.append((self
.UndoOps
.copy()))
513 self
.UList_Index
+= 1
518 if self
.UList_Index
< 0:
521 for o
in self
.UList
[self
.UList_Index
]:
524 bm
.to_mesh(o
[0].data
)
526 SelectObjList
= bpy
.context
.selected_objects
.copy()
527 Active_Obj
= bpy
.context
.active_object
528 bpy
.ops
.object.select_all(action
='TOGGLE')
530 for o
in self
.UList
[self
.UList_Index
]:
532 o
[0].select_set(True)
533 o
[0].hide_viewport
= False
535 if o
[1] == "DUPLICATE":
536 o
[0].select_set(True)
537 o
[0].hide_viewport
= False
539 bpy
.ops
.object.delete(use_global
=False)
541 for so
in SelectObjList
:
542 bpy
.data
.objects
[so
.name
].select_set(True)
543 bpy
.context
.view_layer
.objects
.active
= Active_Obj
545 self
.UList_Index
-= 1
546 self
.UList
[self
.UList_Index
+ 1:] = []
549 def duplicateObject(self
):
551 bpy
.ops
.object.duplicate_move_linked(
552 OBJECT_OT_duplicate
={
554 "mode": 'TRANSLATION',
556 TRANSFORM_OT_translate
={
561 bpy
.ops
.object.duplicate_move(
562 OBJECT_OT_duplicate
={
564 "mode": 'TRANSLATION',
566 TRANSFORM_OT_translate
={
571 ob_new
= bpy
.context
.active_object
573 ob_new
.location
= self
.CurLoc
576 v
.z
= self
.BrushDepthOffset
577 ob_new
.location
+= self
.qRot
* v
580 ob_new
.scale
= self
.ObjectBrush
.scale
582 ob_new
.scale
= self
.ProfileBrush
.scale
586 e
.z
= self
.aRotZ
/ 25.0
588 # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
589 if (self
.alt
is True) and ((self
.nbcol
+ self
.nbrow
) < 3):
590 if self
.RandomRotation
:
591 e
.z
+= random
.random()
593 qe
= e
.to_quaternion()
594 qRot
= self
.qRot
* qe
595 ob_new
.rotation_mode
= 'QUATERNION'
596 ob_new
.rotation_quaternion
= qRot
597 ob_new
.rotation_mode
= 'XYZ'
599 if (ob_new
.display_type
== "WIRE") and (self
.BrushSolidify
is False):
600 ob_new
.hide_viewport
= True
602 if self
.BrushSolidify
:
603 ob_new
.display_type
= "SOLID"
604 ob_new
.show_in_front
= False
606 for o
in bpy
.context
.selected_objects
:
607 UndoAdd(self
, "DUPLICATE", o
)
609 if len(bpy
.context
.selected_objects
) > 0:
610 bpy
.ops
.object.select_all(action
='TOGGLE')
611 for o
in self
.all_sel_obj_list
:
614 bpy
.context
.view_layer
.objects
.active
= self
.OpsObj
617 def update_grid(self
, context
):
619 Thanks to batFINGER for his help :
620 source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
636 # Get the data from the profils or the object
638 brush
= bpy
.data
.objects
.new(
639 self
.Profils
[self
.nProfil
][0],
640 bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
642 obj
= bpy
.data
.objects
["CT_Profil"]
643 obfaces
= brush
.data
.polygons
644 obverts
= brush
.data
.vertices
645 lenverts
= len(obverts
)
647 brush
= bpy
.data
.objects
["CarverBrushCopy"]
648 obj
= context
.selected_objects
[0]
649 obverts
= brush
.data
.vertices
650 obfaces
= brush
.data
.polygons
651 lenverts
= len(brush
.data
.vertices
)
653 # Gap between each row / column
657 # Width of each row / column
658 widthx
= brush
.dimensions
.x
* self
.scale_x
659 widthy
= brush
.dimensions
.y
* self
.scale_y
661 # Compute the corners so the new object will be always at the center
662 left
= -((self
.nbcol
- 1) * (widthx
+ gapx
)) / 2
663 start
= -((self
.nbrow
- 1) * (widthy
+ gapy
)) / 2
665 for i
in range(self
.nbrow
* self
.nbcol
):
667 col
= i
// self
.nbrow
668 startx
= left
+ ((widthx
+ gapx
) * col
)
669 starty
= start
+ ((widthy
+ gapy
) * row
)
671 # Add random rotation
672 if (self
.RandomRotation
) and not (self
.GridScaleX
or self
.GridScaleY
):
673 rotmat
= Matrix
.Rotation(math
.radians(360 * random
.random()), 4, 'Z')
677 verts
.extend([((v
.co
.x
- startx
, v
.co
.y
- starty
, v
.co
.z
)) for v
in obverts
])
678 faces
.extend([[v
+ numface
* lenverts
for v
in p
.vertices
] for p
in obfaces
])
683 mymesh
= bpy
.data
.meshes
.new("CT_Profil")
685 mymesh
.from_pydata(verts
, edges
, faces
)
686 # Calculate the edges
687 mymesh
.update(calc_edges
=True)
690 # Make the object active to remove doubles
691 context
.view_layer
.objects
.active
= obj
694 def boolean_operation(bool_type
="DIFFERENCE"):
695 ActiveObj
= bpy
.context
.active_object
696 sel_index
= 0 if bpy
.context
.selected_objects
[0] != bpy
.context
.active_object
else 1
698 # bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
699 bool_name
= "CT_" + bpy
.context
.selected_objects
[sel_index
].name
700 BoolMod
= ActiveObj
.modifiers
.new(bool_name
, "BOOLEAN")
701 BoolMod
.object = bpy
.context
.selected_objects
[sel_index
]
702 BoolMod
.operation
= bool_type
703 bpy
.context
.selected_objects
[sel_index
].display_type
= 'WIRE'
704 while ActiveObj
.modifiers
.find(bool_name
) > 0:
705 bpy
.ops
.object.modifier_move_up(modifier
=bool_name
)
708 def Rebool(context
, self
):
710 target_obj
= context
.active_object
712 Brush
= context
.selected_objects
[1]
713 Brush
.display_type
= "WIRE"
716 bpy
.ops
.object.select_all(action
='TOGGLE')
718 target_obj
.display_type
= "SOLID"
719 target_obj
.select_set(True)
720 bpy
.ops
.object.duplicate()
722 rebool_obj
= context
.active_object
724 m
= rebool_obj
.modifiers
.new("CT_INTERSECT", "BOOLEAN")
725 m
.operation
= "INTERSECT"
728 m
= target_obj
.modifiers
.new("CT_DIFFERENCE", "BOOLEAN")
729 m
.operation
= "DIFFERENCE"
732 for mb
in target_obj
.modifiers
:
733 if mb
.type == 'BEVEL':
734 mb
.show_viewport
= False
736 if self
.ObjectBrush
or self
.ProfileBrush
:
737 rebool_obj
.show_in_front
= False
739 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
="CT_SOLIDIFY")
741 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
742 self
.report({'ERROR'}, str(exc_value
))
744 if self
.dont_apply_boolean
is False:
746 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
="CT_INTERSECT")
748 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
749 self
.report({'ERROR'}, str(exc_value
))
751 bpy
.ops
.object.select_all(action
='TOGGLE')
753 for mb
in target_obj
.modifiers
:
754 if mb
.type == 'BEVEL':
755 mb
.show_viewport
= True
757 context
.view_layer
.objects
.active
= target_obj
758 target_obj
.select_set(True)
759 if self
.dont_apply_boolean
is False:
761 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
="CT_DIFFERENCE")
763 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
764 self
.report({'ERROR'}, str(exc_value
))
766 bpy
.ops
.object.select_all(action
='TOGGLE')
768 rebool_obj
.select_set(True)
770 def createMeshFromData(self
):
771 if self
.Profils
[self
.nProfil
][0] not in bpy
.data
.meshes
:
772 # Create mesh and object
773 me
= bpy
.data
.meshes
.new(self
.Profils
[self
.nProfil
][0])
774 # Create mesh from given verts, faces.
775 me
.from_pydata(self
.Profils
[self
.nProfil
][2], [], self
.Profils
[self
.nProfil
][3])
776 me
.validate(verbose
=True, clean_customdata
=True)
777 # Update mesh with new data
780 if "CT_Profil" not in bpy
.data
.objects
:
781 ob
= bpy
.data
.objects
.new("CT_Profil", bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]])
782 ob
.location
= Vector((0.0, 0.0, 0.0))
784 # Link object to scene and make active
785 bpy
.context
.collection
.objects
.link(ob
)
786 bpy
.context
.view_layer
.update()
787 bpy
.context
.view_layer
.objects
.active
= ob
789 ob
.location
= Vector((10000.0, 0.0, 0.0))
790 ob
.display_type
= "WIRE"
792 self
.SolidifyPossible
= True
794 bpy
.data
.objects
["CT_Profil"].data
= bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
796 def Selection_Save_Restore(self
):
797 if "CT_Profil" in bpy
.data
.objects
:
799 bpy
.ops
.object.select_all(action
='DESELECT')
800 bpy
.data
.objects
["CT_Profil"].select_set(True)
801 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
["CT_Profil"]
802 if bpy
.data
.objects
["CT_Profil"] in self
.all_sel_obj_list
:
803 self
.all_sel_obj_list
.remove(bpy
.data
.objects
["CT_Profil"])
804 bpy
.ops
.object.delete(use_global
=False)
805 Selection_Restore(self
)
807 def Selection_Save(self
):
808 obj_name
= getattr(bpy
.context
.active_object
, "name", None)
809 self
.all_sel_obj_list
= bpy
.context
.selected_objects
.copy()
810 self
.save_active_obj
= obj_name
813 def Selection_Restore(self
):
814 for o
in self
.all_sel_obj_list
:
816 if self
.save_active_obj
:
817 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
.get(self
.save_active_obj
, None)
819 def Snap_Cursor(self
, context
, event
, mouse_pos
):
820 """ Find the closest position on the overlay grid and snap the mouse on it """
821 # Get the context arguments
822 region
= context
.region
823 rv3d
= context
.region_data
825 # Get the VIEW3D area
826 for i
, a
in enumerate(context
.screen
.areas
):
827 if a
.type == 'VIEW_3D':
828 space
= context
.screen
.areas
[i
].spaces
.active
830 # Get the grid overlay for the VIEW_3D
831 grid_scale
= space
.overlay
.grid_scale
832 grid_subdivisions
= space
.overlay
.grid_subdivisions
834 # Use the grid scale and subdivision to get the increment
835 increment
= (grid_scale
/ grid_subdivisions
)
836 half_increment
= increment
/ 2
838 # Convert the 2d location of the mouse in 3d
839 for index
, loc
in enumerate(reversed(mouse_pos
)):
840 mouse_loc_3d
= region_2d_to_location_3d(region
, rv3d
, loc
, (0, 0, 0))
842 # Get the remainder from the mouse location and the ratio
843 # Test if the remainder > to the half of the increment
845 modulo
= mouse_loc_3d
[i
] % increment
846 if modulo
< half_increment
:
849 modulo
= increment
- modulo
851 # Add the remainder to get the closest location on the grid
852 mouse_loc_3d
[i
] = mouse_loc_3d
[i
] + modulo
854 # Get the snapped 2d location
855 snap_loc_2d
= location_3d_to_region_2d(region
, rv3d
, mouse_loc_3d
)
857 # Replace the last mouse location by the snapped location
858 if len(self
.mouse_path
) > 0:
859 self
.mouse_path
[len(self
.mouse_path
) - (index
+ 1) ] = tuple(snap_loc_2d
)
861 def mini_grid(self
, context
, color
):
862 """ Draw a snap mini grid around the cursor based on the overlay grid"""
863 # Get the context arguments
864 region
= context
.region
865 rv3d
= context
.region_data
867 # Get the VIEW3D area
868 for i
, a
in enumerate(context
.screen
.areas
):
869 if a
.type == 'VIEW_3D':
870 space
= context
.screen
.areas
[i
].spaces
.active
871 screen_height
= context
.screen
.areas
[i
].height
872 screen_width
= context
.screen
.areas
[i
].width
874 #Draw the snap grid, only in ortho view
875 if not space
.region_3d
.is_perspective
:
876 grid_scale
= space
.overlay
.grid_scale
877 grid_subdivisions
= space
.overlay
.grid_subdivisions
878 increment
= (grid_scale
/ grid_subdivisions
)
880 # Get the 3d location of the mouse forced to a snap value in the operator
881 mouse_coord
= self
.mouse_path
[len(self
.mouse_path
) - 1]
883 snap_loc
= region_2d_to_location_3d(region
, rv3d
, mouse_coord
, (0, 0, 0))
885 # Add the increment to get the closest location on the grid
886 snap_loc
[0] += increment
887 snap_loc
[1] += increment
889 # Get the 2d location of the snap location
890 snap_loc
= location_3d_to_region_2d(region
, rv3d
, snap_loc
)
891 origin
= location_3d_to_region_2d(region
, rv3d
, (0,0,0))
893 # Get the increment value
894 snap_value
= snap_loc
[0] - mouse_coord
[0]
898 # Draw lines on X and Z axis from the cursor through the screen
900 (0, mouse_coord
[1]), (screen_width
, mouse_coord
[1]),
901 (mouse_coord
[0], 0), (mouse_coord
[0], screen_height
)
904 # Draw a mlini grid around the cursor to show the snap options
906 (mouse_coord
[0] + snap_value
, mouse_coord
[1] + 25 + snap_value
),
907 (mouse_coord
[0] + snap_value
, mouse_coord
[1] - 25 - snap_value
),
908 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] + snap_value
),
909 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] + snap_value
),
910 (mouse_coord
[0] - snap_value
, mouse_coord
[1] + 25 + snap_value
),
911 (mouse_coord
[0] - snap_value
, mouse_coord
[1] - 25 - snap_value
),
912 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] - snap_value
),
913 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] - snap_value
),
915 draw_shader(self
, color
, 0.3, 'LINES', grid_coords
, size
=2)
918 def draw_shader(self
, color
, alpha
, type, coords
, size
=1, indices
=None):
919 """ Create a batch for a draw type """
920 bgl
.glEnable(bgl
.GL_BLEND
)
921 bgl
.glEnable(bgl
.GL_LINE_SMOOTH
)
923 bgl
.glPointSize(size
)
925 bgl
.glLineWidth(size
)
928 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
930 shader
= gpu
.shader
.from_builtin('2D_UNIFORM_COLOR')
931 batch
= batch_for_shader(shader
, type, {"pos": coords
}, indices
=indices
)
933 shader
.uniform_float("color", (color
[0], color
[1], color
[2], alpha
))
937 bgl
.glDisable(bgl
.GL_LINE_SMOOTH
)
938 bgl
.glDisable(bgl
.GL_BLEND
)
940 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
941 self
.report({'ERROR'}, str(exc_value
))