1 2import bpy 3import bgl 4import gpu 5from gpu_extras.batch import batch_for_shader 6import math 7import sys 8import random 9import bmesh 10from mathutils import ( 11 Euler, 12 Matrix, 13 Vector, 14 Quaternion, 15) 16from mathutils.geometry import ( 17 intersect_line_plane, 18) 19 20from math import ( 21 sin, 22 cos, 23 pi, 24 ) 25 26import bpy_extras 27 28from bpy_extras import view3d_utils 29from bpy_extras.view3d_utils import ( 30 region_2d_to_vector_3d, 31 region_2d_to_location_3d, 32 location_3d_to_region_2d, 33) 34 35# Cut Square 36def CreateCutSquare(self, context): 37 """ Create a rectangle mesh """ 38 far_limit = 10000.0 39 faces=[] 40 41 # Get the mouse coordinates 42 coord = self.mouse_path[0][0], self.mouse_path[0][1] 43 44 # New mesh 45 me = bpy.data.meshes.new('CMT_Square') 46 bm = bmesh.new() 47 bm.from_mesh(me) 48 49 # New object and link it to the scene 50 ob = bpy.data.objects.new('CMT_Square', me) 51 self.CurrentObj = ob 52 context.collection.objects.link(ob) 53 54 # Scene information 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 59 60 # Get a point on a infinite plane and its direction 61 plane_normal = depth_location 62 plane_direction = plane_normal.normalized() 63 64 if self.snapCursor: 65 plane_point = context.scene.cursor.location 66 else: 67 plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0)) 68 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))) 75 76 # Update vertices index 77 bm.verts.index_update() 78 # New faces 79 t_face = bm.faces.new(faces) 80 # Set mesh 81 bm.to_mesh(me) 82 83 84# Cut Line 85def CreateCutLine(self, context): 86 """ Create a polygon mesh """ 87 far_limit = 10000.0 88 vertices = [] 89 faces = [] 90 loc = [] 91 92 # Get the mouse coordinates 93 coord = self.mouse_path[0][0], self.mouse_path[0][1] 94 95 # New mesh 96 me = bpy.data.meshes.new('CMT_Line') 97 bm = bmesh.new() 98 bm.from_mesh(me) 99 100 # New object and link it to the scene 101 ob = bpy.data.objects.new('CMT_Line', me) 102 self.CurrentObj = ob 103 context.collection.objects.link(ob) 104 105 # Scene information 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 110 111 # Get a point on a infinite plane and its direction 112 plane_normal = depth_location 113 plane_direction = plane_normal.normalized() 114 115 if self.snapCursor: 116 plane_point = context.scene.cursor.location 117 else: 118 plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0)) 119 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])) 128 129 if idx > 0: 130 bm.edges.new([vertices[idx-1],vertices[idx]]) 131 132 faces.append(vertices[idx]) 133 134 # Update vertices index 135 bm.verts.index_update() 136 137 # Nothing is selected, create close geometry 138 if self.CreateMode: 139 if self.Closed and len(vertices) > 1: 140 bm.edges.new([vertices[-1], vertices[0]]) 141 bm.faces.new(faces) 142 else: 143 # Create faces if more than 2 vertices 144 if len(vertices) > 1 : 145 bm.edges.new([vertices[-1], vertices[0]]) 146 bm.faces.new(faces) 147 148 bm.to_mesh(me) 149 150# Cut Circle 151def CreateCutCircle(self, context): 152 """ Create a circle mesh """ 153 far_limit = 10000.0 154 FacesList = [] 155 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] 160 161 # Scene information 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 166 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() 171 172 # New mesh 173 me = bpy.data.meshes.new('CMT_Circle') 174 bm = bmesh.new() 175 bm.from_mesh(me) 176 177 # New object and link it to the scene 178 ob = bpy.data.objects.new('CMT_Circle', me) 179 self.CurrentObj = ob 180 context.collection.objects.link(ob) 181 182 # Create a circle using a tri fan 183 tris_fan, indices = draw_circle(self, mouse_pos_x, mouse_pos_y) 184 185 # Remove the vertex in the center to get the outer line of the circle 186 verts = tris_fan[1:] 187 188 # Find the intersection of a line going thru each vertex and the infinite plane 189 for vert in verts: 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) 196 197 bm.verts.index_update() 198 bm.faces.new(FacesList) 199 bm.to_mesh(me) 200 201 202def create_2d_circle(self, step, radius, rotation = 0): 203 """ Create the vertices of a 2d circle at (0,0) """ 204 verts = [] 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) 208 verts.append(0.0) 209 verts.append(math.cos(math.radians(0.0 + rotation)) * radius) 210 verts.append(math.sin(math.radians(0.0 + rotation)) * radius) 211 verts.append(0.0) 212 return(verts) 213 214 215def draw_circle(self, mouse_pos_x, mouse_pos_y): 216 """ Return the coordinates + indices of a circle using a triangle fan """ 217 tris_verts = [] 218 indices = [] 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 222 223 # Get the vertices of a 2d circle 224 verts = create_2d_circle(self, self.stepAngle[self.step], radius, rotation) 225 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))) 228 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))) 233 i1 = idx+1 234 i2 = idx+2 if idx+2 <= segments else 1 235 indices.append((0,i1,i2)) 236 237 return(tris_verts, indices) 238 239# Object dimensions (SCULPT Tools tips) 240def objDiagonal(obj): 241 return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5 242 243 244# Bevel Update 245def update_bevel(context): 246 selection = context.selected_objects.copy() 247 active = context.active_object 248 249 if len(selection) > 0: 250 for obj in selection: 251 bpy.ops.object.select_all(action='DESELECT') 252 obj.select_set(True) 253 context.view_layer.objects.active = obj 254 255 # Test object name 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') 262 263 else: 264 # No subdiv mode : bevel weight + Crease + Sharp 265 CreateBevel(context, obj) 266 267 bpy.ops.object.select_all(action='DESELECT') 268 269 for obj in selection: 270 obj.select_set(True) 271 context.view_layer.objects.active = active 272 273# Create bevel 274def CreateBevel(context, CurrentObject): 275 # Save active object 276 SavActive = context.active_object 277 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 283 284 if bevel_modifier: 285 # Active "CurrentObject" 286 context.view_layer.objects.active = CurrentObject 287 288 bpy.ops.object.mode_set(mode='EDIT') 289 290 # Edge mode 291 bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') 292 # Clear all 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) 297 298 bpy.ops.mesh.select_all(action='DESELECT') 299 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) 306 307 bpy.ops.mesh.select_all(action='DESELECT') 308 309 bpy.ops.object.mode_set(mode='OBJECT') 310 311 CurrentObject.data.use_customdata_edge_bevel = True 312 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 317 318 bevel_modifier = False 319 for m in CurrentObject.modifiers: 320 if m.name == 'Bevel': 321 bevel_modifier = True 322 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' 327 mod.width = 0.01 328 mod.profile = 0.699099 329 mod.use_clight_overlap = False 330 mod.segments = 3 331 mod.loop_slide = False 332 333 bpy.ops.object.shade_smooth() 334 335 context.object.data.use_auto_smooth = True 336 context.object.data.auto_smooth_angle = 1.0471975 337 338 # Restore the active object 339 context.view_layer.objects.active = SavActive 340 341 342def MoveCursor(qRot, location, self): 343 """ In brush mode : Draw a circle around the brush """ 344 if qRot is not None: 345 verts = create_2d_circle(self, 10, 1) 346 self.CLR_C.clear() 347 vc = Vector() 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] 352 vc = qRot @ vc 353 self.CLR_C.append(vc.x) 354 self.CLR_C.append(vc.y) 355 self.CLR_C.append(vc.z) 356 357 358def 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() 369 q = Quaternion() 370 q.w = 0.0 371 q.x = rotationAxis.x 372 q.y = rotationAxis.y 373 q.z = rotationAxis.z 374 else: 375 rotationAxis = vector1.cross(vector2) 376 s = math.sqrt((1.0 + cosTheta) * 2.0) 377 invs = 1 / s 378 q = Quaternion() 379 q.w = s * 0.5 380 q.x = rotationAxis.x * invs 381 q.y = rotationAxis.y * invs 382 q.z = rotationAxis.z * invs 383 return q 384 385 386# Picking (template) 387def Picking(context, event): 388 """ Put the 3d cursor on the closest object""" 389 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 395 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 400 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()) 407 else: # Usual object 408 obj = dup.object.original 409 yield (obj, obj.matrix_world.copy()) 410 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 417 # cast the ray 418 success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj) 419 if success: 420 return location, normal, face_index 421 return None, None, None 422 423 # cast rays and find the closest object 424 best_length_squared = -1.0 425 best_obj = None 426 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) 431 if hit is not None: 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 437 best_obj = obj 438 else: 439 if best_obj is None: 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 443 444 445def 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) 452 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) 458 if success: 459 return hit, normal, face_index 460 return None, None, None 461 462 best_length_squared = ray_max * ray_max 463 best_obj = None 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() 468 if hit is not None: 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 473 best_obj = obj 474 hits = hit_world 475 ns = normal 476 fs = face_index 477 478 if best_obj is not None: 479 return hits, ns, rotation 480 481 return None, None, None 482 483def SelectObject(self, copyobj): 484 copyobj.select_set(True) 485 486 for child in copyobj.children: 487 SelectObject(self, child) 488 489 if copyobj.parent is None: 490 bpy.context.view_layer.objects.active = copyobj 491 492# Undo 493def printUndo(self): 494 for l in self.UList: 495 print(l) 496 497 498def UndoAdd(self, type, obj): 499 """ Create a backup mesh before apply the action to the object """ 500 if obj is None: 501 return 502 503 if type != "DUPLICATE": 504 bm = bmesh.new() 505 bm.from_mesh(obj.data) 506 self.UndoOps.append((obj, type, bm)) 507 else: 508 self.UndoOps.append((obj, type, None)) 509 510 511def UndoListUpdate(self): 512 self.UList.append((self.UndoOps.copy())) 513 self.UList_Index += 1 514 self.UndoOps.clear() 515 516 517def Undo(self): 518 if self.UList_Index < 0: 519 return 520 # get previous mesh 521 for o in self.UList[self.UList_Index]: 522 if o[1] == "MESH": 523 bm = o[2] 524 bm.to_mesh(o[0].data) 525 526 SelectObjList = bpy.context.selected_objects.copy() 527 Active_Obj = bpy.context.active_object 528 bpy.ops.object.select_all(action='TOGGLE') 529 530 for o in self.UList[self.UList_Index]: 531 if o[1] == "REBOOL": 532 o[0].select_set(True) 533 o[0].hide_viewport = False 534 535 if o[1] == "DUPLICATE": 536 o[0].select_set(True) 537 o[0].hide_viewport = False 538 539 bpy.ops.object.delete(use_global=False) 540 541 for so in SelectObjList: 542 bpy.data.objects[so.name].select_set(True) 543 bpy.context.view_layer.objects.active = Active_Obj 544 545 self.UList_Index -= 1 546 self.UList[self.UList_Index + 1:] = [] 547 548 549def duplicateObject(self): 550 if self.Instantiate: 551 bpy.ops.object.duplicate_move_linked( 552 OBJECT_OT_duplicate={ 553 "linked": True, 554 "mode": 'TRANSLATION', 555 }, 556 TRANSFORM_OT_translate={ 557 "value": (0, 0, 0), 558 }, 559 ) 560 else: 561 bpy.ops.object.duplicate_move( 562 OBJECT_OT_duplicate={ 563 "linked": False, 564 "mode": 'TRANSLATION', 565 }, 566 TRANSFORM_OT_translate={ 567 "value": (0, 0, 0), 568 }, 569 ) 570 571 ob_new = bpy.context.active_object 572 573 ob_new.location = self.CurLoc 574 v = Vector() 575 v.x = v.y = 0.0 576 v.z = self.BrushDepthOffset 577 ob_new.location += self.qRot * v 578 579 if self.ObjectMode: 580 ob_new.scale = self.ObjectBrush.scale 581 if self.ProfileMode: 582 ob_new.scale = self.ProfileBrush.scale 583 584 e = Euler() 585 e.x = e.y = 0.0 586 e.z = self.aRotZ / 25.0 587 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() 592 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' 598 599 if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False): 600 ob_new.hide_viewport = True 601 602 if self.BrushSolidify: 603 ob_new.display_type = "SOLID" 604 ob_new.show_in_front = False 605 606 for o in bpy.context.selected_objects: 607 UndoAdd(self, "DUPLICATE", o) 608 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: 612 o.select_set(True) 613 614 bpy.context.view_layer.objects.active = self.OpsObj 615 616 617def update_grid(self, context): 618 """ 619 Thanks to batFINGER for his help : 620 source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata 621 """ 622 verts = [] 623 edges = [] 624 faces = [] 625 numface = 0 626 627 if self.nbcol < 1: 628 self.nbcol = 1 629 if self.nbrow < 1: 630 self.nbrow = 1 631 if self.gapx < 0: 632 self.gapx = 0 633 if self.gapy < 0: 634 self.gapy = 0 635 636 # Get the data from the profils or the object 637 if self.ProfileMode: 638 brush = bpy.data.objects.new( 639 self.Profils[self.nProfil][0], 640 bpy.data.meshes[self.Profils[self.nProfil][0]] 641 ) 642 obj = bpy.data.objects["CT_Profil"] 643 obfaces = brush.data.polygons 644 obverts = brush.data.vertices 645 lenverts = len(obverts) 646 else: 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) 652 653 # Gap between each row / column 654 gapx = self.gapx 655 gapy = self.gapy 656 657 # Width of each row / column 658 widthx = brush.dimensions.x * self.scale_x 659 widthy = brush.dimensions.y * self.scale_y 660 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 664 665 for i in range(self.nbrow * self.nbcol): 666 row = i % self.nbrow 667 col = i // self.nbrow 668 startx = left + ((widthx + gapx) * col) 669 starty = start + ((widthy + gapy) * row) 670 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') 674 for v in obverts: 675 v.co = v.co @ rotmat 676 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]) 679 numface += 1 680 681 # Update the mesh 682 # Create mesh data 683 mymesh = bpy.data.meshes.new("CT_Profil") 684 # Generate mesh data 685 mymesh.from_pydata(verts, edges, faces) 686 # Calculate the edges 687 mymesh.update(calc_edges=True) 688 # Update data 689 obj.data = mymesh 690 # Make the object active to remove doubles 691 context.view_layer.objects.active = obj 692 693 694def 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 697 698 # bpy.ops.object.modifier_apply(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) 706 707 708def Rebool(context, self): 709 710 target_obj = context.active_object 711 712 Brush = context.selected_objects[1] 713 Brush.display_type = "WIRE" 714 715 #Deselect all 716 bpy.ops.object.select_all(action='TOGGLE') 717 718 target_obj.display_type = "SOLID" 719 target_obj.select_set(True) 720 bpy.ops.object.duplicate() 721 722 rebool_obj = context.active_object 723 724 m = rebool_obj.modifiers.new("CT_INTERSECT", "BOOLEAN") 725 m.operation = "INTERSECT" 726 m.object = Brush 727 728 m = target_obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN") 729 m.operation = "DIFFERENCE" 730 m.object = Brush 731 732 for mb in target_obj.modifiers: 733 if mb.type == 'BEVEL': 734 mb.show_viewport = False 735 736 if self.ObjectBrush or self.ProfileBrush: 737 rebool_obj.show_in_front = False 738 try: 739 bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY") 740 except: 741 exc_type, exc_value, exc_traceback = sys.exc_info() 742 self.report({'ERROR'}, str(exc_value)) 743 744 if self.dont_apply_boolean is False: 745 try: 746 bpy.ops.object.modifier_apply(modifier="CT_INTERSECT") 747 except: 748 exc_type, exc_value, exc_traceback = sys.exc_info() 749 self.report({'ERROR'}, str(exc_value)) 750 751 bpy.ops.object.select_all(action='TOGGLE') 752 753 for mb in target_obj.modifiers: 754 if mb.type == 'BEVEL': 755 mb.show_viewport = True 756 757 context.view_layer.objects.active = target_obj 758 target_obj.select_set(True) 759 if self.dont_apply_boolean is False: 760 try: 761 bpy.ops.object.modifier_apply(modifier="CT_DIFFERENCE") 762 except: 763 exc_type, exc_value, exc_traceback = sys.exc_info() 764 self.report({'ERROR'}, str(exc_value)) 765 766 bpy.ops.object.select_all(action='TOGGLE') 767 768 rebool_obj.select_set(True) 769 770def 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 778 me.update() 779 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)) 783 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 788 ob.select_set(True) 789 ob.location = Vector((10000.0, 0.0, 0.0)) 790 ob.display_type = "WIRE" 791 792 self.SolidifyPossible = True 793 else: 794 bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]] 795 796def Selection_Save_Restore(self): 797 if "CT_Profil" in bpy.data.objects: 798 Selection_Save(self) 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) 806 807def 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 811 812 813def Selection_Restore(self): 814 for o in self.all_sel_obj_list: 815 o.select_set(True) 816 if self.save_active_obj: 817 bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None) 818 819def 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 824 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 829 830 # Get the grid overlay for the VIEW_3D 831 grid_scale = space.overlay.grid_scale 832 grid_subdivisions = space.overlay.grid_subdivisions 833 834 # Use the grid scale and subdivision to get the increment 835 increment = (grid_scale / grid_subdivisions) 836 half_increment = increment / 2 837 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)) 841 842 # Get the remainder from the mouse location and the ratio 843 # Test if the remainder > to the half of the increment 844 for i in range(3): 845 modulo = mouse_loc_3d[i] % increment 846 if modulo < half_increment: 847 modulo = - modulo 848 else: 849 modulo = increment - modulo 850 851 # Add the remainder to get the closest location on the grid 852 mouse_loc_3d[i] = mouse_loc_3d[i] + modulo 853 854 # Get the snapped 2d location 855 snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d) 856 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) 860 861def 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 866 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 873 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) 879 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] 882 883 snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0)) 884 885 # Add the increment to get the closest location on the grid 886 snap_loc[0] += increment 887 snap_loc[1] += increment 888 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)) 892 893 # Get the increment value 894 snap_value = snap_loc[0] - mouse_coord[0] 895 896 grid_coords = [] 897 898 # Draw lines on X and Z axis from the cursor through the screen 899 grid_coords = [ 900 (0, mouse_coord[1]), (screen_width, mouse_coord[1]), 901 (mouse_coord[0], 0), (mouse_coord[0], screen_height) 902 ] 903 904 # Draw a mlini grid around the cursor to show the snap options 905 grid_coords += [ 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), 914 ] 915 draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2) 916 917 918def 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) 922 if type =='POINTS': 923 bgl.glPointSize(size) 924 else: 925 bgl.glLineWidth(size) 926 try: 927 if len(coords[0])>2: 928 shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') 929 else: 930 shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') 931 batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices) 932 shader.bind() 933 shader.uniform_float("color", (color[0], color[1], color[2], alpha)) 934 batch.draw(shader) 935 bgl.glLineWidth(1) 936 bgl.glPointSize(1) 937 bgl.glDisable(bgl.GL_LINE_SMOOTH) 938 bgl.glDisable(bgl.GL_BLEND) 939 except: 940 exc_type, exc_value, exc_traceback = sys.exc_info() 941 self.report({'ERROR'}, str(exc_value)) 942