1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # -----------------------------------------------------------------------
4 # Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019
5 # -----------------------------------------------------------------------
10 from bpy
.types
import Operator
11 from mathutils
import Vector
12 from .pdt_functions
import (
21 from .pdt_command_functions
import (
24 set_angle_distance_two
,
25 set_angle_distance_three
,
32 from .pdt_msg_strings
import (
53 from .pdt_bix
import add_line_to_bisection
54 from .pdt_etof
import extend_vertex
55 from .pdt_xall
import intersect_all
57 from . import pdt_exception
58 PDT_SelectionError
= pdt_exception
.SelectionError
59 PDT_InvalidVector
= pdt_exception
.InvalidVector
60 PDT_CommandFailure
= pdt_exception
.CommandFailure
61 PDT_ObjectModeError
= pdt_exception
.ObjectModeError
62 PDT_MathsError
= pdt_exception
.MathsError
63 PDT_IntersectionError
= pdt_exception
.IntersectionError
64 PDT_NoObjectError
= pdt_exception
.NoObjectError
65 PDT_FeatureError
= pdt_exception
.FeatureError
68 class PDT_OT_CommandReRun(Operator
):
69 """Repeat Current Displayed Command"""
71 bl_idname
= "pdt.command_rerun"
72 bl_label
= "Re-run Current Command"
73 bl_options
= {"REGISTER", "UNDO"}
75 def execute(self
, context
):
76 """Repeat Current Command Line Input.
79 context: Blender bpy.context instance.
84 command_run(self
, context
)
88 def command_run(self
, context
):
89 """Run Command String as input into Command Line.
92 Uses pg.command, pg.error & many other 'pg.' variables to set PDT menu items,
95 Command Format; Operation(single letter) Mode(single letter) Values(up to 3 values
98 Example; CD0.4,0.6,1.1 - Moves Cursor Delta XYZ = 0.4,0.6,1.1 from Current Position/Active
101 Example; SP35 - Splits active Edge at 35% of separation between edge's vertices
103 Valid First Letters (as 'operation' - pg.command[0])
104 C = Cursor, G = Grab(move), N = New Vertex, V = Extrude Vertices Only,
105 E = Extrude geometry, P = Move Pivot Point, D = Duplicate geometry, S = Split Edges
107 Capitals and lower case letters are both allowed
109 Valid Second Letters (as 'mode' - pg.command[1])
111 A = Absolute XYZ, D = Delta XYZ, I = Distance at Angle, P = Percent
112 X = X Delta, Y = Y, Delta Z, = Z Delta, O = Output (Maths Operation only)
113 V = Vertex Bevel, E = Edge Bevel, I = Intersect then Bevel
115 Capitals and lower case letters are both allowed
117 Valid Values (pdt_command[2:])
118 Only Integers and Floats, missing values are set to 0, appropriate length checks are
119 performed as Values is split by commas.
121 Example; CA,,3 - Cursor to Absolute, is re-interpreted as CA0,0,3
123 Exception for Maths Operation, Values section is evaluated as Maths expression
125 Example; madegrees(atan(3/4)) - sets PDT Angle to smallest angle of 3,4,5 Triangle;
129 context: Blender bpy.context instance.
135 scene
= context
.scene
137 command
= pg
.command
.strip()
139 # Check Object Type & Mode First
140 obj
= context
.view_layer
.objects
.active
141 if obj
is not None and command
[0].upper() not in {"M", "?", "HELP"}:
142 if obj
.mode
not in {"OBJECT", "EDIT"} or obj
.type not in {"MESH", "EMPTY"}:
143 pg
.error
= PDT_OBJ_MODE_ERROR
144 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
145 raise PDT_ObjectModeError
147 # Special Cases of Command.
148 if command
== "?" or command
.lower() == "help":
150 context
.window_manager
.popup_menu(pdt_help
, title
="PDT Command Line Help", icon
="INFO")
155 if command
.upper() == "J2V":
156 join_two_vertices(context
)
158 if command
.upper() == "AD2":
159 set_angle_distance_two(context
)
161 if command
.upper() == "AD3":
162 set_angle_distance_three(context
)
164 if command
.upper() == "OTC":
165 origin_to_cursor(context
)
167 if command
.upper() == "TAP":
170 if command
.upper() == "BIS":
171 add_line_to_bisection(context
)
173 if command
.upper() == "ETF":
174 extend_vertex(context
)
176 if command
.upper() == "INTALL":
177 intersect_all(context
)
179 if command
.upper()[1:] == "NML":
180 placement_normal(context
, command
.upper()[0])
182 if command
.upper()[1:] == "CEN":
183 placement_arc_centre(context
, command
.upper()[0])
185 if command
.upper()[1:] == "INT":
186 placement_intersect(context
, command
.upper()[0])
189 # Check Command Length
191 pg
.error
= PDT_ERR_CHARS_NUM
192 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
196 operation
= command
[0].upper()
197 if operation
not in {"C", "D", "E", "F", "G", "N", "M", "P", "V", "S"}:
198 pg
.error
= PDT_ERR_BADFLETTER
199 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
202 # Check Second Letter.
203 mode
= command
[1].lower()
205 (operation
== "F" and mode
not in {"v", "e", "i"})
206 or (operation
in {"D", "E"} and mode
not in {"d", "i", "n"}) #new
207 or (operation
== "M" and mode
not in {"a", "d", "i", "p", "o", "x", "y", "z"})
208 or (operation
not in {"D", "E", "F", "M"} and mode
not in {"a", "d", "i", "p", "n"}) #new
210 pg
.error
= f
"'{mode}' {PDT_ERR_NON_VALID} '{operation}'"
211 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
218 command_maths(context
, mode
, pg
, command
[2:], mode
)
220 except PDT_MathsError
:
223 # -----------------------------------------------------
224 # Not a Maths Operation, so let's parse the command line
226 pg
, values
, obj
, obj_loc
, bm
, verts
= command_parse(context
)
227 except PDT_SelectionError
:
230 # ---------------------
231 # Cursor or Pivot Point
232 if operation
in {"C", "P"}:
234 move_cursor_pivot(context
, pg
, operation
, mode
, obj
, verts
, values
)
235 except PDT_CommandFailure
:
238 # ------------------------
239 # Move Vertices or Objects
242 move_entities(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
)
243 except PDT_CommandFailure
:
250 add_new_vertex(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
)
251 except PDT_CommandFailure
:
258 split_edges(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, values
)
259 except PDT_CommandFailure
:
267 extrude_vertices(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, verts
, values
)
268 except PDT_CommandFailure
:
275 extrude_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
)
276 except PDT_CommandFailure
:
283 duplicate_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
)
284 except PDT_CommandFailure
:
291 fillet_geometry(context
, pg
, mode
, obj
, bm
, verts
, values
)
292 except PDT_CommandFailure
:
296 def pdt_help(self
, context
):
297 """Display PDT Command Line help in a pop-up.
300 context: Blender bpy.context instance
305 label
= self
.layout
.label
306 label(text
="Primary Letters (Available Secondary Letters):")
308 label(text
="C: Cursor (a, d, i, p, v)")
309 label(text
="D: Duplicate Geometry (d, i, v)")
310 label(text
="E: Extrude Geometry (d, i, v)")
311 label(text
="F: Fillet (v, e, i)")
312 label(text
="G: Grab (Move) (a, d, i, p, v)")
313 label(text
="N: New Vertex (a, d, i, p, v)")
314 label(text
="M: Maths Functions (a, d, p, o, x, y, z)")
315 label(text
="P: Pivot Point (a, d, i, p, v)")
316 label(text
="V: Extrude Vertice Only (a, d, i, p, v)")
317 label(text
="S: Split Edges (a, d, i, p)")
318 label(text
="?: Quick Help")
320 label(text
="Secondary Letters:")
322 label(text
="- General Options:")
323 label(text
="a: Absolute (Global) Coordinates e.g. 1,3,2")
324 label(text
="d: Delta (Relative) Coordinates, e.g. 0.5,0,1.2")
325 label(text
="i: Directional (Polar) Coordinates e.g. 2.6,45")
326 label(text
="p: Percent e.g. 67.5")
327 label(text
="n: Work in View Normal Axis")
329 label(text
="- Fillet Options:")
330 label(text
="v: Fillet Vertices")
331 label(text
="e: Fillet Edges")
332 label(text
="i: Fillet & Intersect 2 Disconnected Edges")
333 label(text
="- Math Options:")
334 label(text
="x, y, z: Send result to X, Y and Z input fields in PDT Design")
335 label(text
="d, a, p: Send result to Distance, Angle or Percent input field in PDT Design")
336 label(text
="o: Send Maths Calculation to Output")
338 label(text
="Note that commands are case-insensitive: ED = Ed = eD = ed")
340 label(text
="Examples:")
342 label(text
="ed0.5,,0.6")
343 label(text
="'- Extrude Geometry Delta 0.5 in X, 0 in Y, 0.6 in Z")
345 label(text
="fe0.1,4,0.5")
346 label(text
="'- Fillet Edges")
347 label(text
="'- Radius: 0.1 (float) -- the radius (or offset) of the bevel/fillet")
348 label(text
="'- Segments: 4 (int) -- choosing an even amount of segments gives better geometry")
349 label(text
="'- Profile: 0.5 (float[0.0;1.0]) -- 0.5 (default) yields a circular, convex shape")
351 label(text
="More Information at:")
352 label(text
="https://github.com/Clockmender/Precision-Drawing-Tools/wiki")
355 def command_maths(context
, mode
, pg
, expression
, output_target
):
356 """Evaluates Maths Input.
359 context: Blender bpy.context instance.
360 mode: The Operation Mode, e.g. a for Absolute
361 pg: PDT Parameters Group - our variables
362 expression: The Maths component of the command input e.g. sqrt(56)
363 output_target: The output variable box on the UI
370 namespace
.update(vars(math
))
372 maths_result
= eval(expression
, namespace
, namespace
)
374 pg
.error
= PDT_ERR_BADMATHS
375 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
378 decimal_places
= context
.preferences
.addons
[__package__
].preferences
.pdt_input_round
379 if output_target
== "x":
380 pg
.cartesian_coords
.x
= round(maths_result
, decimal_places
)
381 elif output_target
== "y":
382 pg
.cartesian_coords
.y
= round(maths_result
, decimal_places
)
383 elif output_target
== "z":
384 pg
.cartesian_coords
.z
= round(maths_result
, decimal_places
)
385 elif output_target
== "d":
386 pg
.distance
= round(maths_result
, decimal_places
)
387 elif output_target
== "a":
388 pg
.angle
= round(maths_result
, decimal_places
)
389 elif output_target
== "p":
390 pg
.percent
= round(maths_result
, decimal_places
)
393 pg
.maths_output
= round(maths_result
, decimal_places
)
396 def command_parse(context
):
397 """Parse Command Input.
400 context: Blender bpy.context instance.
403 pg: PDT Parameters Group - our variables
404 values_out: The Output Values as a list of numbers
405 obj: The Active Object
406 obj_loc: The object's location in 3D space
407 bm: The object's Bmesh
408 verts: The object's selected vertices, or selected history vertices.
410 scene
= context
.scene
412 command
= pg
.command
.strip()
413 operation
= command
[0].upper()
414 mode
= command
[1].lower()
415 values
= command
[2:].split(",")
417 obj
= context
.view_layer
.objects
.active
429 values
= [0.0, values
[0], 0.0]
430 elif pg
.plane
== "YZ":
431 values
= [values
[0], 0.0, 0.0]
432 elif pg
.plane
== "XY":
433 values
= [0.0, 0.0, values
[0]]
436 values
= [0.0, 0.0, values
[0][1:]]
438 values
= [0.0, 0.0, f
"-{values[0]}"]
439 # Apply System Rounding
440 decimal_places
= context
.preferences
.addons
[__package__
].preferences
.pdt_input_round
441 values_out
= [str(round(float(v
), decimal_places
)) for v
in values
]
443 obj_loc
= Vector((0,0,0))
446 if mode_sel
== 'REL' and operation
not in {"C", "P"}:
450 if mode
== "a" and operation
not in {"C", "P"}:
451 # Place new Vertex, or Extrude Vertices by Absolute Coords.
452 if mode_sel
== 'REL':
456 if obj
.mode
== "EDIT":
457 bm
= bmesh
.from_edit_mesh(obj
.data
)
458 obj_loc
= obj
.matrix_world
.decompose()[0]
462 pg
.error
= PDT_OBJ_MODE_ERROR
463 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
464 raise PDT_ObjectModeError
466 pg
.error
= PDT_ERR_NO_ACT_OBJ
467 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
468 raise PDT_NoObjectError
470 if mode_sel
== 'SEL' and mode
not in {"a"}:
471 # All other options except Cursor or Pivot by Absolute
472 # These options require no object, etc.
473 bm
, good
= obj_check(obj
, scene
, operation
)
474 if good
and obj
.mode
== 'EDIT':
475 obj_loc
= obj
.matrix_world
.decompose()[0]
476 if len(bm
.select_history
) == 0 or operation
== "G":
477 verts
= [v
for v
in bm
.verts
if v
.select
]
479 pg
.error
= PDT_ERR_NO_SEL_GEOM
480 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
481 raise PDT_SelectionError
483 verts
= bm
.select_history
485 debug(f
"command: {operation}{mode}{values_out}")
486 debug(f
"obj: {obj}, bm: {bm}, obj_loc: {obj_loc}")
488 return pg
, values_out
, obj
, obj_loc
, bm
, verts
491 def move_cursor_pivot(context
, pg
, operation
, mode
, obj
, verts
, values
):
492 """Moves Cursor & Pivot Point.
495 context: Blender bpy.context instance.
496 pg: PDT Parameters Group - our variables
497 operation: The Operation e.g. Create New Vertex
498 mode: The Operation Mode, e.g. a for Absolute
499 obj: The Active Object
500 verts: The object's selected vertices, or selected history vertices
501 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
507 # Absolute/Global Coordinates, or Delta/Relative Coordinates
508 if mode
in {"a", "d", "n"}:
510 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
512 raise PDT_InvalidVector
513 # Direction/Polar Coordinates
516 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
518 raise PDT_InvalidVector
523 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
525 raise PDT_InvalidVector
527 scene
= context
.scene
529 obj_loc
= Vector((0,0,0))
531 obj_loc
= obj
.matrix_world
.decompose()[0]
535 scene
.cursor
.location
= vector_delta
536 elif operation
== "P":
537 pg
.pivot_loc
= vector_delta
538 elif mode
in {"d", "i", "n"}:
539 if pg
.plane
== "LO" and mode
in {"d", "n"}:
540 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
541 elif pg
.plane
== "LO" and mode
== "i":
542 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
543 if mode_sel
== "REL":
545 scene
.cursor
.location
= scene
.cursor
.location
+ vector_delta
547 pg
.pivot_loc
= pg
.pivot_loc
+ vector_delta
548 elif mode_sel
== "SEL":
549 if obj
.mode
== "EDIT":
551 scene
.cursor
.location
= verts
[-1].co
+ obj_loc
+ vector_delta
553 pg
.pivot_loc
= verts
[-1].co
+ obj_loc
+ vector_delta
554 if obj
.mode
== "OBJECT":
556 scene
.cursor
.location
= obj_loc
+ vector_delta
558 pg
.pivot_loc
= obj_loc
+ vector_delta
561 if obj
.mode
== "EDIT":
563 scene
.cursor
.location
= obj_loc
+ vector_delta
565 pg
.pivot_loc
= obj_loc
+ vector_delta
566 if obj
.mode
== "OBJECT":
568 scene
.cursor
.location
= vector_delta
570 pg
.pivot_loc
= vector_delta
573 def move_entities(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
):
577 context: Blender bpy.context instance.
578 pg: PDT Parameters Group - our variables
579 operation: The Operation e.g. Create New Vertex
580 mode: The Operation Mode, e.g. a for Absolute
581 obj: The Active Object
582 bm: The object's Bmesh
583 verts: The object's selected vertices, or selected history vertices
584 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
590 obj_loc
= obj
.matrix_world
.decompose()[0]
592 # Absolute/Global Coordinates
595 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
597 raise PDT_InvalidVector
598 if obj
.mode
== "EDIT":
599 for v
in [v
for v
in bm
.verts
if v
.select
]:
600 v
.co
= vector_delta
- obj_loc
601 bmesh
.ops
.remove_doubles(
602 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001
604 if obj
.mode
== "OBJECT":
605 for ob
in context
.view_layer
.objects
.selected
:
606 ob
.location
= vector_delta
608 elif mode
in {"d", "i", "n"}:
609 if mode
in {"d", "n"}:
610 # Delta/Relative Coordinates
612 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
614 raise PDT_InvalidVector
616 # Direction/Polar Coordinates
618 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
620 raise PDT_InvalidVector
622 if pg
.plane
== "LO" and mode
in {"d", "n"}:
623 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
624 elif pg
.plane
== "LO" and mode
== "i":
625 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
627 if obj
.mode
== "EDIT":
629 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], vec
=vector_delta
631 if obj
.mode
== "OBJECT":
632 for ob
in context
.view_layer
.objects
.selected
:
633 ob
.location
= obj_loc
+ vector_delta
634 # Percent Options Only Other Choice
637 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
639 raise PDT_InvalidVector
640 if obj
.mode
== 'EDIT':
641 verts
[-1].co
= vector_delta
642 if obj
.mode
== "OBJECT":
643 obj
.location
= vector_delta
644 if obj
.mode
== 'EDIT':
645 bmesh
.update_edit_mesh(obj
.data
)
646 bm
.select_history
.clear()
649 def add_new_vertex(context
, pg
, operation
, mode
, obj
, bm
, verts
, values
):
653 context: Blender bpy.context instance.
654 pg, operation, mode, obj, bm, verts, values
660 obj_loc
= obj
.matrix_world
.decompose()[0]
662 if not obj
.mode
== "EDIT":
663 pg
.error
= PDT_ERR_ADDVEDIT
664 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
665 raise PDT_SelectionError
666 if mode
not in {"a"}:
667 if not isinstance(verts
[0], bmesh
.types
.BMVert
):
668 pg
.error
= PDT_ERR_VERT_MODE
669 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
670 raise PDT_FeatureError
671 # Absolute/Global Coordinates
674 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
676 raise PDT_InvalidVector
677 new_vertex
= bm
.verts
.new(vector_delta
- obj_loc
)
678 # Delta/Relative Coordinates
679 elif mode
in {"d", "n"}:
681 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
683 raise PDT_InvalidVector
685 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
686 new_vertex
= bm
.verts
.new(verts
[-1].co
+ vector_delta
)
687 # Direction/Polar Coordinates
690 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
692 raise PDT_InvalidVector
694 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
695 new_vertex
= bm
.verts
.new(verts
[-1].co
+ vector_delta
)
696 # Percent Options Only Other Choice
699 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
701 raise PDT_InvalidVector
702 new_vertex
= bm
.verts
.new(vector_delta
)
704 for v
in [v
for v
in bm
.verts
if v
.select
]:
706 new_vertex
.select_set(True)
707 bmesh
.update_edit_mesh(obj
.data
)
708 bm
.select_history
.clear()
711 def split_edges(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, values
):
715 context: Blender bpy.context instance.
716 pg: PDT Parameters Group - our variables
717 operation: The Operation e.g. Create New Vertex
718 mode: The Operation Mode, e.g. a for Absolute
719 obj: The Active Object
720 obj_loc: The object's location in 3D space
721 bm: The object's Bmesh
722 verts: The object's selected vertices, or selected history vertices
723 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
729 if not obj
.mode
== "EDIT":
730 pg
.error
= PDT_ERR_SPLITEDIT
731 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
733 # Absolute/Global Coordinates
736 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
738 raise PDT_InvalidVector
739 edges
= [e
for e
in bm
.edges
if e
.select
]
741 pg
.error
= f
"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
742 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
744 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
745 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
746 new_vertex
= new_verts
[0]
747 new_vertex
.co
= vector_delta
- obj_loc
748 # Delta/Relative Coordinates
751 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
753 raise PDT_InvalidVector
754 edges
= [e
for e
in bm
.edges
if e
.select
]
755 faces
= [f
for f
in bm
.faces
if f
.select
]
757 pg
.error
= PDT_ERR_FACE_SEL
758 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
761 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
762 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
764 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
765 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
766 bmesh
.ops
.translate(bm
, verts
=new_verts
, vec
=vector_delta
)
767 # Directional/Polar Coordinates
770 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
772 raise PDT_InvalidVector
773 edges
= [e
for e
in bm
.edges
if e
.select
]
774 faces
= [f
for f
in bm
.faces
if f
.select
]
776 pg
.error
= PDT_ERR_FACE_SEL
777 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
780 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
781 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
783 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
784 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
785 bmesh
.ops
.translate(bm
, verts
=new_verts
, vec
=vector_delta
)
789 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
791 raise PDT_InvalidVector
792 edges
= [e
for e
in bm
.edges
if e
.select
]
793 faces
= [f
for f
in bm
.faces
if f
.select
]
795 pg
.error
= PDT_ERR_FACE_SEL
796 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
799 pg
.error
= f
"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
800 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
802 geom
= bmesh
.ops
.subdivide_edges(bm
, edges
=edges
, cuts
=1)
803 new_verts
= [v
for v
in geom
["geom_split"] if isinstance(v
, bmesh
.types
.BMVert
)]
804 new_vertex
= new_verts
[0]
805 new_vertex
.co
= vector_delta
807 for v
in [v
for v
in bm
.verts
if v
.select
]:
811 bmesh
.update_edit_mesh(obj
.data
)
812 bm
.select_history
.clear()
815 def extrude_vertices(context
, pg
, operation
, mode
, obj
, obj_loc
, bm
, verts
, values
):
819 context: Blender bpy.context instance.
820 pg: PDT Parameters Group - our variables
821 operation: The Operation e.g. Create New Vertex
822 mode: The Operation Mode, e.g. a for Absolute
823 obj: The Active Object
824 obj_loc: The object's location in 3D space
825 bm: The object's Bmesh
826 verts: The object's selected vertices, or selected history vertices
827 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
833 if not obj
.mode
== "EDIT":
834 pg
.error
= PDT_ERR_EXTEDIT
835 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
837 # Absolute/Global Coordinates
840 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
842 raise PDT_InvalidVector
843 new_vertex
= bm
.verts
.new(vector_delta
- obj_loc
)
844 verts
= [v
for v
in bm
.verts
if v
.select
].copy()
846 bm
.edges
.new([v
, new_vertex
])
848 new_vertex
.select_set(True)
849 bmesh
.ops
.remove_doubles(
850 bm
, verts
=[v
for v
in bm
.verts
if v
.select
], dist
=0.0001
852 # Delta/Relative Coordinates
853 elif mode
in {"d", "n"}:
855 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
857 raise PDT_InvalidVector
859 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
861 new_vertex
= bm
.verts
.new(v
.co
)
862 new_vertex
.co
= new_vertex
.co
+ vector_delta
863 bm
.edges
.new([v
, new_vertex
])
865 new_vertex
.select_set(True)
866 # Direction/Polar Coordinates
869 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
871 raise PDT_InvalidVector
873 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
875 new_vertex
= bm
.verts
.new(v
.co
)
876 new_vertex
.co
= new_vertex
.co
+ vector_delta
877 bm
.edges
.new([v
, new_vertex
])
879 new_vertex
.select_set(True)
882 extend_all
= pg
.extend
884 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 1)
886 raise PDT_InvalidVector
887 verts
= [v
for v
in bm
.verts
if v
.select
].copy()
888 new_vertex
= bm
.verts
.new(vector_delta
)
890 for v
in [v
for v
in bm
.verts
if v
.select
]:
891 bm
.edges
.new([v
, new_vertex
])
894 bm
.edges
.new([verts
[-1], new_vertex
])
895 new_vertex
.select_set(True)
897 bmesh
.update_edit_mesh(obj
.data
)
900 def extrude_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
):
904 context: Blender bpy.context instance.
905 pg: PDT Parameters Group - our variables
906 operation: The Operation e.g. Create New Vertex
907 mode: The Operation Mode, e.g. a for Absolute
908 obj: The Active Object
909 bm: The object's Bmesh
910 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
916 if not obj
.mode
== "EDIT":
917 pg
.error
= PDT_ERR_EXTEDIT
918 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
920 # Delta/Relative Coordinates
921 if mode
in {"d", "n"}:
923 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
925 raise PDT_InvalidVector
926 # Direction/Polar Coordinates
929 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
931 raise PDT_InvalidVector
933 ret
= bmesh
.ops
.extrude_face_region(
936 [f
for f
in bm
.faces
if f
.select
]
937 + [e
for e
in bm
.edges
if e
.select
]
938 + [v
for v
in bm
.verts
if v
.select
]
940 use_select_history
=True,
942 geom_extr
= ret
["geom"]
943 verts_extr
= [v
for v
in geom_extr
if isinstance(v
, bmesh
.types
.BMVert
)]
944 edges_extr
= [e
for e
in geom_extr
if isinstance(e
, bmesh
.types
.BMEdge
)]
945 faces_extr
= [f
for f
in geom_extr
if isinstance(f
, bmesh
.types
.BMFace
)]
948 if pg
.plane
== "LO" and mode
in {"d", "n"}:
949 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
950 elif pg
.plane
== "LO" and mode
== "i":
951 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
953 bmesh
.ops
.translate(bm
, verts
=verts_extr
, vec
=vector_delta
)
954 update_sel(bm
, verts_extr
, edges_extr
, faces_extr
)
955 bmesh
.update_edit_mesh(obj
.data
)
956 bm
.select_history
.clear()
959 def duplicate_geometry(context
, pg
, operation
, mode
, obj
, bm
, values
):
960 """Duplicate Geometry.
963 context: Blender bpy.context instance.
964 pg: PDT Parameters Group - our variables
965 operation: The Operation e.g. Create New Vertex
966 mode: The Operation Mode, e.g. a for Absolute
967 obj: The Active Object
968 bm: The object's Bmesh
969 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
975 if not obj
.mode
== "EDIT":
976 pg
.error
= PDT_ERR_DUPEDIT
977 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
979 # Delta/Relative Coordinates
980 if mode
in {"d", "n"}:
982 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 3)
984 raise PDT_InvalidVector
985 # Direction/Polar Coordinates
988 vector_delta
= vector_build(context
, pg
, obj
, operation
, values
, 2)
990 raise PDT_InvalidVector
992 ret
= bmesh
.ops
.duplicate(
995 [f
for f
in bm
.faces
if f
.select
]
996 + [e
for e
in bm
.edges
if e
.select
]
997 + [v
for v
in bm
.verts
if v
.select
]
999 use_select_history
=True,
1001 geom_dupe
= ret
["geom"]
1002 verts_dupe
= [v
for v
in geom_dupe
if isinstance(v
, bmesh
.types
.BMVert
)]
1003 edges_dupe
= [e
for e
in geom_dupe
if isinstance(e
, bmesh
.types
.BMEdge
)]
1004 faces_dupe
= [f
for f
in geom_dupe
if isinstance(f
, bmesh
.types
.BMFace
)]
1007 if pg
.plane
== "LO" and mode
in {"d", "n"}:
1008 vector_delta
= view_coords(vector_delta
.x
, vector_delta
.y
, vector_delta
.z
)
1009 elif pg
.plane
== "LO" and mode
== "i":
1010 vector_delta
= view_dir(pg
.distance
, pg
.angle
)
1012 bmesh
.ops
.translate(bm
, verts
=verts_dupe
, vec
=vector_delta
)
1013 update_sel(bm
, verts_dupe
, edges_dupe
, faces_dupe
)
1014 bmesh
.update_edit_mesh(obj
.data
)
1017 def fillet_geometry(context
, pg
, mode
, obj
, bm
, verts
, values
):
1021 context: Blender bpy.context instance.
1022 pg: PDT Parameters Group - our variables
1023 operation: The Operation e.g. Create New Vertex
1024 mode: The Operation Mode, e.g. a for Absolute
1025 obj: The Active Object
1026 bm: The object's Bmesh
1027 verts: The object's selected vertices, or selected history vertices
1028 values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
1034 if not obj
.mode
== "EDIT":
1035 pg
.error
= PDT_ERR_FILEDIT
1036 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1038 if mode
in {"i", "v"}:
1043 # Note that passing an empty parameter results in that parameter being seen as "0"
1044 # _offset <= 0 is ignored since a bevel/fillet radius must be > 0 to make sense
1045 _offset
= float(values
[0])
1046 # Force _segments to an integer (bug fix T95442)
1047 _segments
= int(float(values
[1]))
1049 _segments
= 1 # This is a single, flat segment (ignores profile)
1050 _profile
= float(values
[2])
1051 if _profile
< 0.0 or _profile
> 1.0:
1052 _profile
= 0.5 # This is a circular profile
1054 # Fillet & Intersect Two Edges
1055 # Always use Current Selection
1056 verts
= [v
for v
in bm
.verts
if v
.select
]
1057 edges
= [e
for e
in bm
.edges
if e
.select
]
1058 if len(edges
) == 2 and len(verts
) == 4:
1060 v_active
= edges
[0].verts
[0]
1061 v_other
= edges
[0].verts
[1]
1062 v_last
= edges
[1].verts
[0]
1063 v_first
= edges
[1].verts
[1]
1064 vector_delta
, done
= intersection(v_active
.co
,
1071 pg
.error
= f
"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}"
1072 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1073 raise PDT_IntersectionError
1074 if (v_active
.co
- vector_delta
).length
< (v_other
.co
- vector_delta
).length
:
1075 v_active
.co
= vector_delta
1076 v_other
.select_set(False)
1078 v_other
.co
= vector_delta
1079 v_active
.select_set(False)
1080 if (v_last
.co
- vector_delta
).length
< (v_first
.co
- vector_delta
).length
:
1081 v_last
.co
= vector_delta
1082 v_first
.select_set(False)
1084 v_first
.co
= vector_delta
1085 v_last
.select_set(False)
1086 bmesh
.ops
.remove_doubles(bm
, verts
=bm
.verts
, dist
=0.0001)
1088 pg
.error
= f
"{PDT_ERR_SEL_4_VERTS} {len(verts)} Vert(s), {len(edges)} Edge(s))"
1089 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
1090 raise PDT_SelectionError
1093 offset_type
="OFFSET",