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
.use_auto_smooth
= True
342 context
.object.data
.auto_smooth_angle
= 1.0471975
344 # Restore the active object
345 context
.view_layer
.objects
.active
= SavActive
348 def MoveCursor(qRot
, location
, self
):
349 """ In brush mode : Draw a circle around the brush """
351 verts
= create_2d_circle(self
, 10, 1)
354 for idx
in range(int(len(verts
) / 3)):
355 vc
.x
= verts
[idx
* 3]
356 vc
.y
= verts
[idx
* 3 + 1]
357 vc
.z
= verts
[idx
* 3 + 2]
359 self
.CLR_C
.append(vc
.x
)
360 self
.CLR_C
.append(vc
.y
)
361 self
.CLR_C
.append(vc
.z
)
364 def rot_axis_quat(vector1
, vector2
):
365 """ Find the rotation (quaternion) from vector 1 to vector 2"""
366 vector1
= vector1
.normalized()
367 vector2
= vector2
.normalized()
368 cosTheta
= vector1
.dot(vector2
)
369 rotationAxis
= Vector((0.0, 0.0, 0.0))
370 if (cosTheta
< -1 + 0.001):
371 v
= Vector((0.0, 1.0, 0.0))
372 #Get the vector at the right angles to both
373 rotationAxis
= vector1
.cross(v
)
374 rotationAxis
= rotationAxis
.normalized()
381 rotationAxis
= vector1
.cross(vector2
)
382 s
= math
.sqrt((1.0 + cosTheta
) * 2.0)
386 q
.x
= rotationAxis
.x
* invs
387 q
.y
= rotationAxis
.y
* invs
388 q
.z
= rotationAxis
.z
* invs
393 def Picking(context
, event
):
394 """ Put the 3d cursor on the closest object"""
396 # get the context arguments
397 scene
= context
.scene
398 region
= context
.region
399 rv3d
= context
.region_data
400 coord
= event
.mouse_region_x
, event
.mouse_region_y
402 # get the ray from the viewport and mouse
403 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
404 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
405 ray_target
= ray_origin
+ view_vector
407 def visible_objects_and_duplis():
408 depsgraph
= context
.evaluated_depsgraph_get()
409 for dup
in depsgraph
.object_instances
:
410 if dup
.is_instance
: # Real dupli instance
411 obj
= dup
.instance_object
.original
412 yield (obj
, dup
.matrix
.copy())
414 obj
= dup
.object.original
415 yield (obj
, obj
.matrix_world
.copy())
417 def obj_ray_cast(obj
, matrix
):
418 # get the ray relative to the object
419 matrix_inv
= matrix
.inverted()
420 ray_origin_obj
= matrix_inv
@ ray_origin
421 ray_target_obj
= matrix_inv
@ ray_target
422 ray_direction_obj
= ray_target_obj
- ray_origin_obj
424 success
, location
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_direction_obj
)
426 return location
, normal
, face_index
427 return None, None, None
429 # cast rays and find the closest object
430 best_length_squared
= -1.0
433 # cast rays and find the closest object
434 for obj
, matrix
in visible_objects_and_duplis():
435 if obj
.type == 'MESH':
436 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
438 hit_world
= matrix
@ hit
439 length_squared
= (hit_world
- ray_origin
).length_squared
440 if best_obj
is None or length_squared
< best_length_squared
:
441 scene
.cursor
.location
= hit_world
442 best_length_squared
= length_squared
446 depth_location
= region_2d_to_vector_3d(region
, rv3d
, coord
)
447 loc
= region_2d_to_location_3d(region
, rv3d
, coord
, depth_location
)
448 scene
.cursor
.location
= loc
451 def Pick(context
, event
, self
, ray_max
=10000.0):
452 region
= context
.region
453 rv3d
= context
.region_data
454 coord
= event
.mouse_region_x
, event
.mouse_region_y
455 view_vector
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, coord
)
456 ray_origin
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, coord
)
457 ray_target
= ray_origin
+ (view_vector
* ray_max
)
459 def obj_ray_cast(obj
, matrix
):
460 matrix_inv
= matrix
.inverted()
461 ray_origin_obj
= matrix_inv
@ ray_origin
462 ray_target_obj
= matrix_inv
@ ray_target
463 success
, hit
, normal
, face_index
= obj
.ray_cast(ray_origin_obj
, ray_target_obj
)
465 return hit
, normal
, face_index
466 return None, None, None
468 best_length_squared
= ray_max
* ray_max
470 for obj
in self
.CList
:
471 matrix
= obj
.matrix_world
472 hit
, normal
, face_index
= obj_ray_cast(obj
, matrix
)
473 rotation
= obj
.rotation_euler
.to_quaternion()
475 hit_world
= matrix
@ hit
476 length_squared
= (hit_world
- ray_origin
).length_squared
477 if length_squared
< best_length_squared
:
478 best_length_squared
= length_squared
484 if best_obj
is not None:
485 return hits
, ns
, rotation
487 return None, None, None
489 def SelectObject(self
, copyobj
):
490 copyobj
.select_set(True)
492 for child
in copyobj
.children
:
493 SelectObject(self
, child
)
495 if copyobj
.parent
is None:
496 bpy
.context
.view_layer
.objects
.active
= copyobj
504 def UndoAdd(self
, type, obj
):
505 """ Create a backup mesh before apply the action to the object """
509 if type != "DUPLICATE":
511 bm
.from_mesh(obj
.data
)
512 self
.UndoOps
.append((obj
, type, bm
))
514 self
.UndoOps
.append((obj
, type, None))
517 def UndoListUpdate(self
):
518 self
.UList
.append((self
.UndoOps
.copy()))
519 self
.UList_Index
+= 1
524 if self
.UList_Index
< 0:
527 for o
in self
.UList
[self
.UList_Index
]:
530 bm
.to_mesh(o
[0].data
)
532 SelectObjList
= bpy
.context
.selected_objects
.copy()
533 Active_Obj
= bpy
.context
.active_object
534 bpy
.ops
.object.select_all(action
='TOGGLE')
536 for o
in self
.UList
[self
.UList_Index
]:
538 o
[0].select_set(True)
539 o
[0].hide_viewport
= False
541 if o
[1] == "DUPLICATE":
542 o
[0].select_set(True)
543 o
[0].hide_viewport
= False
545 bpy
.ops
.object.delete(use_global
=False)
547 for so
in SelectObjList
:
548 bpy
.data
.objects
[so
.name
].select_set(True)
549 bpy
.context
.view_layer
.objects
.active
= Active_Obj
551 self
.UList_Index
-= 1
552 self
.UList
[self
.UList_Index
+ 1:] = []
555 def duplicateObject(self
):
557 bpy
.ops
.object.duplicate_move_linked(
558 OBJECT_OT_duplicate
={
560 "mode": 'TRANSLATION',
562 TRANSFORM_OT_translate
={
567 bpy
.ops
.object.duplicate_move(
568 OBJECT_OT_duplicate
={
570 "mode": 'TRANSLATION',
572 TRANSFORM_OT_translate
={
577 ob_new
= bpy
.context
.active_object
579 ob_new
.location
= self
.CurLoc
582 v
.z
= self
.BrushDepthOffset
583 ob_new
.location
+= self
.qRot
* v
586 ob_new
.scale
= self
.ObjectBrush
.scale
588 ob_new
.scale
= self
.ProfileBrush
.scale
592 e
.z
= self
.aRotZ
/ 25.0
594 # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
595 if (self
.alt
is True) and ((self
.nbcol
+ self
.nbrow
) < 3):
596 if self
.RandomRotation
:
597 e
.z
+= random
.random()
599 qe
= e
.to_quaternion()
600 qRot
= self
.qRot
* qe
601 ob_new
.rotation_mode
= 'QUATERNION'
602 ob_new
.rotation_quaternion
= qRot
603 ob_new
.rotation_mode
= 'XYZ'
605 if (ob_new
.display_type
== "WIRE") and (self
.BrushSolidify
is False):
606 ob_new
.hide_viewport
= True
608 if self
.BrushSolidify
:
609 ob_new
.display_type
= "SOLID"
610 ob_new
.show_in_front
= False
612 for o
in bpy
.context
.selected_objects
:
613 UndoAdd(self
, "DUPLICATE", o
)
615 if len(bpy
.context
.selected_objects
) > 0:
616 bpy
.ops
.object.select_all(action
='TOGGLE')
617 for o
in self
.all_sel_obj_list
:
620 bpy
.context
.view_layer
.objects
.active
= self
.OpsObj
623 def update_grid(self
, context
):
625 Thanks to batFINGER for his help :
626 source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
642 # Get the data from the profils or the object
644 brush
= bpy
.data
.objects
.new(
645 self
.Profils
[self
.nProfil
][0],
646 bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
648 obj
= bpy
.data
.objects
["CT_Profil"]
649 obfaces
= brush
.data
.polygons
650 obverts
= brush
.data
.vertices
651 lenverts
= len(obverts
)
653 brush
= bpy
.data
.objects
["CarverBrushCopy"]
654 obj
= context
.selected_objects
[0]
655 obverts
= brush
.data
.vertices
656 obfaces
= brush
.data
.polygons
657 lenverts
= len(brush
.data
.vertices
)
659 # Gap between each row / column
663 # Width of each row / column
664 widthx
= brush
.dimensions
.x
* self
.scale_x
665 widthy
= brush
.dimensions
.y
* self
.scale_y
667 # Compute the corners so the new object will be always at the center
668 left
= -((self
.nbcol
- 1) * (widthx
+ gapx
)) / 2
669 start
= -((self
.nbrow
- 1) * (widthy
+ gapy
)) / 2
671 for i
in range(self
.nbrow
* self
.nbcol
):
673 col
= i
// self
.nbrow
674 startx
= left
+ ((widthx
+ gapx
) * col
)
675 starty
= start
+ ((widthy
+ gapy
) * row
)
677 # Add random rotation
678 if (self
.RandomRotation
) and not (self
.GridScaleX
or self
.GridScaleY
):
679 rotmat
= Matrix
.Rotation(math
.radians(360 * random
.random()), 4, 'Z')
683 verts
.extend([((v
.co
.x
- startx
, v
.co
.y
- starty
, v
.co
.z
)) for v
in obverts
])
684 faces
.extend([[v
+ numface
* lenverts
for v
in p
.vertices
] for p
in obfaces
])
689 mymesh
= bpy
.data
.meshes
.new("CT_Profil")
691 mymesh
.from_pydata(verts
, edges
, faces
)
692 # Calculate the edges
693 mymesh
.update(calc_edges
=True)
696 # Make the object active to remove doubles
697 context
.view_layer
.objects
.active
= obj
700 def boolean_operation(bool_type
="DIFFERENCE"):
701 ActiveObj
= bpy
.context
.active_object
702 sel_index
= 0 if bpy
.context
.selected_objects
[0] != bpy
.context
.active_object
else 1
704 # bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
705 bool_name
= "CT_" + bpy
.context
.selected_objects
[sel_index
].name
706 BoolMod
= ActiveObj
.modifiers
.new(bool_name
, "BOOLEAN")
707 BoolMod
.object = bpy
.context
.selected_objects
[sel_index
]
708 BoolMod
.operation
= bool_type
709 bpy
.context
.selected_objects
[sel_index
].display_type
= 'WIRE'
710 while ActiveObj
.modifiers
.find(bool_name
) > 0:
711 bpy
.ops
.object.modifier_move_up(modifier
=bool_name
)
714 def Rebool(context
, self
):
716 target_obj
= context
.active_object
718 Brush
= context
.selected_objects
[1]
719 Brush
.display_type
= "WIRE"
722 bpy
.ops
.object.select_all(action
='TOGGLE')
724 target_obj
.display_type
= "SOLID"
725 target_obj
.select_set(True)
726 bpy
.ops
.object.duplicate()
728 rebool_obj
= context
.active_object
730 m
= rebool_obj
.modifiers
.new("CT_INTERSECT", "BOOLEAN")
731 m
.operation
= "INTERSECT"
734 m
= target_obj
.modifiers
.new("CT_DIFFERENCE", "BOOLEAN")
735 m
.operation
= "DIFFERENCE"
738 for mb
in target_obj
.modifiers
:
739 if mb
.type == 'BEVEL':
740 mb
.show_viewport
= False
742 if self
.ObjectBrush
or self
.ProfileBrush
:
743 rebool_obj
.show_in_front
= False
745 bpy
.ops
.object.modifier_apply(modifier
="CT_SOLIDIFY")
747 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
748 self
.report({'ERROR'}, str(exc_value
))
750 if self
.dont_apply_boolean
is False:
752 bpy
.ops
.object.modifier_apply(modifier
="CT_INTERSECT")
754 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
755 self
.report({'ERROR'}, str(exc_value
))
757 bpy
.ops
.object.select_all(action
='TOGGLE')
759 for mb
in target_obj
.modifiers
:
760 if mb
.type == 'BEVEL':
761 mb
.show_viewport
= True
763 context
.view_layer
.objects
.active
= target_obj
764 target_obj
.select_set(True)
765 if self
.dont_apply_boolean
is False:
767 bpy
.ops
.object.modifier_apply(modifier
="CT_DIFFERENCE")
769 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
770 self
.report({'ERROR'}, str(exc_value
))
772 bpy
.ops
.object.select_all(action
='TOGGLE')
774 rebool_obj
.select_set(True)
776 def createMeshFromData(self
):
777 if self
.Profils
[self
.nProfil
][0] not in bpy
.data
.meshes
:
778 # Create mesh and object
779 me
= bpy
.data
.meshes
.new(self
.Profils
[self
.nProfil
][0])
780 # Create mesh from given verts, faces.
781 me
.from_pydata(self
.Profils
[self
.nProfil
][2], [], self
.Profils
[self
.nProfil
][3])
782 me
.validate(verbose
=True, clean_customdata
=True)
783 # Update mesh with new data
786 if "CT_Profil" not in bpy
.data
.objects
:
787 ob
= bpy
.data
.objects
.new("CT_Profil", bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]])
788 ob
.location
= Vector((0.0, 0.0, 0.0))
790 # Link object to scene and make active
791 bpy
.context
.collection
.objects
.link(ob
)
792 bpy
.context
.view_layer
.update()
793 bpy
.context
.view_layer
.objects
.active
= ob
795 ob
.location
= Vector((10000.0, 0.0, 0.0))
796 ob
.display_type
= "WIRE"
798 self
.SolidifyPossible
= True
800 bpy
.data
.objects
["CT_Profil"].data
= bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
802 def Selection_Save_Restore(self
):
803 if "CT_Profil" in bpy
.data
.objects
:
805 bpy
.ops
.object.select_all(action
='DESELECT')
806 bpy
.data
.objects
["CT_Profil"].select_set(True)
807 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
["CT_Profil"]
808 if bpy
.data
.objects
["CT_Profil"] in self
.all_sel_obj_list
:
809 self
.all_sel_obj_list
.remove(bpy
.data
.objects
["CT_Profil"])
810 bpy
.ops
.object.delete(use_global
=False)
811 Selection_Restore(self
)
813 def Selection_Save(self
):
814 obj_name
= getattr(bpy
.context
.active_object
, "name", None)
815 self
.all_sel_obj_list
= bpy
.context
.selected_objects
.copy()
816 self
.save_active_obj
= obj_name
819 def Selection_Restore(self
):
820 for o
in self
.all_sel_obj_list
:
822 if self
.save_active_obj
:
823 bpy
.context
.view_layer
.objects
.active
= bpy
.data
.objects
.get(self
.save_active_obj
, None)
825 def Snap_Cursor(self
, context
, event
, mouse_pos
):
826 """ Find the closest position on the overlay grid and snap the mouse on it """
827 # Get the context arguments
828 region
= context
.region
829 rv3d
= context
.region_data
831 # Get the VIEW3D area
832 for i
, a
in enumerate(context
.screen
.areas
):
833 if a
.type == 'VIEW_3D':
834 space
= context
.screen
.areas
[i
].spaces
.active
836 # Get the grid overlay for the VIEW_3D
837 grid_scale
= space
.overlay
.grid_scale
838 grid_subdivisions
= space
.overlay
.grid_subdivisions
840 # Use the grid scale and subdivision to get the increment
841 increment
= (grid_scale
/ grid_subdivisions
)
842 half_increment
= increment
/ 2
844 # Convert the 2d location of the mouse in 3d
845 for index
, loc
in enumerate(reversed(mouse_pos
)):
846 mouse_loc_3d
= region_2d_to_location_3d(region
, rv3d
, loc
, (0, 0, 0))
848 # Get the remainder from the mouse location and the ratio
849 # Test if the remainder > to the half of the increment
851 modulo
= mouse_loc_3d
[i
] % increment
852 if modulo
< half_increment
:
855 modulo
= increment
- modulo
857 # Add the remainder to get the closest location on the grid
858 mouse_loc_3d
[i
] = mouse_loc_3d
[i
] + modulo
860 # Get the snapped 2d location
861 snap_loc_2d
= location_3d_to_region_2d(region
, rv3d
, mouse_loc_3d
)
863 # Replace the last mouse location by the snapped location
864 if len(self
.mouse_path
) > 0:
865 self
.mouse_path
[len(self
.mouse_path
) - (index
+ 1) ] = tuple(snap_loc_2d
)
867 def mini_grid(self
, context
, color
):
868 """ Draw a snap mini grid around the cursor based on the overlay grid"""
869 # Get the context arguments
870 region
= context
.region
871 rv3d
= context
.region_data
873 # Get the VIEW3D area
874 for i
, a
in enumerate(context
.screen
.areas
):
875 if a
.type == 'VIEW_3D':
876 space
= context
.screen
.areas
[i
].spaces
.active
877 screen_height
= context
.screen
.areas
[i
].height
878 screen_width
= context
.screen
.areas
[i
].width
880 #Draw the snap grid, only in ortho view
881 if not space
.region_3d
.is_perspective
:
882 grid_scale
= space
.overlay
.grid_scale
883 grid_subdivisions
= space
.overlay
.grid_subdivisions
884 increment
= (grid_scale
/ grid_subdivisions
)
886 # Get the 3d location of the mouse forced to a snap value in the operator
887 mouse_coord
= self
.mouse_path
[len(self
.mouse_path
) - 1]
889 snap_loc
= region_2d_to_location_3d(region
, rv3d
, mouse_coord
, (0, 0, 0))
891 # Add the increment to get the closest location on the grid
892 snap_loc
[0] += increment
893 snap_loc
[1] += increment
895 # Get the 2d location of the snap location
896 snap_loc
= location_3d_to_region_2d(region
, rv3d
, snap_loc
)
897 origin
= location_3d_to_region_2d(region
, rv3d
, (0,0,0))
899 # Get the increment value
900 snap_value
= snap_loc
[0] - mouse_coord
[0]
904 # Draw lines on X and Z axis from the cursor through the screen
906 (0, mouse_coord
[1]), (screen_width
, mouse_coord
[1]),
907 (mouse_coord
[0], 0), (mouse_coord
[0], screen_height
)
910 # Draw a mlini grid around the cursor to show the snap options
912 (mouse_coord
[0] + snap_value
, mouse_coord
[1] + 25 + snap_value
),
913 (mouse_coord
[0] + snap_value
, mouse_coord
[1] - 25 - snap_value
),
914 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] + snap_value
),
915 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] + snap_value
),
916 (mouse_coord
[0] - snap_value
, mouse_coord
[1] + 25 + snap_value
),
917 (mouse_coord
[0] - snap_value
, mouse_coord
[1] - 25 - snap_value
),
918 (mouse_coord
[0] + 25 + snap_value
, mouse_coord
[1] - snap_value
),
919 (mouse_coord
[0] - 25 - snap_value
, mouse_coord
[1] - snap_value
),
921 draw_shader(self
, color
, 0.3, 'LINES', grid_coords
, size
=2)
924 def draw_shader(self
, color
, alpha
, type, coords
, size
=1, indices
=None):
925 """ Create a batch for a draw type """
926 gpu
.state
.blend_set('ALPHA')
928 gpu
.state
.program_point_size_set(False)
929 gpu
.state
.point_size_set(size
)
930 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR')
932 shader
= gpu
.shader
.from_builtin('POLYLINE_UNIFORM_COLOR')
933 shader
.uniform_float("viewportSize", gpu
.state
.viewport_get()[2:])
934 shader
.uniform_float("lineWidth", 1.0)
937 shader
.uniform_float("color", (color
[0], color
[1], color
[2], alpha
))
938 batch
= batch_for_shader(shader
, type, {"pos": coords
}, indices
=indices
)
941 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
942 self
.report({'ERROR'}, str(exc_value
))
944 gpu
.state
.point_size_set(1.0)
945 gpu
.state
.blend_set('NONE')