1# ***** BEGIN GPL LICENSE BLOCK ***** 2# 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU General Public License 6# as published by the Free Software Foundation; either version 2 7# of the License, or (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program; if not, write to the Free Software Foundation, 16# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17# 18# ***** END GPL LICENCE BLOCK ***** 19# 20# ----------------------------------------------------------------------- 21# Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019 22# ----------------------------------------------------------------------- 23# 24from bpy.types import Operator 25from .pdt_msg_strings import ( 26 PDT_ERR_NON_VALID, 27 PDT_LAB_ABS, 28 PDT_LAB_DEL, 29 PDT_LAB_DIR, 30 PDT_LAB_INTERSECT, 31 PDT_LAB_PERCENT, 32) 33 34 35class PDT_OT_PlacementAbs(Operator): 36 """Use Absolute, or Global Placement.""" 37 38 bl_idname = "pdt.absolute" 39 bl_label = "Absolute Mode" 40 bl_options = {"REGISTER", "UNDO"} 41 42 def execute(self, context): 43 """Manipulates Geometry, or Objects by Absolute (World) Coordinates. 44 45 Note: 46 - Reads pg.operate from Operation Mode Selector as 'operation' 47 - Reads pg.cartesian_coords scene variables to: 48 -- set position of CUrsor (CU) 49 -- set postion of Pivot Point (PP) 50 -- MoVe geometry/objects (MV) 51 -- Extrude Vertices (EV) 52 -- Split Edges (SE) 53 -- add a New Vertex (NV) 54 55 Invalid Options result in self.report Error. 56 57 Args: 58 context: Blender bpy.context instance. 59 60 Returns: 61 Status Set. 62 """ 63 64 pg = context.scene.pdt_pg 65 operation = pg.operation 66 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 67 68 if operation == "CU": 69 # Cursor 70 pg.command = ( 71 f"ca{str(round(pg.cartesian_coords.x, decimal_places))}" 72 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 73 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 74 ) 75 elif operation == "PP": 76 # Pivot Point 77 pg.command = ( 78 f"pa{str(round(pg.cartesian_coords.x, decimal_places))}" 79 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 80 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 81 ) 82 elif operation == "MV": 83 # Move Entities 84 pg.command = ( 85 f"ga{str(round(pg.cartesian_coords.x, decimal_places))}" 86 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 87 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 88 ) 89 elif operation == "SE": 90 # Split Edges 91 pg.command = ( 92 f"sa{str(round(pg.cartesian_coords.x, decimal_places))}" 93 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 94 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 95 ) 96 elif operation == "NV": 97 # New Vertex 98 pg.command = ( 99 f"na{str(round(pg.cartesian_coords.x, decimal_places))}" 100 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 101 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 102 ) 103 elif operation == "EV": 104 # Extrude Vertices 105 pg.command = ( 106 f"va{str(round(pg.cartesian_coords.x, decimal_places))}" 107 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 108 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 109 ) 110 else: 111 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_ABS}" 112 self.report({"ERROR"}, error_message) 113 return {"FINISHED"} 114 115 116class PDT_OT_PlacementDelta(Operator): 117 """Use Delta, or Incremental Placement.""" 118 119 bl_idname = "pdt.delta" 120 bl_label = "Delta Mode" 121 bl_options = {"REGISTER", "UNDO"} 122 123 def execute(self, context): 124 """Manipulates Geometry, or Objects by Delta Offset (Increment). 125 126 Note: 127 - Reads pg.operation from Operation Mode Selector as 'operation' 128 - Reads pg.select, pg.plane, pg.cartesian_coords scene variables to: 129 -- set position of CUrsor (CU) 130 -- set position of Pivot Point (PP) 131 -- MoVe geometry/objects (MV) 132 -- Extrude Vertices (EV) 133 -- Split Edges (SE) 134 -- add a New Vertex (NV) 135 -- Duplicate Geometry (DG) 136 -- Extrude Geometry (EG) 137 138 Invalid Options result in self.report Error. 139 140 Args: 141 context: Blender bpy.context instance. 142 143 Returns: 144 Status Set. 145 """ 146 147 pg = context.scene.pdt_pg 148 operation = pg.operation 149 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 150 151 if operation == "CU": 152 # Cursor 153 pg.command = ( 154 f"cd{str(round(pg.cartesian_coords.x, decimal_places))}" 155 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 156 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 157 ) 158 elif operation == "PP": 159 # Pivot Point 160 pg.command = ( 161 f"pd{str(round(pg.cartesian_coords.x, decimal_places))}" 162 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 163 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 164 ) 165 elif operation == "MV": 166 # Move Entities 167 pg.command = ( 168 f"gd{str(round(pg.cartesian_coords.x, decimal_places))}" 169 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 170 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 171 ) 172 elif operation == "SE": 173 # Split Edges 174 pg.command = ( 175 f"sd{str(round(pg.cartesian_coords.x, decimal_places))}" 176 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 177 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 178 ) 179 elif operation == "NV": 180 # New Vertex 181 pg.command = ( 182 f"nd{str(round(pg.cartesian_coords.x, decimal_places))}" 183 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 184 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 185 ) 186 elif operation == "EV": 187 # Extrue Vertices 188 pg.command = ( 189 f"vd{str(round(pg.cartesian_coords.x, decimal_places))}" 190 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 191 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 192 ) 193 elif operation == "DG": 194 # Duplicate Entities 195 pg.command = ( 196 f"dd{str(round(pg.cartesian_coords.x, decimal_places))}" 197 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 198 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 199 ) 200 elif operation == "EG": 201 # Extrue Geometry 202 pg.command = ( 203 f"ed{str(round(pg.cartesian_coords.x, decimal_places))}" 204 f",{str(round(pg.cartesian_coords.y, decimal_places))}" 205 f",{str(round(pg.cartesian_coords.z, decimal_places))}" 206 ) 207 else: 208 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_DEL}" 209 self.report({"ERROR"}, error_message) 210 return {"FINISHED"} 211 212 213class PDT_OT_PlacementDis(Operator): 214 """Use Directional, or Distance @ Angle Placement.""" 215 216 bl_idname = "pdt.distance" 217 bl_label = "Distance@Angle Mode" 218 bl_options = {"REGISTER", "UNDO"} 219 220 def execute(self, context): 221 """Manipulates Geometry, or Objects by Distance at Angle (Direction). 222 223 Note: 224 - Reads pg.operation from Operation Mode Selector as 'operation' 225 - Reads pg.select, pg.distance, pg.angle, pg.plane & pg.flip_angle scene variables to: 226 -- set position of CUrsor (CU) 227 -- set position of Pivot Point (PP) 228 -- MoVe geometry/objects (MV) 229 -- Extrude Vertices (EV) 230 -- Split Edges (SE) 231 -- add a New Vertex (NV) 232 -- Duplicate Geometry (DG) 233 -- Extrude Geometry (EG) 234 235 Invalid Options result in self.report Error. 236 237 Args: 238 context: Blender bpy.context instance. 239 240 Returns: 241 Status Set. 242 """ 243 244 pg = context.scene.pdt_pg 245 operation = pg.operation 246 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 247 248 if operation == "CU": 249 # Cursor 250 pg.command = ( 251 f"ci{str(round(pg.distance, decimal_places))}" 252 f",{str(round(pg.angle, decimal_places))}" 253 ) 254 elif operation == "PP": 255 # Pivot Point 256 pg.command = ( 257 f"pi{str(round(pg.distance, decimal_places))}" 258 f",{str(round(pg.angle, decimal_places))}" 259 ) 260 elif operation == "MV": 261 # Move Entities 262 pg.command = ( 263 f"gi{str(round(pg.distance, decimal_places))}" 264 f",{str(round(pg.angle, decimal_places))}" 265 ) 266 elif operation == "SE": 267 # Split Edges 268 pg.command = ( 269 f"si{str(round(pg.distance, decimal_places))}" 270 f",{str(round(pg.angle, decimal_places))}" 271 ) 272 elif operation == "NV": 273 # New Vertex 274 pg.command = ( 275 f"ni{str(round(pg.distance, decimal_places))}" 276 f",{str(round(pg.angle, decimal_places))}" 277 ) 278 elif operation == "EV": 279 # Extrude Vertices 280 pg.command = ( 281 f"vi{str(round(pg.distance, decimal_places))}" 282 f",{str(round(pg.angle, decimal_places))}" 283 ) 284 elif operation == "DG": 285 # Duplicate Geometry 286 pg.command = ( 287 f"di{str(round(pg.distance, decimal_places))}" 288 f",{str(round(pg.angle, decimal_places))}" 289 ) 290 elif operation == "EG": 291 # Extrude Geometry 292 pg.command = ( 293 f"ei{str(round(pg.distance, decimal_places))}" 294 f",{str(round(pg.angle, decimal_places))}" 295 ) 296 else: 297 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_DIR}" 298 self.report({"ERROR"}, error_message) 299 return {"FINISHED"} 300 301 302class PDT_OT_PlacementPer(Operator): 303 """Use Percentage Placement.""" 304 305 bl_idname = "pdt.percent" 306 bl_label = "Percentage Mode" 307 bl_options = {"REGISTER", "UNDO"} 308 309 def execute(self, context): 310 """Manipulates Geometry, or Objects by Percentage between 2 points. 311 312 Note: 313 - Reads pg.operation from Operation Mode Selector as 'operation' 314 - Reads pg.percent, pg.extend & pg.flip_percent scene variables to: 315 -- set position of CUrsor (CU) 316 -- set position of Pivot Point (PP) 317 -- MoVe geometry/objects (MV) 318 -- Extrude Vertices (EV) 319 -- Split Edges (SE) 320 -- add a New Vertex (NV) 321 322 Invalid Options result in self.report Error. 323 324 Args: 325 context: Blender bpy.context instance. 326 327 Returns: 328 Status Set. 329 """ 330 331 pg = context.scene.pdt_pg 332 operation = pg.operation 333 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 334 335 if operation == "CU": 336 # Cursor 337 pg.command = f"cp{str(round(pg.percent, decimal_places))}" 338 elif operation == "PP": 339 # Pivot Point 340 pg.command = f"pp{str(round(pg.percent, decimal_places))}" 341 elif operation == "MV": 342 # Move Entities 343 pg.command = f"gp{str(round(pg.percent, decimal_places))}" 344 elif operation == "SE": 345 # Split Edges 346 pg.command = f"sp{str(round(pg.percent, decimal_places))}" 347 elif operation == "NV": 348 # New Vertex 349 pg.command = f"np{str(round(pg.percent, decimal_places))}" 350 elif operation == "EV": 351 # Extrude Vertices 352 pg.command = f"vp{str(round(pg.percent, decimal_places))}" 353 else: 354 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_PERCENT}" 355 self.report({"ERROR"}, error_message) 356 return {"FINISHED"} 357 358 359class PDT_OT_PlacementNormal(Operator): 360 """Use Normal, or Perpendicular Placement.""" 361 362 bl_idname = "pdt.normal" 363 bl_label = "Normal Mode" 364 bl_options = {"REGISTER", "UNDO"} 365 366 def execute(self, context): 367 """Manipulates Geometry, or Objects by Normal Intersection between 3 points. 368 369 Note: 370 - Reads pg.operation from Operation Mode Selector as 'operation' 371 - Reads pg.extend scene variable to: 372 -- set position of CUrsor (CU) 373 -- set position of Pivot Point (PP) 374 -- MoVe geometry/objects (MV) 375 -- Extrude Vertices (EV) 376 -- Split Edges (SE) 377 -- add a New Vertex (NV) 378 379 Invalid Options result in self.report Error. 380 381 Args: 382 context: Blender bpy.context instance. 383 384 Returns: 385 Status Set. 386 """ 387 388 pg = context.scene.pdt_pg 389 operation = pg.operation 390 if operation == "CU": 391 pg.command = f"cnml" 392 elif operation == "PP": 393 pg.command = f"pnml" 394 elif operation == "MV": 395 pg.command = f"gnml" 396 elif operation == "EV": 397 pg.command = f"vnml" 398 elif operation == "SE": 399 pg.command = f"snml" 400 elif operation == "NV": 401 pg.command = f"nnml" 402 else: 403 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" 404 self.report({"ERROR"}, error_message) 405 return {"FINISHED"} 406 407 408class PDT_OT_PlacementCen(Operator): 409 """Use Placement at Arc Centre.""" 410 411 bl_idname = "pdt.centre" 412 bl_label = "Centre Mode" 413 bl_options = {"REGISTER", "UNDO"} 414 415 def execute(self, context): 416 """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc. 417 418 Note: 419 - Reads pg.operation from Operation Mode Selector as 'operation' 420 -- set position of CUrsor (CU) 421 -- set position of Pivot Point (PP) 422 -- MoVe geometry/objects (MV) 423 -- Extrude Vertices (EV) 424 -- add a New vertex (NV) 425 426 Invalid Options result in self.report Error. 427 428 Args: 429 context: Blender bpy.context instance. 430 431 Returns: 432 Status Set. 433 """ 434 435 pg = context.scene.pdt_pg 436 operation = pg.operation 437 if operation == "CU": 438 pg.command = f"ccen" 439 elif operation == "PP": 440 pg.command = f"pcen" 441 elif operation == "MV": 442 pg.command = f"gcen" 443 elif operation == "EV": 444 pg.command = f"vcen" 445 elif operation == "NV": 446 pg.command = f"ncen" 447 else: 448 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" 449 self.report({"ERROR"}, error_message) 450 return {"FINISHED"} 451 452 453class PDT_OT_PlacementInt(Operator): 454 """Use Intersection, or Convergence Placement.""" 455 456 bl_idname = "pdt.intersect" 457 bl_label = "Intersect Mode" 458 bl_options = {"REGISTER", "UNDO"} 459 460 def execute(self, context): 461 """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges. 462 463 Note: 464 - Reads pg.operation from Operation Mode Selector as 'operation' 465 - Reads pg.plane scene variable and operates in Working Plane to: 466 -- set position of CUrsor (CU) 467 -- set position of Pivot Point (PP) 468 -- MoVe geometry/objects (MV) 469 -- Extrude Vertices (EV) 470 -- add a New vertex (NV) 471 472 Invalid Options result in "self.report" Error. 473 474 Args: 475 context: Blender bpy.context instance. 476 477 Returns: 478 Status Set. 479 """ 480 481 pg = context.scene.pdt_pg 482 operation = pg.operation 483 if operation == "CU": 484 pg.command = f"cint" 485 elif operation == "PP": 486 pg.command = f"pint" 487 elif operation == "MV": 488 pg.command = f"gint" 489 elif operation == "EV": 490 pg.command = f"vint" 491 elif operation == "NV": 492 pg.command = f"nint" 493 else: 494 error_message = f"{operation} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}" 495 self.report({"ERROR"}, error_message) 496 return {"FINISHED"} 497 498 499class PDT_OT_JoinVerts(Operator): 500 """Join 2 Free Vertices into an Edge.""" 501 502 bl_idname = "pdt.join" 503 bl_label = "Join 2 Vertices" 504 bl_options = {"REGISTER", "UNDO"} 505 506 @classmethod 507 def poll(cls, context): 508 ob = context.object 509 if ob is None: 510 return False 511 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"]) 512 513 def execute(self, context): 514 """Joins 2 Free Vertices that do not form part of a Face. 515 516 Note: 517 Joins two vertices that do not form part of a single face 518 It is designed to close open Edge Loops, where a face is not required 519 or to join two disconnected Edges. 520 521 Args: 522 context: Blender bpy.context instance. 523 524 Returns: 525 Status Set. 526 """ 527 528 pg = context.scene.pdt_pg 529 pg.command = f"j2v" 530 return {"FINISHED"} 531 532 533class PDT_OT_Fillet(Operator): 534 """Fillet Edges by Vertex, Set Use Verts to False for Extruded Structure.""" 535 536 bl_idname = "pdt.fillet" 537 bl_label = "Fillet" 538 bl_options = {"REGISTER", "UNDO"} 539 540 @classmethod 541 def poll(cls, context): 542 ob = context.object 543 if ob is None: 544 return False 545 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"]) 546 547 def execute(self, context): 548 """Create Fillets by Vertex or by Geometry. 549 550 Note: 551 Fillets connected edges, or connected faces 552 Uses: 553 - pg.fillet_radius ; Radius of fillet 554 - pg.fillet_segments ; Number of segments 555 - pg.fillet_profile ; Profile, values 0 to 1 556 - pg.fillet_vertices_only ; Vertices (True), or Face/Edges 557 - pg.fillet_intersect ; Intersect dges first (True), or not 558 559 Args: 560 context: Blender bpy.context instance. 561 562 Returns: 563 Status Set. 564 """ 565 566 pg = context.scene.pdt_pg 567 decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round 568 if pg.fillet_intersect: 569 pg.command = ( 570 f"fi{str(round(pg.fillet_radius, decimal_places))}" 571 f",{str(round(pg.fillet_segments, decimal_places))}" 572 f",{str(round(pg.fillet_profile, decimal_places))}" 573 ) 574 elif pg.fillet_vertices_only: 575 pg.command = ( 576 f"fv{str(round(pg.fillet_radius, decimal_places))}" 577 f",{str(round(pg.fillet_segments, decimal_places))}" 578 f",{str(round(pg.fillet_profile, decimal_places))}" 579 ) 580 else: 581 pg.command = ( 582 f"fe{str(round(pg.fillet_radius, decimal_places))}" 583 f",{str(round(pg.fillet_segments, decimal_places))}" 584 f",{str(round(pg.fillet_profile, decimal_places))}" 585 ) 586 return {"FINISHED"} 587 588 589class PDT_OT_Angle2(Operator): 590 """Measure Distance and Angle in Working Plane, Also sets Deltas.""" 591 592 bl_idname = "pdt.angle2" 593 bl_label = "Measure 2D" 594 bl_options = {"REGISTER", "UNDO"} 595 596 def execute(self, context): 597 """Measures Angle and Offsets between 2 Points in View Plane. 598 599 Note: 600 Uses 2 Selected Vertices to set pg.angle and pg.distance scene variables 601 also sets delta offset from these 2 points using standard Numpy Routines 602 Works in Edit and Oject Modes. 603 604 Args: 605 context: Blender bpy.context instance. 606 607 Returns: 608 Status Set. 609 """ 610 611 pg = context.scene.pdt_pg 612 pg.command = f"ad2" 613 return {"FINISHED"} 614 615 616class PDT_OT_Angle3(Operator): 617 """Measure Distance and Angle in 3D Space.""" 618 619 bl_idname = "pdt.angle3" 620 bl_label = "Measure 3D" 621 bl_options = {"REGISTER", "UNDO"} 622 623 def execute(self, context): 624 """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas. 625 626 Note: 627 Uses 3 Selected Vertices to set pg.angle and pg.distance scene variables 628 also sets delta offset from these 3 points using standard Numpy Routines 629 Works in Edit and Oject Modes. 630 631 Args: 632 context: Blender bpy.context instance. 633 634 Returns: 635 Status Set. 636 """ 637 638 pg = context.scene.pdt_pg 639 pg.command = f"ad3" 640 return {"FINISHED"} 641 642 643class PDT_OT_Origin(Operator): 644 """Move Object Origin to Cursor Location.""" 645 646 bl_idname = "pdt.origin" 647 bl_label = "Move Origin" 648 bl_options = {"REGISTER", "UNDO"} 649 650 def execute(self, context): 651 """Sets Object Origin in Edit Mode to Cursor Location. 652 653 Note: 654 Keeps geometry static in World Space whilst moving Object Origin 655 Requires cursor location 656 Works in Edit and Object Modes. 657 658 Args: 659 context: Blender bpy.context instance. 660 661 Returns: 662 Status Set. 663 """ 664 665 pg = context.scene.pdt_pg 666 pg.command = f"otc" 667 return {"FINISHED"} 668 669 670class PDT_OT_Taper(Operator): 671 """Taper Vertices at Angle in Chosen Axis Mode.""" 672 673 bl_idname = "pdt.taper" 674 bl_label = "Taper" 675 bl_options = {"REGISTER", "UNDO"} 676 677 @classmethod 678 def poll(cls, context): 679 ob = context.object 680 if ob is None: 681 return False 682 return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"]) 683 684 def execute(self, context): 685 """Taper Geometry along World Axes. 686 687 Note: 688 Similar to Blender Shear command except that it shears by angle rather than displacement. 689 Rotates about World Axes and displaces along World Axes, angle must not exceed +-80 degrees. 690 Rotation axis is centred on Active Vertex. 691 Works only in Edit mode. 692 693 Args: 694 context: Blender bpy.context instance. 695 696 Note: 697 Uses pg.taper & pg.angle scene variables 698 699 Returns: 700 Status Set. 701 """ 702 703 pg = context.scene.pdt_pg 704 pg.command = f"tap" 705 return {"FINISHED"} 706