Cleanup: remove "Tweak" event type
[blender-addons.git] / object_carver / carver_utils.py
blob0ac4c9916210a9e4137dc2acec144ef71fe12ef2
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 import bgl
5 import gpu
6 from gpu_extras.batch import batch_for_shader
7 import math
8 import sys
9 import random
10 import bmesh
11 from mathutils import (
12 Euler,
13 Matrix,
14 Vector,
15 Quaternion,
17 from mathutils.geometry import (
18 intersect_line_plane,
21 from math import (
22 sin,
23 cos,
24 pi,
27 import bpy_extras
29 from bpy_extras import view3d_utils
30 from bpy_extras.view3d_utils import (
31 region_2d_to_vector_3d,
32 region_2d_to_location_3d,
33 location_3d_to_region_2d,
36 # Cut Square
37 def CreateCutSquare(self, context):
38 """ Create a rectangle mesh """
39 far_limit = 10000.0
40 faces=[]
42 # Get the mouse coordinates
43 coord = self.mouse_path[0][0], self.mouse_path[0][1]
45 # New mesh
46 me = bpy.data.meshes.new('CMT_Square')
47 bm = bmesh.new()
48 bm.from_mesh(me)
50 # New object and link it to the scene
51 ob = bpy.data.objects.new('CMT_Square', me)
52 self.CurrentObj = ob
53 context.collection.objects.link(ob)
55 # Scene information
56 region = context.region
57 rv3d = context.region_data
58 depth_location = region_2d_to_vector_3d(region, rv3d, coord)
59 self.ViewVector = depth_location
61 # Get a point on a infinite plane and its direction
62 plane_normal = depth_location
63 plane_direction = plane_normal.normalized()
65 if self.snapCursor:
66 plane_point = context.scene.cursor.location
67 else:
68 plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
70 # Find the intersection of a line going thru each vertex and the infinite plane
71 for v_co in self.rectangle_coord:
72 vec = region_2d_to_vector_3d(region, rv3d, v_co)
73 p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
74 p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
75 faces.append(bm.verts.new(intersect_line_plane(p0, p1, plane_point, plane_direction)))
77 # Update vertices index
78 bm.verts.index_update()
79 # New faces
80 t_face = bm.faces.new(faces)
81 # Set mesh
82 bm.to_mesh(me)
85 # Cut Line
86 def CreateCutLine(self, context):
87 """ Create a polygon mesh """
88 far_limit = 10000.0
89 vertices = []
90 faces = []
91 loc = []
93 # Get the mouse coordinates
94 coord = self.mouse_path[0][0], self.mouse_path[0][1]
96 # New mesh
97 me = bpy.data.meshes.new('CMT_Line')
98 bm = bmesh.new()
99 bm.from_mesh(me)
101 # New object and link it to the scene
102 ob = bpy.data.objects.new('CMT_Line', me)
103 self.CurrentObj = ob
104 context.collection.objects.link(ob)
106 # Scene information
107 region = context.region
108 rv3d = context.region_data
109 depth_location = region_2d_to_vector_3d(region, rv3d, coord)
110 self.ViewVector = depth_location
112 # Get a point on a infinite plane and its direction
113 plane_normal = depth_location
114 plane_direction = plane_normal.normalized()
116 if self.snapCursor:
117 plane_point = context.scene.cursor.location
118 else:
119 plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
121 # Use dict to remove doubles
122 # Find the intersection of a line going thru each vertex and the infinite plane
123 for idx, v_co in enumerate(list(dict.fromkeys(self.mouse_path))):
124 vec = region_2d_to_vector_3d(region, rv3d, v_co)
125 p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
126 p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
127 loc.append(intersect_line_plane(p0, p1, plane_point, plane_direction))
128 vertices.append(bm.verts.new(loc[idx]))
130 if idx > 0:
131 bm.edges.new([vertices[idx-1],vertices[idx]])
133 faces.append(vertices[idx])
135 # Update vertices index
136 bm.verts.index_update()
138 # Nothing is selected, create close geometry
139 if self.CreateMode:
140 if self.Closed and len(vertices) > 1:
141 bm.edges.new([vertices[-1], vertices[0]])
142 bm.faces.new(faces)
143 else:
144 # Create faces if more than 2 vertices
145 if len(vertices) > 1 :
146 bm.edges.new([vertices[-1], vertices[0]])
147 bm.faces.new(faces)
149 bm.to_mesh(me)
151 # Cut Circle
152 def CreateCutCircle(self, context):
153 """ Create a circle mesh """
154 far_limit = 10000.0
155 FacesList = []
157 # Get the mouse coordinates
158 mouse_pos_x = self.mouse_path[0][0]
159 mouse_pos_y = self.mouse_path[0][1]
160 coord = self.mouse_path[0][0], self.mouse_path[0][1]
162 # Scene information
163 region = context.region
164 rv3d = context.region_data
165 depth_location = region_2d_to_vector_3d(region, rv3d, coord)
166 self.ViewVector = depth_location
168 # Get a point on a infinite plane and its direction
169 plane_point = context.scene.cursor.location if self.snapCursor else Vector((0.0, 0.0, 0.0))
170 plane_normal = depth_location
171 plane_direction = plane_normal.normalized()
173 # New mesh
174 me = bpy.data.meshes.new('CMT_Circle')
175 bm = bmesh.new()
176 bm.from_mesh(me)
178 # New object and link it to the scene
179 ob = bpy.data.objects.new('CMT_Circle', me)
180 self.CurrentObj = ob
181 context.collection.objects.link(ob)
183 # Create a circle using a tri fan
184 tris_fan, indices = draw_circle(self, mouse_pos_x, mouse_pos_y)
186 # Remove the vertex in the center to get the outer line of the circle
187 verts = tris_fan[1:]
189 # Find the intersection of a line going thru each vertex and the infinite plane
190 for vert in verts:
191 vec = region_2d_to_vector_3d(region, rv3d, vert)
192 p0 = region_2d_to_location_3d(region, rv3d, vert, vec)
193 p1 = p0 + plane_direction * far_limit
194 loc0 = intersect_line_plane(p0, p1, plane_point, plane_direction)
195 t_v0 = bm.verts.new(loc0)
196 FacesList.append(t_v0)
198 bm.verts.index_update()
199 bm.faces.new(FacesList)
200 bm.to_mesh(me)
203 def create_2d_circle(self, step, radius, rotation = 0):
204 """ Create the vertices of a 2d circle at (0,0) """
205 verts = []
206 for angle in range(0, 360, step):
207 verts.append(math.cos(math.radians(angle + rotation)) * radius)
208 verts.append(math.sin(math.radians(angle + rotation)) * radius)
209 verts.append(0.0)
210 verts.append(math.cos(math.radians(0.0 + rotation)) * radius)
211 verts.append(math.sin(math.radians(0.0 + rotation)) * radius)
212 verts.append(0.0)
213 return(verts)
216 def draw_circle(self, mouse_pos_x, mouse_pos_y):
217 """ Return the coordinates + indices of a circle using a triangle fan """
218 tris_verts = []
219 indices = []
220 segments = int(360 / self.stepAngle[self.step])
221 radius = self.mouse_path[1][0] - self.mouse_path[0][0]
222 rotation = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 2
224 # Get the vertices of a 2d circle
225 verts = create_2d_circle(self, self.stepAngle[self.step], radius, rotation)
227 # Create the first vertex at mouse position for the center of the circle
228 tris_verts.append(Vector((mouse_pos_x + self.xpos , mouse_pos_y + self.ypos)))
230 # For each vertex of the circle, add the mouse position and the translation
231 for idx in range(int(len(verts) / 3) - 1):
232 tris_verts.append(Vector((verts[idx * 3] + mouse_pos_x + self.xpos, \
233 verts[idx * 3 + 1] + mouse_pos_y + self.ypos)))
234 i1 = idx+1
235 i2 = idx+2 if idx+2 <= segments else 1
236 indices.append((0,i1,i2))
238 return(tris_verts, indices)
240 # Object dimensions (SCULPT Tools tips)
241 def objDiagonal(obj):
242 return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5
245 # Bevel Update
246 def update_bevel(context):
247 selection = context.selected_objects.copy()
248 active = context.active_object
250 if len(selection) > 0:
251 for obj in selection:
252 bpy.ops.object.select_all(action='DESELECT')
253 obj.select_set(True)
254 context.view_layer.objects.active = obj
256 # Test object name
257 # Subdive mode : Only bevel weight
258 if obj.data.name.startswith("S_") or obj.data.name.startswith("S "):
259 bpy.ops.object.mode_set(mode='EDIT')
260 bpy.ops.mesh.region_to_loop()
261 bpy.ops.transform.edge_bevelweight(value=1)
262 bpy.ops.object.mode_set(mode='OBJECT')
264 else:
265 # No subdiv mode : bevel weight + Crease + Sharp
266 CreateBevel(context, obj)
268 bpy.ops.object.select_all(action='DESELECT')
270 for obj in selection:
271 obj.select_set(True)
272 context.view_layer.objects.active = active
274 # Create bevel
275 def CreateBevel(context, CurrentObject):
276 # Save active object
277 SavActive = context.active_object
279 # Test if initial object has bevel
280 bevel_modifier = False
281 for modifier in SavActive.modifiers:
282 if modifier.name == 'Bevel':
283 bevel_modifier = True
285 if bevel_modifier:
286 # Active "CurrentObject"
287 context.view_layer.objects.active = CurrentObject
289 bpy.ops.object.mode_set(mode='EDIT')
291 # Edge mode
292 bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
293 # Clear all
294 bpy.ops.mesh.select_all(action='SELECT')
295 bpy.ops.mesh.mark_sharp(clear=True)
296 bpy.ops.transform.edge_crease(value=-1)
297 bpy.ops.transform.edge_bevelweight(value=-1)
299 bpy.ops.mesh.select_all(action='DESELECT')
301 # Select (in radians) all 30° sharp edges
302 bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
303 # Apply bevel weight + Crease + Sharp to the selected edges
304 bpy.ops.mesh.mark_sharp()
305 bpy.ops.transform.edge_crease(value=1)
306 bpy.ops.transform.edge_bevelweight(value=1)
308 bpy.ops.mesh.select_all(action='DESELECT')
310 bpy.ops.object.mode_set(mode='OBJECT')
312 CurrentObject.data.use_customdata_edge_bevel = True
314 for i in range(len(CurrentObject.data.edges)):
315 if CurrentObject.data.edges[i].select is True:
316 CurrentObject.data.edges[i].bevel_weight = 1.0
317 CurrentObject.data.edges[i].use_edge_sharp = True
319 bevel_modifier = False
320 for m in CurrentObject.modifiers:
321 if m.name == 'Bevel':
322 bevel_modifier = True
324 if bevel_modifier is False:
325 bpy.ops.object.modifier_add(type='BEVEL')
326 mod = context.object.modifiers[-1]
327 mod.limit_method = 'WEIGHT'
328 mod.width = 0.01
329 mod.profile = 0.699099
330 mod.use_clamp_overlap = False
331 mod.segments = 3
332 mod.loop_slide = False
334 bpy.ops.object.shade_smooth()
336 context.object.data.use_auto_smooth = True
337 context.object.data.auto_smooth_angle = 1.0471975
339 # Restore the active object
340 context.view_layer.objects.active = SavActive
343 def MoveCursor(qRot, location, self):
344 """ In brush mode : Draw a circle around the brush """
345 if qRot is not None:
346 verts = create_2d_circle(self, 10, 1)
347 self.CLR_C.clear()
348 vc = Vector()
349 for idx in range(int(len(verts) / 3)):
350 vc.x = verts[idx * 3]
351 vc.y = verts[idx * 3 + 1]
352 vc.z = verts[idx * 3 + 2]
353 vc = qRot @ vc
354 self.CLR_C.append(vc.x)
355 self.CLR_C.append(vc.y)
356 self.CLR_C.append(vc.z)
359 def rot_axis_quat(vector1, vector2):
360 """ Find the rotation (quaternion) from vector 1 to vector 2"""
361 vector1 = vector1.normalized()
362 vector2 = vector2.normalized()
363 cosTheta = vector1.dot(vector2)
364 rotationAxis = Vector((0.0, 0.0, 0.0))
365 if (cosTheta < -1 + 0.001):
366 v = Vector((0.0, 1.0, 0.0))
367 #Get the vector at the right angles to both
368 rotationAxis = vector1.cross(v)
369 rotationAxis = rotationAxis.normalized()
370 q = Quaternion()
371 q.w = 0.0
372 q.x = rotationAxis.x
373 q.y = rotationAxis.y
374 q.z = rotationAxis.z
375 else:
376 rotationAxis = vector1.cross(vector2)
377 s = math.sqrt((1.0 + cosTheta) * 2.0)
378 invs = 1 / s
379 q = Quaternion()
380 q.w = s * 0.5
381 q.x = rotationAxis.x * invs
382 q.y = rotationAxis.y * invs
383 q.z = rotationAxis.z * invs
384 return q
387 # Picking (template)
388 def Picking(context, event):
389 """ Put the 3d cursor on the closest object"""
391 # get the context arguments
392 scene = context.scene
393 region = context.region
394 rv3d = context.region_data
395 coord = event.mouse_region_x, event.mouse_region_y
397 # get the ray from the viewport and mouse
398 view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
399 ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
400 ray_target = ray_origin + view_vector
402 def visible_objects_and_duplis():
403 depsgraph = context.evaluated_depsgraph_get()
404 for dup in depsgraph.object_instances:
405 if dup.is_instance: # Real dupli instance
406 obj = dup.instance_object.original
407 yield (obj, dup.matrix.copy())
408 else: # Usual object
409 obj = dup.object.original
410 yield (obj, obj.matrix_world.copy())
412 def obj_ray_cast(obj, matrix):
413 # get the ray relative to the object
414 matrix_inv = matrix.inverted()
415 ray_origin_obj = matrix_inv @ ray_origin
416 ray_target_obj = matrix_inv @ ray_target
417 ray_direction_obj = ray_target_obj - ray_origin_obj
418 # cast the ray
419 success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
420 if success:
421 return location, normal, face_index
422 return None, None, None
424 # cast rays and find the closest object
425 best_length_squared = -1.0
426 best_obj = None
428 # cast rays and find the closest object
429 for obj, matrix in visible_objects_and_duplis():
430 if obj.type == 'MESH':
431 hit, normal, face_index = obj_ray_cast(obj, matrix)
432 if hit is not None:
433 hit_world = matrix @ hit
434 length_squared = (hit_world - ray_origin).length_squared
435 if best_obj is None or length_squared < best_length_squared:
436 scene.cursor.location = hit_world
437 best_length_squared = length_squared
438 best_obj = obj
439 else:
440 if best_obj is None:
441 depth_location = region_2d_to_vector_3d(region, rv3d, coord)
442 loc = region_2d_to_location_3d(region, rv3d, coord, depth_location)
443 scene.cursor.location = loc
446 def Pick(context, event, self, ray_max=10000.0):
447 region = context.region
448 rv3d = context.region_data
449 coord = event.mouse_region_x, event.mouse_region_y
450 view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
451 ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
452 ray_target = ray_origin + (view_vector * ray_max)
454 def obj_ray_cast(obj, matrix):
455 matrix_inv = matrix.inverted()
456 ray_origin_obj = matrix_inv @ ray_origin
457 ray_target_obj = matrix_inv @ ray_target
458 success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
459 if success:
460 return hit, normal, face_index
461 return None, None, None
463 best_length_squared = ray_max * ray_max
464 best_obj = None
465 for obj in self.CList:
466 matrix = obj.matrix_world
467 hit, normal, face_index = obj_ray_cast(obj, matrix)
468 rotation = obj.rotation_euler.to_quaternion()
469 if hit is not None:
470 hit_world = matrix @ hit
471 length_squared = (hit_world - ray_origin).length_squared
472 if length_squared < best_length_squared:
473 best_length_squared = length_squared
474 best_obj = obj
475 hits = hit_world
476 ns = normal
477 fs = face_index
479 if best_obj is not None:
480 return hits, ns, rotation
482 return None, None, None
484 def SelectObject(self, copyobj):
485 copyobj.select_set(True)
487 for child in copyobj.children:
488 SelectObject(self, child)
490 if copyobj.parent is None:
491 bpy.context.view_layer.objects.active = copyobj
493 # Undo
494 def printUndo(self):
495 for l in self.UList:
496 print(l)
499 def UndoAdd(self, type, obj):
500 """ Create a backup mesh before apply the action to the object """
501 if obj is None:
502 return
504 if type != "DUPLICATE":
505 bm = bmesh.new()
506 bm.from_mesh(obj.data)
507 self.UndoOps.append((obj, type, bm))
508 else:
509 self.UndoOps.append((obj, type, None))
512 def UndoListUpdate(self):
513 self.UList.append((self.UndoOps.copy()))
514 self.UList_Index += 1
515 self.UndoOps.clear()
518 def Undo(self):
519 if self.UList_Index < 0:
520 return
521 # get previous mesh
522 for o in self.UList[self.UList_Index]:
523 if o[1] == "MESH":
524 bm = o[2]
525 bm.to_mesh(o[0].data)
527 SelectObjList = bpy.context.selected_objects.copy()
528 Active_Obj = bpy.context.active_object
529 bpy.ops.object.select_all(action='TOGGLE')
531 for o in self.UList[self.UList_Index]:
532 if o[1] == "REBOOL":
533 o[0].select_set(True)
534 o[0].hide_viewport = False
536 if o[1] == "DUPLICATE":
537 o[0].select_set(True)
538 o[0].hide_viewport = False
540 bpy.ops.object.delete(use_global=False)
542 for so in SelectObjList:
543 bpy.data.objects[so.name].select_set(True)
544 bpy.context.view_layer.objects.active = Active_Obj
546 self.UList_Index -= 1
547 self.UList[self.UList_Index + 1:] = []
550 def duplicateObject(self):
551 if self.Instantiate:
552 bpy.ops.object.duplicate_move_linked(
553 OBJECT_OT_duplicate={
554 "linked": True,
555 "mode": 'TRANSLATION',
557 TRANSFORM_OT_translate={
558 "value": (0, 0, 0),
561 else:
562 bpy.ops.object.duplicate_move(
563 OBJECT_OT_duplicate={
564 "linked": False,
565 "mode": 'TRANSLATION',
567 TRANSFORM_OT_translate={
568 "value": (0, 0, 0),
572 ob_new = bpy.context.active_object
574 ob_new.location = self.CurLoc
575 v = Vector()
576 v.x = v.y = 0.0
577 v.z = self.BrushDepthOffset
578 ob_new.location += self.qRot * v
580 if self.ObjectMode:
581 ob_new.scale = self.ObjectBrush.scale
582 if self.ProfileMode:
583 ob_new.scale = self.ProfileBrush.scale
585 e = Euler()
586 e.x = e.y = 0.0
587 e.z = self.aRotZ / 25.0
589 # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
590 if (self.alt is True) and ((self.nbcol + self.nbrow) < 3):
591 if self.RandomRotation:
592 e.z += random.random()
594 qe = e.to_quaternion()
595 qRot = self.qRot * qe
596 ob_new.rotation_mode = 'QUATERNION'
597 ob_new.rotation_quaternion = qRot
598 ob_new.rotation_mode = 'XYZ'
600 if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
601 ob_new.hide_viewport = True
603 if self.BrushSolidify:
604 ob_new.display_type = "SOLID"
605 ob_new.show_in_front = False
607 for o in bpy.context.selected_objects:
608 UndoAdd(self, "DUPLICATE", o)
610 if len(bpy.context.selected_objects) > 0:
611 bpy.ops.object.select_all(action='TOGGLE')
612 for o in self.all_sel_obj_list:
613 o.select_set(True)
615 bpy.context.view_layer.objects.active = self.OpsObj
618 def update_grid(self, context):
620 Thanks to batFINGER for his help :
621 source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
623 verts = []
624 edges = []
625 faces = []
626 numface = 0
628 if self.nbcol < 1:
629 self.nbcol = 1
630 if self.nbrow < 1:
631 self.nbrow = 1
632 if self.gapx < 0:
633 self.gapx = 0
634 if self.gapy < 0:
635 self.gapy = 0
637 # Get the data from the profils or the object
638 if self.ProfileMode:
639 brush = bpy.data.objects.new(
640 self.Profils[self.nProfil][0],
641 bpy.data.meshes[self.Profils[self.nProfil][0]]
643 obj = bpy.data.objects["CT_Profil"]
644 obfaces = brush.data.polygons
645 obverts = brush.data.vertices
646 lenverts = len(obverts)
647 else:
648 brush = bpy.data.objects["CarverBrushCopy"]
649 obj = context.selected_objects[0]
650 obverts = brush.data.vertices
651 obfaces = brush.data.polygons
652 lenverts = len(brush.data.vertices)
654 # Gap between each row / column
655 gapx = self.gapx
656 gapy = self.gapy
658 # Width of each row / column
659 widthx = brush.dimensions.x * self.scale_x
660 widthy = brush.dimensions.y * self.scale_y
662 # Compute the corners so the new object will be always at the center
663 left = -((self.nbcol - 1) * (widthx + gapx)) / 2
664 start = -((self.nbrow - 1) * (widthy + gapy)) / 2
666 for i in range(self.nbrow * self.nbcol):
667 row = i % self.nbrow
668 col = i // self.nbrow
669 startx = left + ((widthx + gapx) * col)
670 starty = start + ((widthy + gapy) * row)
672 # Add random rotation
673 if (self.RandomRotation) and not (self.GridScaleX or self.GridScaleY):
674 rotmat = Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z')
675 for v in obverts:
676 v.co = v.co @ rotmat
678 verts.extend([((v.co.x - startx, v.co.y - starty, v.co.z)) for v in obverts])
679 faces.extend([[v + numface * lenverts for v in p.vertices] for p in obfaces])
680 numface += 1
682 # Update the mesh
683 # Create mesh data
684 mymesh = bpy.data.meshes.new("CT_Profil")
685 # Generate mesh data
686 mymesh.from_pydata(verts, edges, faces)
687 # Calculate the edges
688 mymesh.update(calc_edges=True)
689 # Update data
690 obj.data = mymesh
691 # Make the object active to remove doubles
692 context.view_layer.objects.active = obj
695 def boolean_operation(bool_type="DIFFERENCE"):
696 ActiveObj = bpy.context.active_object
697 sel_index = 0 if bpy.context.selected_objects[0] != bpy.context.active_object else 1
699 # bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
700 bool_name = "CT_" + bpy.context.selected_objects[sel_index].name
701 BoolMod = ActiveObj.modifiers.new(bool_name, "BOOLEAN")
702 BoolMod.object = bpy.context.selected_objects[sel_index]
703 BoolMod.operation = bool_type
704 bpy.context.selected_objects[sel_index].display_type = 'WIRE'
705 while ActiveObj.modifiers.find(bool_name) > 0:
706 bpy.ops.object.modifier_move_up(modifier=bool_name)
709 def Rebool(context, self):
711 target_obj = context.active_object
713 Brush = context.selected_objects[1]
714 Brush.display_type = "WIRE"
716 #Deselect all
717 bpy.ops.object.select_all(action='TOGGLE')
719 target_obj.display_type = "SOLID"
720 target_obj.select_set(True)
721 bpy.ops.object.duplicate()
723 rebool_obj = context.active_object
725 m = rebool_obj.modifiers.new("CT_INTERSECT", "BOOLEAN")
726 m.operation = "INTERSECT"
727 m.object = Brush
729 m = target_obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN")
730 m.operation = "DIFFERENCE"
731 m.object = Brush
733 for mb in target_obj.modifiers:
734 if mb.type == 'BEVEL':
735 mb.show_viewport = False
737 if self.ObjectBrush or self.ProfileBrush:
738 rebool_obj.show_in_front = False
739 try:
740 bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
741 except:
742 exc_type, exc_value, exc_traceback = sys.exc_info()
743 self.report({'ERROR'}, str(exc_value))
745 if self.dont_apply_boolean is False:
746 try:
747 bpy.ops.object.modifier_apply(modifier="CT_INTERSECT")
748 except:
749 exc_type, exc_value, exc_traceback = sys.exc_info()
750 self.report({'ERROR'}, str(exc_value))
752 bpy.ops.object.select_all(action='TOGGLE')
754 for mb in target_obj.modifiers:
755 if mb.type == 'BEVEL':
756 mb.show_viewport = True
758 context.view_layer.objects.active = target_obj
759 target_obj.select_set(True)
760 if self.dont_apply_boolean is False:
761 try:
762 bpy.ops.object.modifier_apply(modifier="CT_DIFFERENCE")
763 except:
764 exc_type, exc_value, exc_traceback = sys.exc_info()
765 self.report({'ERROR'}, str(exc_value))
767 bpy.ops.object.select_all(action='TOGGLE')
769 rebool_obj.select_set(True)
771 def createMeshFromData(self):
772 if self.Profils[self.nProfil][0] not in bpy.data.meshes:
773 # Create mesh and object
774 me = bpy.data.meshes.new(self.Profils[self.nProfil][0])
775 # Create mesh from given verts, faces.
776 me.from_pydata(self.Profils[self.nProfil][2], [], self.Profils[self.nProfil][3])
777 me.validate(verbose=True, clean_customdata=True)
778 # Update mesh with new data
779 me.update()
781 if "CT_Profil" not in bpy.data.objects:
782 ob = bpy.data.objects.new("CT_Profil", bpy.data.meshes[self.Profils[self.nProfil][0]])
783 ob.location = Vector((0.0, 0.0, 0.0))
785 # Link object to scene and make active
786 bpy.context.collection.objects.link(ob)
787 bpy.context.view_layer.update()
788 bpy.context.view_layer.objects.active = ob
789 ob.select_set(True)
790 ob.location = Vector((10000.0, 0.0, 0.0))
791 ob.display_type = "WIRE"
793 self.SolidifyPossible = True
794 else:
795 bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]]
797 def Selection_Save_Restore(self):
798 if "CT_Profil" in bpy.data.objects:
799 Selection_Save(self)
800 bpy.ops.object.select_all(action='DESELECT')
801 bpy.data.objects["CT_Profil"].select_set(True)
802 bpy.context.view_layer.objects.active = bpy.data.objects["CT_Profil"]
803 if bpy.data.objects["CT_Profil"] in self.all_sel_obj_list:
804 self.all_sel_obj_list.remove(bpy.data.objects["CT_Profil"])
805 bpy.ops.object.delete(use_global=False)
806 Selection_Restore(self)
808 def Selection_Save(self):
809 obj_name = getattr(bpy.context.active_object, "name", None)
810 self.all_sel_obj_list = bpy.context.selected_objects.copy()
811 self.save_active_obj = obj_name
814 def Selection_Restore(self):
815 for o in self.all_sel_obj_list:
816 o.select_set(True)
817 if self.save_active_obj:
818 bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None)
820 def Snap_Cursor(self, context, event, mouse_pos):
821 """ Find the closest position on the overlay grid and snap the mouse on it """
822 # Get the context arguments
823 region = context.region
824 rv3d = context.region_data
826 # Get the VIEW3D area
827 for i, a in enumerate(context.screen.areas):
828 if a.type == 'VIEW_3D':
829 space = context.screen.areas[i].spaces.active
831 # Get the grid overlay for the VIEW_3D
832 grid_scale = space.overlay.grid_scale
833 grid_subdivisions = space.overlay.grid_subdivisions
835 # Use the grid scale and subdivision to get the increment
836 increment = (grid_scale / grid_subdivisions)
837 half_increment = increment / 2
839 # Convert the 2d location of the mouse in 3d
840 for index, loc in enumerate(reversed(mouse_pos)):
841 mouse_loc_3d = region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))
843 # Get the remainder from the mouse location and the ratio
844 # Test if the remainder > to the half of the increment
845 for i in range(3):
846 modulo = mouse_loc_3d[i] % increment
847 if modulo < half_increment:
848 modulo = - modulo
849 else:
850 modulo = increment - modulo
852 # Add the remainder to get the closest location on the grid
853 mouse_loc_3d[i] = mouse_loc_3d[i] + modulo
855 # Get the snapped 2d location
856 snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d)
858 # Replace the last mouse location by the snapped location
859 if len(self.mouse_path) > 0:
860 self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)
862 def mini_grid(self, context, color):
863 """ Draw a snap mini grid around the cursor based on the overlay grid"""
864 # Get the context arguments
865 region = context.region
866 rv3d = context.region_data
868 # Get the VIEW3D area
869 for i, a in enumerate(context.screen.areas):
870 if a.type == 'VIEW_3D':
871 space = context.screen.areas[i].spaces.active
872 screen_height = context.screen.areas[i].height
873 screen_width = context.screen.areas[i].width
875 #Draw the snap grid, only in ortho view
876 if not space.region_3d.is_perspective :
877 grid_scale = space.overlay.grid_scale
878 grid_subdivisions = space.overlay.grid_subdivisions
879 increment = (grid_scale / grid_subdivisions)
881 # Get the 3d location of the mouse forced to a snap value in the operator
882 mouse_coord = self.mouse_path[len(self.mouse_path) - 1]
884 snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0))
886 # Add the increment to get the closest location on the grid
887 snap_loc[0] += increment
888 snap_loc[1] += increment
890 # Get the 2d location of the snap location
891 snap_loc = location_3d_to_region_2d(region, rv3d, snap_loc)
892 origin = location_3d_to_region_2d(region, rv3d, (0,0,0))
894 # Get the increment value
895 snap_value = snap_loc[0] - mouse_coord[0]
897 grid_coords = []
899 # Draw lines on X and Z axis from the cursor through the screen
900 grid_coords = [
901 (0, mouse_coord[1]), (screen_width, mouse_coord[1]),
902 (mouse_coord[0], 0), (mouse_coord[0], screen_height)
905 # Draw a mlini grid around the cursor to show the snap options
906 grid_coords += [
907 (mouse_coord[0] + snap_value, mouse_coord[1] + 25 + snap_value),
908 (mouse_coord[0] + snap_value, mouse_coord[1] - 25 - snap_value),
909 (mouse_coord[0] + 25 + snap_value, mouse_coord[1] + snap_value),
910 (mouse_coord[0] - 25 - snap_value, mouse_coord[1] + snap_value),
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),
916 draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2)
919 def draw_shader(self, color, alpha, type, coords, size=1, indices=None):
920 """ Create a batch for a draw type """
921 bgl.glEnable(bgl.GL_BLEND)
922 bgl.glEnable(bgl.GL_LINE_SMOOTH)
923 if type =='POINTS':
924 bgl.glPointSize(size)
925 else:
926 bgl.glLineWidth(size)
927 try:
928 if len(coords[0])>2:
929 shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
930 else:
931 shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
932 batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices)
933 shader.bind()
934 shader.uniform_float("color", (color[0], color[1], color[2], alpha))
935 batch.draw(shader)
936 bgl.glLineWidth(1)
937 bgl.glPointSize(1)
938 bgl.glDisable(bgl.GL_LINE_SMOOTH)
939 bgl.glDisable(bgl.GL_BLEND)
940 except:
941 exc_type, exc_value, exc_traceback = sys.exc_info()
942 self.report({'ERROR'}, str(exc_value))