1 # SPDX-FileCopyrightText: 2019-2022 Alan Odom (Clockmender)
2 # SPDX-FileCopyrightText: 2019-2022 Rune Morling (ermo)
4 # SPDX-License-Identifier: GPL-2.0-or-later
9 from bpy
.types
import Operator
10 from mathutils
import Vector
11 from .pdt_functions
import (
20 from .pdt_command_functions
import (
23 set_angle_distance_two
,
24 set_angle_distance_three
,
31 from .pdt_msg_strings
import (
52 from .pdt_bix
import add_line_to_bisection
53 from .pdt_etof
import extend_vertex
54 from .pdt_xall
import intersect_all
56 from . import pdt_exception
57 PDT_SelectionError
= pdt_exception
.SelectionError
58 PDT_InvalidVector
= pdt_exception
.InvalidVector
59 PDT_CommandFailure
= pdt_exception
.CommandFailure
60 PDT_ObjectModeError
= pdt_exception
.ObjectModeError
61 PDT_MathsError
= pdt_exception
.MathsError
62 PDT_IntersectionError
= pdt_exception
.IntersectionError
63 PDT_NoObjectError
= pdt_exception
.NoObjectError
64 PDT_FeatureError
= pdt_exception
.FeatureError
67 class PDT_OT_CommandReRun(Operator
):
68 """Repeat Current Displayed Command"""
70 bl_idname
= "pdt.command_rerun"
71 bl_label
= "Re-run Current Command"
72 bl_options
= {"REGISTER", "UNDO"}
74 def execute(self
, context
):
75 """Repeat Current Command Line Input.
78 context: Blender bpy.context instance.
83 command_run(self
, context
)
87 def command_run(self
, context
):
88 """Run Command String as input into Command Line.
91 Uses pg.command, pg.error & many other 'pg.' variables to set PDT menu items,
94 Command Format; Operation(single letter) Mode(single letter) Values(up to 3 values
97 Example; CD0.4,0.6,1.1 - Moves Cursor Delta XYZ = 0.4,0.6,1.1 from Current Position/Active
100 Example; SP35 - Splits active Edge at 35% of separation between edge's vertices
102 Valid First Letters (as 'operation' - pg.command[0])
103 C = Cursor, G = Grab(move), N = New Vertex, V = Extrude Vertices Only,
104 E = Extrude geometry, P = Move Pivot Point, D = Duplicate geometry, S = Split Edges
106 Capitals and lower case letters are both allowed
108 Valid Second Letters (as 'mode' - pg.command[1])
110 A = Absolute XYZ, D = Delta XYZ, I = Distance at Angle, P = Percent
111 X = X Delta, Y = Y, Delta Z, = Z Delta, O = Output (Maths Operation only)
112 V = Vertex Bevel, E = Edge Bevel, I = Intersect then Bevel
114 Capitals and lower case letters are both allowed
116 Valid Values (pdt_command[2:])
117 Only Integers and Floats, missing values are set to 0, appropriate length checks are
118 performed as Values is split by commas.
120 Example; CA,,3 - Cursor to Absolute, is re-interpreted as CA0,0,3
122 Exception for Maths Operation, Values section is evaluated as Maths expression
124 Example; madegrees(atan(3/4)) - sets PDT Angle to smallest angle of 3,4,5 Triangle;
128 context: Blender bpy.context instance.
134 scene
= context
.scene
136 command
= pg
.command
.strip()
138 # Check Object Type & Mode First
139 obj
= context
.view_layer
.objects
.active
140 if obj
is not None and command
[0].upper() not in {"M", "?", "HELP"}:
141 if obj
.mode
not in {"OBJECT", "EDIT"} or obj
.type not in {"MESH", "EMPTY"}:
142 pg
.error
= PDT_OBJ_MODE_ERROR
143 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
144 raise PDT_ObjectModeError
146 # Special Cases of Command.
147 if command
== "?" or command
.lower() == "help":
149 context
.window_manager
.popup_menu(pdt_help
, title
="PDT Command Line Help", icon
="INFO")
154 if command
.upper() == "J2V":
155 join_two_vertices(context
)
157 if command
.upper() == "AD2":
158 set_angle_distance_two(context
)
160 if command
.upper() == "AD3":
161 set_angle_distance_three(context
)
163 if command
.upper() == "OTC":
164 origin_to_cursor(context
)
166 if command
.upper() == "TAP":
169 if command
.upper() == "BIS":
170 add_line_to_bisection(context
)
172 if command
.upper() == "ETF":
173 extend_vertex(context
)
175 if command
.upper() == "INTALL":
176 intersect_all(context
)
178 if command
.upper()[1:] == "NML":
179 placement_normal(context
, command
.upper()[0])
181 if command
.upper()[1:] == "CEN":
182 placement_arc_centre(context
, command
.upper()[0])
184 if command
.upper()[1:] == "INT":
185 placement_intersect(context
, command
.upper()[0])
188 # Check Command Length
190 pg
.error
= PDT_ERR_CHARS_NUM
191 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
195 operation
= command
[0].upper()
196 if operation
not in {"C", "D", "E", "F", "G", "N", "M", "P", "V", "S"}:
197 pg
.error
= PDT_ERR_BADFLETTER
198 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
201 # Check Second Letter.
202 mode
= command
[1].lower()
204 (operation
== "F" and mode
not in {"v", "e", "i"})
205 or (operation
in {"D", "E"} and mode
not in {"d", "i", "n"}) #new
206 or (operation
== "M" and mode
not in {"a", "d", "i", "p", "o", "x", "y", "z"})
207 or (operation
not in {"D", "E", "F", "M"} and mode
not in {"a", "d", "i", "p", "n"}) #new
209 pg
.error
= f
"'{mode}' {PDT_ERR_NON_VALID} '{operation}'"
210 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
217 command_maths(context
, mode
, pg
, command
[2:], mode
)
219 except PDT_MathsError
:
222 # -----------------------------------------------------
223 # Not a Maths Operation, so let's parse the command line
225 pg
, values
, obj
, obj_loc
, bm
, verts
= command_parse(context
)
226 except PDT_SelectionError
:
229 # ---------------------
230 # Cursor or Pivot Point
231 if operation
in {"C", "P"}:
233 move_cursor_pivot(context
, pg
, operation
, mode
, obj
, verts
, values
)
234 except PDT_CommandFailure
:
237 # ------------------------
238 # Move Vertices or Objects
241 move_entities(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
)
242 except PDT_CommandFailure
:
249 add_new_vertex(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
)
250 except PDT_CommandFailure
:
257 split_edges(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, values
)
258 except PDT_CommandFailure
:
266 extrude_vertices(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, verts
, values
)
267 except PDT_CommandFailure
:
274 extrude_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
)
275 except PDT_CommandFailure
:
282 duplicate_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
)
283 except PDT_CommandFailure
:
290 fillet_geometry(context
, pg
, mode
, obj
, bm
, verts
, values
)
291 except PDT_CommandFailure
:
295 def pdt_help(self
, context
):
296 """Display PDT Command Line help in a pop-up.
299 context: Blender bpy.context instance
304 label
= self
.layout
.label
305 label(text
="Primary Letters (Available Secondary Letters):")
307 label(text
="C: Cursor (a, d, i, p, v)")
308 label(text
="D: Duplicate Geometry (d, i, v)")
309 label(text
="E: Extrude Geometry (d, i, v)")
310 label(text
="F: Fillet (v, e, i)")
311 label(text
="G: Grab (Move) (a, d, i, p, v)")
312 label(text
="N: New Vertex (a, d, i, p, v)")
313 label(text
="M: Maths Functions (a, d, p, o, x, y, z)")
314 label(text
="P: Pivot Point (a, d, i, p, v)")
315 label(text
="V: Extrude Vertice Only (a, d, i, p, v)")
316 label(text
="S: Split Edges (a, d, i, p)")
317 label(text
="?: Quick Help")
319 label(text
="Secondary Letters:")
321 label(text
="- General Options:")
322 label(text
="a: Absolute (Global) Coordinates e.g. 1,3,2")
323 label(text
="d: Delta (Relative) Coordinates, e.g. 0.5,0,1.2")
324 label(text
="i: Directional (Polar) Coordinates e.g. 2.6,45")
325 label(text
="p: Percent e.g. 67.5")
326 label(text
="n: Work in View Normal Axis")
328 label(text
="- Fillet Options:")
329 label(text
="v: Fillet Vertices")
330 label(text
="e: Fillet Edges")
331 label(text
="i: Fillet & Intersect 2 Disconnected Edges")
332 label(text
="- Math Options:")
333 label(text
="x, y, z: Send result to X, Y and Z input fields in PDT Design")
334 label(text
="d, a, p: Send result to Distance, Angle or Percent input field in PDT Design")
335 label(text
="o: Send Maths Calculation to Output")
337 label(text
="Note that commands are case-insensitive: ED = Ed = eD = ed")
339 label(text
="Examples:")
341 label(text
="ed0.5,,0.6")
342 label(text
="'- Extrude Geometry Delta 0.5 in X, 0 in Y, 0.6 in Z")
344 label(text
="fe0.1,4,0.5")
345 label(text
="'- Fillet Edges")
346 label(text
="'- Radius: 0.1 (float) -- the radius (or offset) of the bevel/fillet")
347 label(text
="'- Segments: 4 (int) -- choosing an even amount of segments gives better geometry")
348 label(text
="'- Profile: 0.5 (float[0.0;1.0]) -- 0.5 (default) yields a circular, convex shape")
350 label(text
="More Information at:")
351 label(text
="https://github.com/Clockmender/Precision-Drawing-Tools/wiki")
354 def command_maths(context
, mode
, pg
, expression
, output_target
):
355 """Evaluates Maths Input.
358 context: Blender bpy.context instance.
359 mode: The Operation Mode, e.g. a for Absolute
360 pg: PDT Parameters Group - our variables
361 expression: The Maths component of the command input e.g. sqrt(56)
362 output_target: The output variable box on the UI
369 namespace
.update(vars(math
))
371 maths_result
= eval(expression
, namespace
, namespace
)
373 pg
.error
= PDT_ERR_BADMATHS
374 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
377 decimal_places
= context
.preferences
.addons
[__package__
].preferences
.pdt_input_round
378 if output_target
== "x":
379 pg
.cartesian_coords
.x
= round(maths_result
, decimal_places
)
380 elif output_target
== "y":
381 pg
.cartesian_coords
.y
= round(maths_result
, decimal_places
)
382 elif output_target
== "z":
383 pg
.cartesian_coords
.z
= round(maths_result
, decimal_places
)
384 elif output_target
== "d":
385 pg
.distance
= round(maths_result
, decimal_places
)
386 elif output_target
== "a":
387 pg
.angle
= round(maths_result
, decimal_places
)
388 elif output_target
== "p":
389 pg
.percent
= round(maths_result
, decimal_places
)
392 pg
.maths_output
= round(maths_result
, decimal_places
)
395 def command_parse(context
):
396 """Parse Command Input.
399 context: Blender bpy.context instance.
402 pg: PDT Parameters Group - our variables
403 values_out: The Output Values as a list of numbers
404 obj: The Active Object
405 obj_loc: The object's location in 3D space
406 bm: The object's Bmesh
407 verts: The object's selected vertices, or selected history vertices.
409 scene
= context
.scene
411 command
= pg
.command
.strip()
412 operation
= command
[0].upper()
413 mode
= command
[1].lower()
414 values
= command
[2:].split(",")
416 obj
= context
.view_layer
.objects
.active
428 values
= [0.0, values
[0], 0.0]
429 elif pg
.plane
== "YZ":
430 values
= [values
[0], 0.0, 0.0]
431 elif pg
.plane
== "XY":
432 values
= [0.0, 0.0, values
[0]]
435 values
= [0.0, 0.0, values
[0][1:]]
437 values
= [0.0, 0.0, f
"-{values[0]}"]
438 # Apply System Rounding
439 decimal_places
= context
.preferences
.addons
[__package__
].preferences
.pdt_input_round
440 values_out
= [str(round(float(v
), decimal_places
)) for v
in values
]
442 obj_loc
= Vector((0,0,0))
445 if mode_sel
== 'REL' and operation
not in {"C", "P"}:
449 if mode
== "a" and operation
not in {"C", "P"}:
450 # Place new Vertex, or Extrude Vertices by Absolute Coords.
451 if mode_sel
== 'REL':
455 if obj
.mode
== "EDIT":
456 bm
= bmesh
.from_edit_mesh(obj
.data
)
457 obj_loc
= obj
.matrix_world
.decompose()[0]
461 pg
.error
= PDT_OBJ_MODE_ERROR
462 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
463 raise PDT_ObjectModeError
465 pg
.error
= PDT_ERR_NO_ACT_OBJ
466 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
467 raise PDT_NoObjectError
469 if mode_sel
== 'SEL' and mode
not in {"a"}:
470 # All other options except Cursor or Pivot by Absolute
471 # These options require no object, etc.
472 bm
, good
= obj_check(obj
, scene
, operation
)
473 if good
and obj
.mode
== 'EDIT':
474 obj_loc
= obj
.matrix_world
.decompose()[0]
475 if len(bm
.select_history
) == 0 or operation
== "G":
476 verts
= [v
for v
in bm
.verts
if v
.select
]
478 pg
.error
= PDT_ERR_NO_SEL_GEOM
479 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
480 raise PDT_SelectionError
482 verts
= bm
.select_history
484 debug(f
"command: {operation}{mode}{values_out}")
485 debug(f
"obj: {obj}, bm: {bm}, obj_loc: {obj_loc}")
487 return pg
, values_out
, obj
, obj_loc
, bm
, verts
490 def move_cursor_pivot(context
, pg
, operation
, mode
, obj
, verts
, values
):
491 """Moves Cursor & Pivot Point.
494 context: Blender bpy.context instance.
495 pg: PDT Parameters Group - our variables
496 operation: The Operation e.g. Create New Vertex
497 mode: The Operation Mode, e.g. a for Absolute
498 obj: The Active Object
499 verts: The object's selected vertices, or selected history vertices
500 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
506 # Absolute/Global Coordinates, or Delta/Relative Coordinates
507 if mode
in {"a", "d", "n"}:
509 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
511 raise PDT_InvalidVector
512 # Direction/Polar Coordinates
515 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
517 raise PDT_InvalidVector
522 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
524 raise PDT_InvalidVector
526 scene
= context
.scene
528 obj_loc
= Vector((0,0,0))
530 obj_loc
= obj
.matrix_world
.decompose()[0]
534 scene
.cursor
.location
= vector_delta
535 elif operation
== "P":
536 pg
.pivot_loc
= vector_delta
537 elif mode
in {"d", "i", "n"}:
538 if pg
.plane
== "LO" and mode
in {"d", "n"}:
539 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
540 elif pg
.plane
== "LO" and mode
== "i":
541 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
542 if mode_sel
== "REL":
544 scene
.cursor
.location
= scene
.cursor
.location
+ vector_delta
546 pg
.pivot_loc
= pg
.pivot_loc
+ vector_delta
547 elif mode_sel
== "SEL":
548 if obj
.mode
== "EDIT":
550 scene
.cursor
.location
= verts
[-1].co
+ obj_loc
+ vector_delta
552 pg
.pivot_loc
= verts
[-1].co
+ obj_loc
+ vector_delta
553 if obj
.mode
== "OBJECT":
555 scene
.cursor
.location
= obj_loc
+ vector_delta
557 pg
.pivot_loc
= obj_loc
+ vector_delta
560 if obj
.mode
== "EDIT":
562 scene
.cursor
.location
= obj_loc
+ vector_delta
564 pg
.pivot_loc
= obj_loc
+ vector_delta
565 if obj
.mode
== "OBJECT":
567 scene
.cursor
.location
= vector_delta
569 pg
.pivot_loc
= vector_delta
572 def move_entities(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
):
576 context: Blender bpy.context instance.
577 pg: PDT Parameters Group - our variables
578 operation: The Operation e.g. Create New Vertex
579 mode: The Operation Mode, e.g. a for Absolute
580 obj: The Active Object
581 bm: The object's Bmesh
582 verts: The object's selected vertices, or selected history vertices
583 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
589 obj_loc
= obj
.matrix_world
.decompose()[0]
591 # Absolute/Global Coordinates
594 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
596 raise PDT_InvalidVector
597 if obj
.mode
== "EDIT":
598 for v
in [v
for v
in bm
.verts
if v
.select
]:
599 v
.co
= vector_delta
- obj_loc
600 bmesh
.ops
.remove_doubles(
601 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001
603 if obj
.mode
== "OBJECT":
604 for ob
in context
.view_layer
.objects
.selected
:
605 ob
.location
= vector_delta
607 elif mode
in {"d", "i", "n"}:
608 if mode
in {"d", "n"}:
609 # Delta/Relative Coordinates
611 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
613 raise PDT_InvalidVector
615 # Direction/Polar Coordinates
617 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
619 raise PDT_InvalidVector
621 if pg
.plane
== "LO" and mode
in {"d", "n"}:
622 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
623 elif pg
.plane
== "LO" and mode
== "i":
624 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
626 if obj
.mode
== "EDIT":
628 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], vec
=vector_delta
630 if obj
.mode
== "OBJECT":
631 for ob
in context
.view_layer
.objects
.selected
:
632 ob
.location
= obj_loc
+ vector_delta
633 # Percent Options Only Other Choice
636 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
638 raise PDT_InvalidVector
639 if obj
.mode
== 'EDIT':
640 verts
[-1].co
= vector_delta
641 if obj
.mode
== "OBJECT":
642 obj
.location
= vector_delta
643 if obj
.mode
== 'EDIT':
644 bmesh
.update_edit_mesh(obj
.data
)
645 bm
.select_history
.clear()
648 def add_new_vertex(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
):
652 context: Blender bpy.context instance.
653 pg, operation, mode, obj, bm, verts, values
659 obj_loc
= obj
.matrix_world
.decompose()[0]
661 if not obj
.mode
== "EDIT":
662 pg
.error
= PDT_ERR_ADDVEDIT
663 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
664 raise PDT_SelectionError
665 if mode
not in {"a"}:
666 if not isinstance(verts
[0], bmesh
.types
.BMVert
):
667 pg
.error
= PDT_ERR_VERT_MODE
668 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
669 raise PDT_FeatureError
670 # Absolute/Global Coordinates
673 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
675 raise PDT_InvalidVector
676 new_vertex
= bm
.verts
.new(vector_delta
- obj_loc
)
677 # Delta/Relative Coordinates
678 elif mode
in {"d", "n"}:
680 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
682 raise PDT_InvalidVector
684 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
685 new_vertex
= bm
.verts
.new(verts
[-1].co
+ vector_delta
)
686 # Direction/Polar Coordinates
689 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
691 raise PDT_InvalidVector
693 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
694 new_vertex
= bm
.verts
.new(verts
[-1].co
+ vector_delta
)
695 # Percent Options Only Other Choice
698 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
700 raise PDT_InvalidVector
701 new_vertex
= bm
.verts
.new(vector_delta
)
703 for v
in [v
for v
in bm
.verts
if v
.select
]:
705 new_vertex
.select_set(True)
706 bmesh
.update_edit_mesh(obj
.data
)
707 bm
.select_history
.clear()
710 def split_edges(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, values
):
714 context: Blender bpy.context instance.
715 pg: PDT Parameters Group - our variables
716 operation: The Operation e.g. Create New Vertex
717 mode: The Operation Mode, e.g. a for Absolute
718 obj: The Active Object
719 obj_loc: The object's location in 3D space
720 bm: The object's Bmesh
721 verts: The object's selected vertices, or selected history vertices
722 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
728 if not obj
.mode
== "EDIT":
729 pg
.error
= PDT_ERR_SPLITEDIT
730 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
732 # Absolute/Global Coordinates
735 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
737 raise PDT_InvalidVector
738 edges
= [e
for e
in bm
.edges
if e
.select
]
740 pg
.error
= f
"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
741 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
743 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
744 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
745 new_vertex
= new_verts
[0]
746 new_vertex
.co
= vector_delta
- obj_loc
747 # Delta/Relative Coordinates
750 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
752 raise PDT_InvalidVector
753 edges
= [e
for e
in bm
.edges
if e
.select
]
754 faces
= [f
for f
in bm
.faces
if f
.select
]
756 pg
.error
= PDT_ERR_FACE_SEL
757 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
760 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
761 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
763 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
764 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
765 bmesh
.ops
.translate(bm
, verts
=new_verts
, vec
=vector_delta
)
766 # Directional/Polar Coordinates
769 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
771 raise PDT_InvalidVector
772 edges
= [e
for e
in bm
.edges
if e
.select
]
773 faces
= [f
for f
in bm
.faces
if f
.select
]
775 pg
.error
= PDT_ERR_FACE_SEL
776 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
779 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
780 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
782 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
783 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
784 bmesh
.ops
.translate(bm
, verts
=new_verts
, vec
=vector_delta
)
788 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
790 raise PDT_InvalidVector
791 edges
= [e
for e
in bm
.edges
if e
.select
]
792 faces
= [f
for f
in bm
.faces
if f
.select
]
794 pg
.error
= PDT_ERR_FACE_SEL
795 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
798 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
799 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
801 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
802 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
803 new_vertex
= new_verts
[0]
804 new_vertex
.co
= vector_delta
806 for v
in [v
for v
in bm
.verts
if v
.select
]:
810 bmesh
.update_edit_mesh(obj
.data
)
811 bm
.select_history
.clear()
814 def extrude_vertices(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, verts
, values
):
818 context: Blender bpy.context instance.
819 pg: PDT Parameters Group - our variables
820 operation: The Operation e.g. Create New Vertex
821 mode: The Operation Mode, e.g. a for Absolute
822 obj: The Active Object
823 obj_loc: The object's location in 3D space
824 bm: The object's Bmesh
825 verts: The object's selected vertices, or selected history vertices
826 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
832 if not obj
.mode
== "EDIT":
833 pg
.error
= PDT_ERR_EXTEDIT
834 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
836 # Absolute/Global Coordinates
839 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
841 raise PDT_InvalidVector
842 new_vertex
= bm
.verts
.new(vector_delta
- obj_loc
)
843 verts
= [v
for v
in bm
.verts
if v
.select
].copy()
845 bm
.edges
.new([v
, new_vertex
])
847 new_vertex
.select_set(True)
848 bmesh
.ops
.remove_doubles(
849 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001
851 # Delta/Relative Coordinates
852 elif mode
in {"d", "n"}:
854 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
856 raise PDT_InvalidVector
858 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
860 new_vertex
= bm
.verts
.new(v
.co
)
861 new_vertex
.co
= new_vertex
.co
+ vector_delta
862 bm
.edges
.new([v
, new_vertex
])
864 new_vertex
.select_set(True)
865 # Direction/Polar Coordinates
868 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
870 raise PDT_InvalidVector
872 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
874 new_vertex
= bm
.verts
.new(v
.co
)
875 new_vertex
.co
= new_vertex
.co
+ vector_delta
876 bm
.edges
.new([v
, new_vertex
])
878 new_vertex
.select_set(True)
881 extend_all
= pg
.extend
883 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
885 raise PDT_InvalidVector
886 verts
= [v
for v
in bm
.verts
if v
.select
].copy()
887 new_vertex
= bm
.verts
.new(vector_delta
)
889 for v
in [v
for v
in bm
.verts
if v
.select
]:
890 bm
.edges
.new([v
, new_vertex
])
893 bm
.edges
.new([verts
[-1], new_vertex
])
894 new_vertex
.select_set(True)
896 bmesh
.update_edit_mesh(obj
.data
)
899 def extrude_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
):
903 context: Blender bpy.context instance.
904 pg: PDT Parameters Group - our variables
905 operation: The Operation e.g. Create New Vertex
906 mode: The Operation Mode, e.g. a for Absolute
907 obj: The Active Object
908 bm: The object's Bmesh
909 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
915 if not obj
.mode
== "EDIT":
916 pg
.error
= PDT_ERR_EXTEDIT
917 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
919 # Delta/Relative Coordinates
920 if mode
in {"d", "n"}:
922 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
924 raise PDT_InvalidVector
925 # Direction/Polar Coordinates
928 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
930 raise PDT_InvalidVector
932 ret
= bmesh
.ops
.extrude_face_region(
935 [f
for f
in bm
.faces
if f
.select
]
936 + [e
for e
in bm
.edges
if e
.select
]
937 + [v
for v
in bm
.verts
if v
.select
]
939 use_select_history
=True,
941 geom_extr
= ret
["geom"]
942 verts_extr
= [v
for v
in geom_extr
if isinstance(v
, bmesh
.types
.BMVert
)]
943 edges_extr
= [e
for e
in geom_extr
if isinstance(e
, bmesh
.types
.BMEdge
)]
944 faces_extr
= [f
for f
in geom_extr
if isinstance(f
, bmesh
.types
.BMFace
)]
947 if pg
.plane
== "LO" and mode
in {"d", "n"}:
948 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
949 elif pg
.plane
== "LO" and mode
== "i":
950 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
952 bmesh
.ops
.translate(bm
, verts
=verts_extr
, vec
=vector_delta
)
953 update_sel(bm
, verts_extr
, edges_extr
, faces_extr
)
954 bmesh
.update_edit_mesh(obj
.data
)
955 bm
.select_history
.clear()
958 def duplicate_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
):
959 """Duplicate Geometry.
962 context: Blender bpy.context instance.
963 pg: PDT Parameters Group - our variables
964 operation: The Operation e.g. Create New Vertex
965 mode: The Operation Mode, e.g. a for Absolute
966 obj: The Active Object
967 bm: The object's Bmesh
968 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
974 if not obj
.mode
== "EDIT":
975 pg
.error
= PDT_ERR_DUPEDIT
976 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
978 # Delta/Relative Coordinates
979 if mode
in {"d", "n"}:
981 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
983 raise PDT_InvalidVector
984 # Direction/Polar Coordinates
987 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
989 raise PDT_InvalidVector
991 ret
= bmesh
.ops
.duplicate(
994 [f
for f
in bm
.faces
if f
.select
]
995 + [e
for e
in bm
.edges
if e
.select
]
996 + [v
for v
in bm
.verts
if v
.select
]
998 use_select_history
=True,
1000 geom_dupe
= ret
["geom"]
1001 verts_dupe
= [v
for v
in geom_dupe
if isinstance(v
, bmesh
.types
.BMVert
)]
1002 edges_dupe
= [e
for e
in geom_dupe
if isinstance(e
, bmesh
.types
.BMEdge
)]
1003 faces_dupe
= [f
for f
in geom_dupe
if isinstance(f
, bmesh
.types
.BMFace
)]
1006 if pg
.plane
== "LO" and mode
in {"d", "n"}:
1007 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
1008 elif pg
.plane
== "LO" and mode
== "i":
1009 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
1011 bmesh
.ops
.translate(bm
, verts
=verts_dupe
, vec
=vector_delta
)
1012 update_sel(bm
, verts_dupe
, edges_dupe
, faces_dupe
)
1013 bmesh
.update_edit_mesh(obj
.data
)
1016 def fillet_geometry(context
, pg
, mode
, obj
, bm
, verts
, values
):
1020 context: Blender bpy.context instance.
1021 pg: PDT Parameters Group - our variables
1022 operation: The Operation e.g. Create New Vertex
1023 mode: The Operation Mode, e.g. a for Absolute
1024 obj: The Active Object
1025 bm: The object's Bmesh
1026 verts: The object's selected vertices, or selected history vertices
1027 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
1033 if not obj
.mode
== "EDIT":
1034 pg
.error
= PDT_ERR_FILEDIT
1035 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1037 if mode
in {"i", "v"}:
1042 # Note that passing an empty parameter results in that parameter being seen as "0"
1043 # _offset <= 0 is ignored since a bevel/fillet radius must be > 0 to make sense
1044 _offset
= float(values
[0])
1045 # Force _segments to an integer (bug fix T95442)
1046 _segments
= int(float(values
[1]))
1048 _segments
= 1 # This is a single, flat segment (ignores profile)
1049 _profile
= float(values
[2])
1050 if _profile
< 0.0 or _profile
> 1.0:
1051 _profile
= 0.5 # This is a circular profile
1053 # Fillet & Intersect Two Edges
1054 # Always use Current Selection
1055 verts
= [v
for v
in bm
.verts
if v
.select
]
1056 edges
= [e
for e
in bm
.edges
if e
.select
]
1057 if len(edges
) == 2 and len(verts
) == 4:
1059 v_active
= edges
[0].verts
[0]
1060 v_other
= edges
[0].verts
[1]
1061 v_last
= edges
[1].verts
[0]
1062 v_first
= edges
[1].verts
[1]
1063 vector_delta
, done
= intersection(v_active
.co
,
1070 pg
.error
= f
"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}"
1071 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1072 raise PDT_IntersectionError
1073 if (v_active
.co
- vector_delta
).length
< (v_other
.co
- vector_delta
).length
:
1074 v_active
.co
= vector_delta
1075 v_other
.select_set(False)
1077 v_other
.co
= vector_delta
1078 v_active
.select_set(False)
1079 if (v_last
.co
- vector_delta
).length
< (v_first
.co
- vector_delta
).length
:
1080 v_last
.co
= vector_delta
1081 v_first
.select_set(False)
1083 v_first
.co
= vector_delta
1084 v_last
.select_set(False)
1085 bmesh
.ops
.remove_doubles(bm
, verts
=bm
.verts
, dist
=0.0001)
1087 pg
.error
= f
"{PDT_ERR_SEL_4_VERTS} {len(verts)} Vert(s), {len(edges)} Edge(s))"
1088 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1089 raise PDT_SelectionError
1092 offset_type
="OFFSET",