Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRune Morling <ermo.blender.org@spammesenseless.net>2019-12-09 02:52:27 +0300
committerRune Morling <ermo.blender.org@spammesenseless.net>2019-12-09 02:52:27 +0300
commite7594d45b1dd3d38a795bcafd9c0221ce7922a8d (patch)
tree4dfba991cefea30c97ca99a67a8b226d4d58dbcd /precision_drawing_tools/pdt_command.py
parentd94489ea875a20f2bc0aa2b5e490d5ab0c230833 (diff)
Add Clockmender's Precision Drawing Tools v1.1.5
Accepted for inclusion per T70238
Diffstat (limited to 'precision_drawing_tools/pdt_command.py')
-rw-r--r--precision_drawing_tools/pdt_command.py880
1 files changed, 880 insertions, 0 deletions
diff --git a/precision_drawing_tools/pdt_command.py b/precision_drawing_tools/pdt_command.py
new file mode 100644
index 00000000..423bc83c
--- /dev/null
+++ b/precision_drawing_tools/pdt_command.py
@@ -0,0 +1,880 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+# -----------------------------------------------------------------------
+# Author: Alan Odom (Clockmender), Rune Morling (ermo) Copyright (c) 2019
+# -----------------------------------------------------------------------
+#
+import bpy
+import bmesh
+from mathutils import Vector
+import math
+from .pdt_functions import (
+ debug,
+ disAng,
+ getPercent,
+ objCheck,
+ oops,
+ updateSel,
+)
+from .pdt_msg_strings import (
+ PDT_ERR_ADDVEDIT,
+ PDT_ERR_BAD1VALS,
+ PDT_ERR_BAD2VALS,
+ PDT_ERR_BAD3VALS,
+ PDT_ERR_BADCOORDL,
+ PDT_ERR_BADFLETTER,
+ PDT_ERR_BADMATHS,
+ PDT_ERR_BADSLETTER,
+ PDT_ERR_CHARS_NUM,
+ PDT_ERR_DUPEDIT,
+ PDT_ERR_EXTEDIT,
+ PDT_ERR_FACE_SEL,
+ PDT_ERR_FILEDIT,
+ PDT_ERR_NOCOMMAS,
+ PDT_ERR_NON_VALID,
+ PDT_ERR_NO_ACT_OBJ,
+ PDT_ERR_NO_SEL_GEOM,
+ PDT_ERR_SEL_1_EDGE,
+ PDT_ERR_SEL_1_EDGEM,
+ PDT_ERR_SEL_1_VERT,
+ PDT_ERR_SPLITEDIT
+)
+
+
+def pdt_help(self, context):
+ """Display PDT Command Line help in a pop-up."""
+ label = self.layout.label
+ label(text="Primary Letters (Available Secondary Letters):")
+ label(text="")
+ label(text="C: Cursor (a, d, i, p)")
+ label(text="D: Duplicate Geometry (d, i)")
+ label(text="E: Extrude Geometry (d, i)")
+ label(text="F: Fillet (v, e)")
+ label(text="G: Grab (Move) (a, d, i, p)")
+ label(text="N: New Vertex (a, d, i, p)")
+ label(text="M: Maths Functions (x, y, z, d, a, p)")
+ label(text="P: Pivot Point (a, d, i, p)")
+ label(text="V: Extrude Vertice Only (a, d, i, p)")
+ label(text="S: Split Edges (a, d, i, p)")
+ label(text="?: Quick Help")
+ label(text="")
+ label(text="Secondary Letters:")
+ label(text="")
+ label(text="- General Options:")
+ label(text="a: Absolute (Global) Coordinates e.g. 1,3,2")
+ label(text="d: Delta (Relative) Coordinates, e.g. 0.5,0,1.2")
+ label(text="i: Directional (Polar) Coordinates e.g. 2.6,45")
+ label(text="p: Percent e.g. 67.5")
+ label(text="- Fillet Options:")
+ label(text="v: Fillet Vertices")
+ label(text="e: Fillet Edges")
+ label(text="- Math Options:")
+ label(text="x, y, z: Send result to X, Y and Z input fields in PDT Design")
+ label(text="d, a, p: Send result to Distance, Angle or Percent input field in PDT Design")
+ label(text="")
+ label(text="Note that commands are case-insensitive: ED = Ed = eD = ed")
+ label(text="")
+ label(text="Examples:")
+ label(text="")
+ label(text="ed0.5,,0.6")
+ label(text="'- Extrude Geometry Delta 0.5 in X, 0 in Y, 0.6 in Z")
+ label(text="")
+ label(text="fe0.1,4,0.5")
+ label(text="'- Fillet Edges")
+ label(text="'- Radius: 0.1 (float) -- the radius (or offset) of the bevel/fillet")
+ label(text="'- Segments: 4 (int) -- choosing an even amount of segments gives better geometry")
+ label(text="'- Profile: 0.5 (float[0.0;1.0]) -- 0.5 (default) yields a circular, convex shape")
+
+def command_run(self, context):
+ """Run Command String as input into Command Line.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Note:
+ Uses pg.command, pg.error & many other 'pg.' variables to set PDT menu items,
+ or alter functions
+
+ Command Format; Operation(single letter) Mode(single letter) Values(up to 3 values
+ separated by commas)
+
+ Example; CD0.4,0.6,1.1 - Moves Cursor Delta XYZ = 0.4,0.6,1.1 from Current Position/Active
+ Vertex/Object Origin
+
+ Example; SP35 - Splits active Edge at 35% of separation between edge's vertices
+
+ Valid First Letters (as 'oper' - pg.command[0])
+ C = Cursor, G = Grab(move), N = New Vertex, V = Extrude Vertices Only,
+ E = Extrude geometry, P = Move Pivot Point, D = Duplicate geometry, S = Split Edges
+
+ Capitals and lower case letters are both allowed
+
+ Valid Second Letters (as 'mode' - pg.command[1])
+
+ A = Absolute XYZ, D = Delta XYZ, I = Distance at Angle, P = Percent
+ X = X Delta, Y = Y, Delta Z, = Z Delta (Maths Operation only)
+ V = Vertex Bevel, E = Edge Bevel
+
+ Capitals and lower case letters are both allowed
+
+ Valid Values (pdt_command[2:])
+ Only Integers and Floats, missing values are set to 0, appropriate length checks are
+ performed as Values is split by commas.
+
+ Example; CA,,3 - Cursor to Absolute, is re-interpreted as CA0,0,3
+
+ Exception for Maths Operation, Values section is evaluated as Maths command
+
+ Example; madegrees(atan(3/4)) - sets PDT Angle to smallest angle of 3,4,5 Triangle;
+ (36.8699 degrees)
+
+ This is why all Math functions are imported
+
+ Returns:
+ Nothing.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ cmd = pg.command
+
+ if cmd.strip() == "?" or cmd.lower().strip() == "help":
+ # fmt: off
+ context.window_manager.popup_menu(pdt_help, title="PDT Command Line Help", icon="INFO")
+ # fmt: on
+ return
+ if len(cmd) < 3:
+ pg.error = PDT_ERR_CHARS_NUM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ oper = cmd[0].upper()
+ if oper not in {"C", "D", "E", "F", "G", "N", "M", "P", "V", "S"}:
+ pg.error = PDT_ERR_BADFLETTER
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ mode = cmd[1].lower()
+ if mode not in {"a", "d", "e", "g", "i", "p", "v", "x", "y", "z"}:
+ pg.error = PDT_ERR_BADSLETTER
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+
+ # --------------
+ # Math Operation
+ if oper == "M":
+ if mode not in {"x", "y", "z", "d", "a", "p"}:
+ pg.error = f"{mode} {PDT_ERR_NON_VALID} Maths)"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ exp = cmd[2:]
+ namespace = {}
+ namespace.update(vars(math))
+ if "," in exp:
+ pg.error = PDT_ERR_NOCOMMAS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ try:
+ num = eval(exp, namespace, namespace)
+ except ValueError:
+ pg.error = PDT_ERR_BADMATHS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if mode == "x":
+ pg.cartesian_coords.x = num
+ elif mode == "y":
+ pg.cartesian_coords.y = num
+ elif mode == "z":
+ pg.cartesian_coords.z = num
+ elif mode == "d":
+ pg.distance = num
+ elif mode == "a":
+ pg.angle = num
+ elif mode == "p":
+ pg.percent = num
+ return
+ # "x"/"y"/"z" modes are only legal for Math Operation
+ else:
+ if mode in {"x", "y", "z"}:
+ pg.error = PDT_ERR_BADCOORDL
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+
+ # -----------------------------------------------------
+ # Not a Math Operation, so let's parse the command line
+ vals = cmd[2:].split(",")
+ ind = 0
+ for r in vals:
+ try:
+ _ = float(r)
+ good = True
+ except ValueError:
+ vals[ind] = "0"
+ ind = ind + 1
+ mode_s = pg.select
+ flip_a = pg.flip_angle
+ flip_p = pg.flip_percent
+ ext_a = pg.extend
+ plane = pg.plane
+ obj = context.view_layer.objects.active
+ #FIXME: What does call this imply in terms of invariants?
+ # There's a lot of places in the code where we rely on bm not being None...
+ bm, good = objCheck(obj, scene, oper)
+ obj_loc = None
+ if good:
+ obj_loc = obj.matrix_world.decompose()[0]
+ debug(f"cmd: {cmd}")
+ debug(f"obj: {obj}, bm: {bm}, obj_loc: {obj_loc}")
+
+ # static set variable for use in recurring comparisons
+ adip = {"a", "d", "i", "p"}
+
+ # ---------------------
+ # Cursor or Pivot Point
+ if oper in {"C", "P"}:
+ if mode not in adip:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Absolute/Global Coordinates
+ if mode == "a":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ if oper == "C":
+ scene.cursor.location = vector_delta
+ else:
+ pg.pivot_loc = vector_delta
+ # Delta/Relative Coordinates
+ elif mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ if mode_s == "REL":
+ if oper == "C":
+ scene.cursor.location = scene.cursor.location + vector_delta
+ else:
+ pg.pivot_loc = pg.pivot_loc + vector_delta
+ elif mode_s == "SEL":
+ if obj.mode == "EDIT":
+ if oper == "C":
+ scene.cursor.location = (
+ bm.select_history[-1].co + obj_loc + vector_delta
+ )
+ else:
+ pg.pivot_loc = bm.select_history[-1].co + obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ if oper == "C":
+ scene.cursor.location = obj_loc + vector_delta
+ else:
+ pg.pivot_loc = obj_loc + vector_delta
+ # Direction/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ if mode_s == "REL":
+ if oper == "C":
+ scene.cursor.location = scene.cursor.location + vector_delta
+ else:
+ pg.pivot_loc = pg.pivot_loc + vector_delta
+ elif mode_s == "SEL":
+ if obj.mode == "EDIT":
+ if oper == "C":
+ scene.cursor.location = (
+ bm.select_history[-1].co + obj_loc + vector_delta
+ )
+ else:
+ pg.pivot_loc = bm.select_history[-1].co + obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ if oper == "C":
+ scene.cursor.location = obj_loc + vector_delta
+ else:
+ pg.pivot_loc = obj_loc + vector_delta
+ # Percent Options
+ elif mode == "p":
+ if len(vals) != 1:
+ pg.error = PDT_ERR_BAD1VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = getPercent(obj, flip_p, float(vals[0]), oper, scene)
+ if vector_delta is None:
+ return
+ if obj.mode == "EDIT":
+ if oper == "C":
+ scene.cursor.location = obj_loc + vector_delta
+ else:
+ pg.pivot_loc = obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ if oper == "C":
+ scene.cursor.location = vector_delta
+ else:
+ pg.pivot_loc = vector_delta
+ return
+
+ # ------------------------
+ # Move Vertices or Objects
+ elif oper == "G":
+ if mode not in adip:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Absolute/Global Coordinates
+ if mode == "a":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ if obj.mode == "EDIT":
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ for v in verts:
+ v.co = vector_delta - obj_loc
+ bmesh.ops.remove_doubles(
+ bm, verts=[v for v in bm.verts if v.select], dist=0.0001
+ )
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif obj.mode == "OBJECT":
+ for ob in context.view_layer.objects.selected:
+ ob.location = vector_delta
+ # Delta/Relative Coordinates
+ elif mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ if obj.mode == "EDIT":
+ # FIXME: Show error popup if nothing is selected?
+ bmesh.ops.translate(
+ bm, verts=[v for v in bm.verts if v.select], vec=vector_delta
+ )
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif obj.mode == "OBJECT":
+ for ob in context.view_layer.objects.selected:
+ ob.location = obj_loc + vector_delta
+ # Direction/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ if obj.mode == "EDIT":
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ bmesh.ops.translate(bm, verts=verts, vec=vector_delta)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif obj.mode == "OBJECT":
+ for ob in context.view_layer.objects.selected:
+ ob.location = ob.location + vector_delta
+ # Percent Options
+ elif mode == "p":
+ if obj.mode == "OBJECT":
+ if len(vals) != 1:
+ pg.error = PDT_ERR_BAD1VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = getPercent(obj, flip_p, float(vals[0]), oper, scene)
+ if vector_delta is None:
+ return
+ ob.location = vector_delta
+ return
+
+ # --------------
+ # Add New Vertex
+ elif oper == "N":
+ if mode not in adip:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_ADDVEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Absolute/Global Coordinates
+ if mode == "a":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ vNew = vector_delta - obj_loc
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Delta/Relative Coordinates
+ elif mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ vNew = bm.select_history[-1].co + vector_delta
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Direction/Polar Coordinates
+ elif mode == "d":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ vNew = bm.select_history[-1].co + vector_delta
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Percent Options
+ elif mode == "p":
+ if len(vals) != 1:
+ pg.error = PDT_ERR_BAD1VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = getPercent(obj, flip_p, float(vals[0]), oper, scene)
+ vNew = vector_delta
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return
+
+ # -----------
+ # Split Edges
+ elif oper == "S":
+ if mode not in adip:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_SPLITEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Absolute/Global Coordinates
+ if mode == "a":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ edges = [e for e in bm.edges if e.select]
+ if len(edges) != 1:
+ pg.error = f"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ geom = bmesh.ops.subdivide_edges(bm, edges=edges, cuts=1)
+ new_verts = [v for v in geom["geom_split"] if isinstance(v, bmesh.types.BMVert)]
+ nVert = new_verts[0]
+ nVert.co = vector_delta - obj_loc
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Delta/Relative Coordinates
+ elif mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ edges = [e for e in bm.edges if e.select]
+ faces = [f for f in bm.faces if f.select]
+ if len(faces) != 0:
+ pg.error = PDT_ERR_FACE_SEL
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if len(edges) < 1:
+ pg.error = f"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ geom = bmesh.ops.subdivide_edges(bm, edges=edges, cuts=1)
+ new_verts = [v for v in geom["geom_split"] if isinstance(v, bmesh.types.BMVert)]
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ for v in new_verts:
+ v.select_set(False)
+ bmesh.ops.translate(bm, verts=new_verts, vec=vector_delta)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ for v in new_verts:
+ v.select_set(False)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Directional/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ edges = [e for e in bm.edges if e.select]
+ faces = [f for f in bm.faces if f.select]
+ if len(faces) != 0:
+ pg.error = PDT_ERR_FACE_SEL
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if len(edges) < 1:
+ pg.error = f"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ geom = bmesh.ops.subdivide_edges(bm, edges=edges, cuts=1)
+ new_verts = [v for v in geom["geom_split"] if isinstance(v, bmesh.types.BMVert)]
+ bmesh.ops.translate(bm, verts=new_verts, vec=vector_delta)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ for v in new_verts:
+ v.select_set(False)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Percent Options
+ elif mode == "p":
+ if len(vals) != 1:
+ pg.error = PDT_ERR_BAD1VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = getPercent(obj, flip_p, float(vals[0]), oper, scene)
+ if vector_delta is None:
+ return
+ edges = [e for e in bm.edges if e.select]
+ faces = [f for f in bm.faces if f.select]
+ if len(faces) != 0:
+ pg.error = PDT_ERR_FACE_SEL
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if len(edges) < 1:
+ pg.error = f"{PDT_ERR_SEL_1_EDGEM} {len(edges)})"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ geom = bmesh.ops.subdivide_edges(bm, edges=edges, cuts=1)
+ new_verts = [v for v in geom["geom_split"] if isinstance(v, bmesh.types.BMVert)]
+ nVert = new_verts[0]
+ nVert.co = vector_delta
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return
+
+ # ----------------
+ # Extrude Vertices
+ elif oper == "V":
+ if mode not in adip:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_EXTEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Absolute/Global Coordinates
+ if mode == "a":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ vNew = vector_delta - obj_loc
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ bm.edges.new([v, nVert])
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.ops.remove_doubles(
+ bm, verts=[v for v in bm.verts if v.select], dist=0.0001
+ )
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Delta/Relative Coordinates
+ elif mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ for v in verts:
+ nVert = bm.verts.new(v.co)
+ nVert.co = nVert.co + vector_delta
+ bm.edges.new([v, nVert])
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Direction/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ for v in verts:
+ nVert = bm.verts.new(v.co)
+ nVert.co = nVert.co + vector_delta
+ bm.edges.new([v, nVert])
+ v.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Percent Options
+ elif mode == "p":
+ vector_delta = getPercent(obj, flip_p, float(vals[0]), oper, scene)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ nVert = bm.verts.new(vector_delta)
+ if ext_a:
+ for v in [v for v in bm.verts if v.select]:
+ bm.edges.new([v, nVert])
+ v.select_set(False)
+ else:
+ bm.edges.new([bm.select_history[-1], nVert])
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return
+
+ # ----------------
+ # Extrude Geometry
+ elif oper == "E":
+ if mode not in {"d", "i"}:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_EXTEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Delta/Relative Coordinates
+ if mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ ret = bmesh.ops.extrude_face_region(
+ bm,
+ geom=(
+ [f for f in bm.faces if f.select]
+ + [e for e in bm.edges if e.select]
+ + [v for v in bm.verts if v.select]
+ ),
+ use_select_history=True,
+ )
+ geom_extr = ret["geom"]
+ verts_extr = [v for v in geom_extr if isinstance(v, bmesh.types.BMVert)]
+ edges_extr = [e for e in geom_extr if isinstance(e, bmesh.types.BMEdge)]
+ faces_extr = [f for f in geom_extr if isinstance(f, bmesh.types.BMFace)]
+ del ret
+ bmesh.ops.translate(bm, verts=verts_extr, vec=vector_delta)
+ updateSel(bm, verts_extr, edges_extr, faces_extr)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Direction/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ ret = bmesh.ops.extrude_face_region(
+ bm,
+ geom=(
+ [f for f in bm.faces if f.select]
+ + [e for e in bm.edges if e.select]
+ + [v for v in bm.verts if v.select]
+ ),
+ use_select_history=True,
+ )
+ geom_extr = ret["geom"]
+ verts_extr = [v for v in geom_extr if isinstance(v, bmesh.types.BMVert)]
+ edges_extr = [e for e in geom_extr if isinstance(e, bmesh.types.BMEdge)]
+ faces_extr = [f for f in geom_extr if isinstance(f, bmesh.types.BMFace)]
+ del ret
+ bmesh.ops.translate(bm, verts=verts_extr, vec=vector_delta)
+ updateSel(bm, verts_extr, edges_extr, faces_extr)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return
+
+ # ------------------
+ # Duplicate Geometry
+ elif oper == "D":
+ if mode not in {"d", "i"}:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_DUPEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Delta/Relative Coordinates
+ if mode == "d":
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = Vector((float(vals[0]), float(vals[1]), float(vals[2])))
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ ret = bmesh.ops.duplicate(
+ bm,
+ geom=(
+ [f for f in bm.faces if f.select]
+ + [e for e in bm.edges if e.select]
+ + [v for v in bm.verts if v.select]
+ ),
+ use_select_history=True,
+ )
+ geom_dupe = ret["geom"]
+ verts_dupe = [v for v in geom_dupe if isinstance(v, bmesh.types.BMVert)]
+ edges_dupe = [e for e in geom_dupe if isinstance(e, bmesh.types.BMEdge)]
+ faces_dupe = [f for f in geom_dupe if isinstance(f, bmesh.types.BMFace)]
+ del ret
+ bmesh.ops.translate(bm, verts=verts_dupe, vec=vector_delta)
+ updateSel(bm, verts_dupe, edges_dupe, faces_dupe)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ # Direction/Polar Coordinates
+ elif mode == "i":
+ if len(vals) != 2:
+ pg.error = PDT_ERR_BAD2VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ vector_delta = disAng(vals, flip_a, plane, scene)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_NO_SEL_GEOM
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ ret = bmesh.ops.duplicate(
+ bm,
+ geom=(
+ [f for f in bm.faces if f.select]
+ + [e for e in bm.edges if e.select]
+ + [v for v in bm.verts if v.select]
+ ),
+ use_select_history=True,
+ )
+ geom_dupe = ret["geom"]
+ verts_dupe = [v for v in geom_dupe if isinstance(v, bmesh.types.BMVert)]
+ edges_dupe = [e for e in geom_dupe if isinstance(e, bmesh.types.BMEdge)]
+ faces_dupe = [f for f in geom_dupe if isinstance(f, bmesh.types.BMFace)]
+ del ret
+ bmesh.ops.translate(bm, verts=verts_dupe, vec=vector_delta)
+ updateSel(bm, verts_dupe, edges_dupe, faces_dupe)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return
+
+ # ---------------
+ # Fillet Geometry
+ elif oper == "F":
+ if mode not in {"v", "e"}:
+ pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{oper}'"
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if obj is None:
+ pg.error = PDT_ERR_NO_ACT_OBJ
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if not obj.mode == "EDIT":
+ pg.error = PDT_ERR_FILEDIT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if len(vals) != 3:
+ pg.error = PDT_ERR_BAD3VALS
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ if mode == "v":
+ vert_bool = True
+ elif mode == "e":
+ vert_bool = False
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ pg.error = PDT_ERR_SEL_1_VERT
+ context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
+ return
+ # Note that passing an empty parameter results in that parameter being seen as "0"
+ # _offset <= 0 is ignored since a bevel/fillet radius must be > 0 to make sense
+ _offset = float(vals[0])
+ _segments = int(vals[1])
+ if _segments < 1:
+ _segments = 1 # This is a single, flat segment (ignores profile)
+ _profile = float(vals[2])
+ if _profile < 0.0 or _profile > 1.0:
+ _profile = 0.5 # This is a circular profile
+ bpy.ops.mesh.bevel(
+ offset_type="OFFSET",
+ offset=_offset,
+ segments=_segments,
+ profile=_profile,
+ vertex_only=vert_bool
+ )
+ return