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_design.py
parentd94489ea875a20f2bc0aa2b5e490d5ab0c230833 (diff)
Add Clockmender's Precision Drawing Tools v1.1.5
Accepted for inclusion per T70238
Diffstat (limited to 'precision_drawing_tools/pdt_design.py')
-rw-r--r--precision_drawing_tools/pdt_design.py1478
1 files changed, 1478 insertions, 0 deletions
diff --git a/precision_drawing_tools/pdt_design.py b/precision_drawing_tools/pdt_design.py
new file mode 100644
index 00000000..ddde1231
--- /dev/null
+++ b/precision_drawing_tools/pdt_design.py
@@ -0,0 +1,1478 @@
+# ***** 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 bmesh
+import bpy
+import numpy as np
+from bpy.types import Operator
+from mathutils import Vector
+from mathutils.geometry import intersect_point_line
+from math import sin, cos, tan, pi, sqrt
+from .pdt_functions import (
+ setMode,
+ checkSelection,
+ setAxis,
+ updateSel,
+ viewCoords,
+ viewCoordsI,
+ viewDir,
+ arcCentre,
+ intersection,
+ getPercent,
+)
+from .pdt_msg_strings import (
+ PDT_ERR_CONNECTED,
+ PDT_ERR_EDIT_MODE,
+ PDT_ERR_EDOB_MODE,
+ PDT_ERR_FACE_SEL,
+ PDT_ERR_INT_LINES,
+ PDT_ERR_INT_NO_ALL,
+ PDT_ERR_NON_VALID,
+ PDT_ERR_NO_ACT_OBJ,
+ PDT_ERR_NO_ACT_VERTS,
+ PDT_ERR_SEL_1_EDGE,
+ PDT_ERR_SEL_1_VERT,
+ PDT_ERR_SEL_1_VERTI,
+ PDT_ERR_SEL_2_OBJS,
+ PDT_ERR_SEL_2_VERTIO,
+ PDT_ERR_SEL_2_VERTS,
+ PDT_ERR_SEL_3_OBJS,
+ PDT_ERR_SEL_3_VERTIO,
+ PDT_ERR_SEL_3_VERTS,
+ PDT_ERR_SEL_4_OBJS,
+ PDT_ERR_SEL_4_VERTS,
+ PDT_ERR_STRIGHT_LINE,
+ PDT_ERR_TAPER_ANG,
+ PDT_ERR_TAPER_SEL,
+ PDT_ERR_VERT_MODE,
+ PDT_INF_OBJ_MOVED,
+ PDT_LAB_ABS,
+ PDT_LAB_ARCCENTRE,
+ PDT_LAB_DEL,
+ PDT_LAB_DIR,
+ PDT_LAB_INTERSECT,
+ PDT_LAB_NOR,
+ PDT_LAB_PERCENT,
+ PDT_LAB_PLANE
+)
+
+
+class PDT_OT_PlacementAbs(Operator):
+ """Use Absolute, or Global Placement."""
+
+ bl_idname = "pdt.absolute"
+ bl_label = "Absolute Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Absolute (World) Coordinates.
+
+ - Reads pg.operate from Operation Mode Selector as 'data'
+ - Reads pg.cartesian_coords scene variables to:
+ -- set position of CUrsor (CU)
+ -- set postion of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- Split Edges (SE)
+ -- add a New Vertex (NV)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' is used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ oper = pg.operation
+
+ vector_delta = pg.cartesian_coords
+ if oper not in {"CU", "PP", "NV"}:
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ obj_loc = obj.matrix_world.decompose()[0]
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ errmsg = PDT_ERR_NO_ACT_VERTS
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper == "CU":
+ scene.cursor.location = vector_delta
+ scene.cursor.rotation_euler = (0, 0, 0)
+ elif oper == "PP":
+ pg.pivot_loc = vector_delta
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ for v in verts:
+ v.co = vector_delta - obj_loc
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001)
+ bmesh.update_edit_mesh(obj.data)
+ elif obj.mode == "OBJECT":
+ for ob in context.view_layer.objects.selected:
+ ob.location = vector_delta
+ elif oper == "SE" and obj.mode == "EDIT":
+ edges = [e for e in bm.edges if e.select]
+ if len(edges) != 1:
+ errmsg = f"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ 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()
+ elif oper == "NV":
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ vNew = vector_delta - obj.location
+ nVert = bm.verts.new(vNew)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ nVert.select_set(True)
+ else:
+ errmsg = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif oper == "EV" and obj.mode == "EDIT":
+ 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)
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(bm, verts=[v for v in bm.verts if v.select], dist=0.0001)
+ bmesh.update_edit_mesh(obj.data)
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_ABS}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementDelta(Operator):
+ """Use Delta, or Incremental Placement."""
+
+ bl_idname = "pdt.delta"
+ bl_label = "Delta Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Delta Offset (Increment).
+
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.select, pg.plane, pg.cartesian_coords scene variables to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- Split Edges (SE)
+ -- add a New Vertex (NV)
+ -- Duplicate Geometry (DG)
+ -- Extrude Geometry (EG)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ x_loc = pg.cartesian_coords.x
+ y_loc = pg.cartesian_coords.y
+ z_loc = pg.cartesian_coords.z
+ mode_s = pg.select
+ oper = pg.operation
+
+ if pg.plane == "LO":
+ vector_delta = viewCoords(x_loc, y_loc, z_loc)
+ else:
+ vector_delta = Vector((x_loc, y_loc, z_loc))
+ if mode_s == "REL" and oper == "CU":
+ scene.cursor.location = scene.cursor.location + vector_delta
+ elif mode_s == "REL" and oper == "PP":
+ pg.pivot_loc = pg.pivot_loc + vector_delta
+ else:
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ obj_loc = obj.matrix_world.decompose()[0]
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ if oper not in {"MV", "SE", "EV", "DG", "EG"}:
+ if len(bm.select_history) >= 1:
+ actV = checkSelection(1, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_1_VERTI} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper not in {"CU", "PP", "NV"} and obj.mode == "EDIT":
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ errmsg = PDT_ERR_NO_ACT_VERTS
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper == "CU":
+ if obj.mode == "EDIT":
+ scene.cursor.location = obj_loc + actV + vector_delta
+ elif obj.mode == "OBJECT":
+ scene.cursor.location = obj_loc + vector_delta
+ elif oper == "PP":
+ if obj.mode == "EDIT":
+ pg.pivot_loc = obj_loc + actV + vector_delta
+ elif obj.mode == "OBJECT":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ 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 = obj_loc + vector_delta
+ elif oper == "SE" and obj.mode == "EDIT":
+ edges = [e for e in bm.edges if e.select]
+ faces = [f for f in bm.faces if f.select]
+ if len(faces) != 0:
+ errmsg = PDT_ERR_FACE_SEL
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if len(edges) < 1:
+ errmsg = f"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ 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)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif oper == "NV":
+ if obj.mode == "EDIT":
+ vNew = actV + vector_delta
+ nVert = bm.verts.new(vNew)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ else:
+ errmsg = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif oper == "EV" and obj.mode == "EDIT":
+ for v in [v for v in bm.verts if v.select]:
+ 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()
+ elif oper == "DG" and obj.mode == "EDIT":
+ 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()
+ elif oper == "EG" and obj.mode == "EDIT":
+ 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()
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_DEL}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementDis(Operator):
+ """Use Directional, or Distance @ Angle Placement."""
+
+ bl_idname = "pdt.distance"
+ bl_label = "Distance@Angle Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Distance at Angle (Direction).
+
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.select, pg.distance, pg.angle, pg.plane & pg.flip_angle scene variables to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- Split Edges (SE)
+ -- add a New Vertex (NV)
+ -- Duplicate Geometry (DG)
+ -- Extrude Geometry (EG)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ dis_v = pg.distance
+ ang_v = pg.angle
+ plane = pg.plane
+ mode_s = pg.select
+ oper = pg.operation
+ flip_a = pg.flip_angle
+ if flip_a:
+ if ang_v > 0:
+ ang_v = ang_v - 180
+ else:
+ ang_v = ang_v + 180
+ pg.angle = ang_v
+ if plane == "LO":
+ vector_delta = viewDir(dis_v, ang_v)
+ else:
+ a1, a2, _ = setMode(plane)
+ vector_delta = Vector((0, 0, 0))
+ vector_delta[a1] = vector_delta[a1] + (dis_v * cos(ang_v * pi / 180))
+ vector_delta[a2] = vector_delta[a2] + (dis_v * sin(ang_v * pi / 180))
+ if mode_s == "REL" and oper == "CU":
+ scene.cursor.location = scene.cursor.location + vector_delta
+ elif mode_s == "REL" and oper == "PP":
+ pg.pivot_loc = pg.pivot_loc + vector_delta
+ else:
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ obj_loc = obj.matrix_world.decompose()[0]
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ if oper not in {"MV", "SE", "EV", "DG", "EG"}:
+ if len(bm.select_history) >= 1:
+ actV = checkSelection(1, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_1_VERTI} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper not in {"CU", "PP", "NV"} and obj.mode == "EDIT":
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ errmsg = PDT_ERR_NO_ACT_VERTS
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper == "CU":
+ if obj.mode == "EDIT":
+ scene.cursor.location = obj_loc + actV + vector_delta
+ elif obj.mode == "OBJECT":
+ scene.cursor.location = obj_loc + vector_delta
+ elif oper == "PP":
+ if obj.mode == "EDIT":
+ pg.pivot_loc = obj_loc + actV + vector_delta
+ elif obj.mode == "OBJECT":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ 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
+ elif oper == "SE" and obj.mode == "EDIT":
+ edges = [e for e in bm.edges if e.select]
+ faces = [f for f in bm.faces if f.select]
+ if len(faces) != 0:
+ errmsg = PDT_ERR_FACE_SEL
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if len(edges) < 1:
+ errmsg = f"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ 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)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif oper == "NV":
+ if obj.mode == "EDIT":
+ vNew = actV + vector_delta
+ nVert = bm.verts.new(vNew)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ else:
+ errmsg = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif oper == "EV" and obj.mode == "EDIT":
+ for v in [v for v in bm.verts if v.select]:
+ 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()
+ elif oper == "DG" and obj.mode == "EDIT":
+ 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()
+ elif oper == "EG" and obj.mode == "EDIT":
+ 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()
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_DIR}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementPer(Operator):
+ """Use Percentage Placement."""
+
+ bl_idname = "pdt.percent"
+ bl_label = "Percentage Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Percentage between 2 points.
+
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.percent, pg.extend & pg.flip_percent scene variables to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- Split edges (SE)
+ -- add a New vertex (NV)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ per_v = pg.percent
+ oper = pg.operation
+ ext_a = pg.extend
+ flip_p = pg.flip_percent
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ obj_loc = obj.matrix_world.decompose()[0]
+ vector_delta = getPercent(obj, flip_p, per_v, oper, scene)
+ if vector_delta is None:
+ return {"FINISHED"}
+
+ if oper == "CU":
+ if obj.mode == "EDIT":
+ scene.cursor.location = obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ scene.cursor.location = vector_delta
+ elif oper == "PP":
+ if obj.mode == "EDIT":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ pg.pivot_loc = vector_delta
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ bm.select_history[-1].co = vector_delta
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif obj.mode == "OBJECT":
+ obj.location = vector_delta
+ elif oper == "SE" and obj.mode == "EDIT":
+ edges = [e for e in bm.edges if e.select]
+ if len(edges) != 1:
+ errmsg = f"{PDT_ERR_SEL_1_EDGE} {len(edges)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ 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()
+ elif oper == "NV":
+ if obj.mode == "EDIT":
+ nVert = bm.verts.new(vector_delta)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ else:
+ errmsg = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif oper == "EV" and obj.mode == "EDIT":
+ 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()
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_PERCENT}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementNormal(Operator):
+ """Use Normal, or Perpendicular Placement."""
+
+ bl_idname = "pdt.normal"
+ bl_label = "Normal Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Normal Intersection between 3 points.
+
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.extend scene variable to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- Split Edges (SE)
+ -- add a New Vertex (NV)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ oper = pg.operation
+ ext_a = pg.extend
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ obj_loc = obj.matrix_world.decompose()[0]
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ if len(bm.select_history) == 3:
+ actV, othV, lstV = checkSelection(3, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_3_VERTS} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif obj.mode == "OBJECT":
+ objs = context.view_layer.objects.selected
+ if len(objs) != 3:
+ errmsg = f"{PDT_ERR_SEL_3_OBJS} {len(objs)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ objs_s = [ob for ob in objs if ob.name != obj.name]
+ actV = obj.matrix_world.decompose()[0]
+ othV = objs_s[-1].matrix_world.decompose()[0]
+ lstV = objs_s[-2].matrix_world.decompose()[0]
+ vector_delta = intersect_point_line(actV, othV, lstV)[0]
+ if oper == "CU":
+ if obj.mode == "EDIT":
+ scene.cursor.location = obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ scene.cursor.location = vector_delta
+ elif oper == "PP":
+ if obj.mode == "EDIT":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif obj.mode == "OBJECT":
+ pg.pivot_loc = vector_delta
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ if ext_a:
+ for v in [v for v in bm.verts if v.select]:
+ v.co = vector_delta
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(
+ bm, verts=[v for v in bm.verts if v.select], dist=0.0001
+ )
+ else:
+ bm.select_history[-1].co = vector_delta
+ bm.select_history.clear()
+ bmesh.update_edit_mesh(obj.data)
+ elif obj.mode == "OBJECT":
+ context.view_layer.objects.active.location = vector_delta
+ elif oper == "NV":
+ if obj.mode == "EDIT":
+ nVert = bm.verts.new(vector_delta)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ nVert.select_set(True)
+ else:
+ errmsg = f"{PDT_ERR_EDIT_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif oper == "EV" and obj.mode == "EDIT":
+ vNew = vector_delta
+ nVert = bm.verts.new(vNew)
+ if ext_a:
+ for v in [v for v in bm.verts if v.select]:
+ bm.edges.new([v, nVert])
+ else:
+ bm.edges.new([bm.select_history[-1], nVert])
+ 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()
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_NOR}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementInt(Operator):
+ """Use Intersection, or Convergence Placement."""
+
+ bl_idname = "pdt.intersect"
+ bl_label = "Intersect Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects by Convergance Intersection between 4 points, or 2 Edges.
+
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.plane scene variable and operates in Working Plane to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- add a New vertex (NV)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ oper = pg.operation
+ plane = pg.plane
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ obj_loc = obj.matrix_world.decompose()[0]
+ bm = bmesh.from_edit_mesh(obj.data)
+ edges = [e for e in bm.edges if e.select]
+ if len(edges) == 2:
+ ext_a = True
+ va = edges[0].verts[0]
+ actV = va.co
+ vo = edges[0].verts[1]
+ othV = vo.co
+ vl = edges[1].verts[0]
+ lstV = vl.co
+ vf = edges[1].verts[1]
+ fstV = vf.co
+ elif len(bm.select_history) == 4:
+ ext_a = pg.extend
+ va = bm.select_history[-1]
+ vo = bm.select_history[-2]
+ vl = bm.select_history[-3]
+ vf = bm.select_history[-4]
+ actV, othV, lstV, fstV = checkSelection(4, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = (
+ PDT_ERR_SEL_4_VERTS
+ + str(len(bm.select_history))
+ + " Vertices/"
+ + str(len(edges))
+ + " Edges)"
+ )
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ vector_delta, done = intersection(actV, othV, lstV, fstV, plane)
+ if not done:
+ errmsg = f"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+ if oper == "CU":
+ scene.cursor.location = obj_loc + vector_delta
+ elif oper == "PP":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif oper == "NV":
+ vNew = vector_delta
+ nVert = bm.verts.new(vNew)
+ for v in [v for v in bm.verts if v.select]:
+ v.select_set(False)
+ for f in bm.faces:
+ f.select_set(False)
+ for e in bm.edges:
+ e.select_set(False)
+ nVert.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif oper in {"MV", "EV"}:
+ nVert = None
+ proc = False
+
+ if (actV - vector_delta).length < (othV - vector_delta).length:
+ if oper == "MV":
+ va.co = vector_delta
+ proc = True
+ elif oper == "EV":
+ nVert = bm.verts.new(vector_delta)
+ bm.edges.new([va, nVert])
+ proc = True
+ else:
+ if oper == "MV" and ext_a:
+ vo.co = vector_delta
+ elif oper == "EV" and ext_a:
+ nVert = bm.verts.new(vector_delta)
+ bm.edges.new([vo, nVert])
+
+ if (lstV - vector_delta).length < (fstV - vector_delta).length:
+ if oper == "MV" and ext_a:
+ vl.co = vector_delta
+ elif oper == "EV" and ext_a:
+ bm.edges.new([vl, nVert])
+ else:
+ if oper == "MV" and ext_a:
+ vf.co = vector_delta
+ elif oper == "EV" and ext_a:
+ bm.edges.new([vf, nVert])
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
+
+ if not proc and not ext_a:
+ errmsg = PDT_ERR_INT_NO_ALL
+ self.report({"ERROR"}, errmsg)
+ bmesh.update_edit_mesh(obj.data)
+ return {"FINISHED"}
+ else:
+ for v in bm.verts:
+ v.select_set(False)
+ for f in bm.faces:
+ f.select_set(False)
+ for e in bm.edges:
+ e.select_set(False)
+
+ if nVert is not None:
+ nVert.select_set(True)
+ for v in bm.select_history:
+ if v is not None:
+ v.select_set(True)
+ bmesh.update_edit_mesh(obj.data)
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif obj.mode == "OBJECT":
+ if len(context.view_layer.objects.selected) != 4:
+ errmsg = f"{PDT_ERR_SEL_4_OBJS} {len(context.view_layer.objects.selected)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ order = pg.object_order.split(",")
+ objs = sorted(
+ [ob for ob in context.view_layer.objects.selected], key=lambda x: x.name
+ )
+ message = (
+ "Original Object Order was: "
+ + objs[0].name
+ + ", "
+ + objs[1].name
+ + ", "
+ + objs[2].name
+ + ", "
+ + objs[3].name
+ )
+ self.report({"INFO"}, message)
+
+ actV = objs[int(order[0]) - 1].matrix_world.decompose()[0]
+ othV = objs[int(order[1]) - 1].matrix_world.decompose()[0]
+ lstV = objs[int(order[2]) - 1].matrix_world.decompose()[0]
+ fstV = objs[int(order[3]) - 1].matrix_world.decompose()[0]
+ vector_delta, done = intersection(actV, othV, lstV, fstV, plane)
+ if not done:
+ errmsg = f"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if oper == "CU":
+ scene.cursor.location = vector_delta
+ elif oper == "PP":
+ pg.pivot_loc = vector_delta
+ elif oper == "MV":
+ context.view_layer.objects.active.location = vector_delta
+ infmsg = PDT_INF_OBJ_MOVED + message
+ self.report({"INFO"}, infmsg)
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_INTERSECT}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_PlacementCen(Operator):
+ """Use Placement at Arc Centre."""
+
+ bl_idname = "pdt.centre"
+ bl_label = "Centre Mode"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Manipulates Geometry, or Objects to an Arc Centre defined by 3 points on an Imaginary Arc.
+
+ Valid Options for pg.operation; CU PP MV NV EV
+ - Reads pg.operation from Operation Mode Selector as 'oper'
+ - Reads pg.extend scene variable to:
+ -- set position of CUrsor (CU)
+ -- set position of Pivot Point (PP)
+ -- MoVe geometry/objects (MV)
+ -- Extrude Vertices (EV)
+ -- add a New vertex (NV)
+
+ Invalid Options result in self.report Error.
+
+ Local vector variable 'vector_delta' used to reposition features.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ oper = pg.operation
+ ext_a = pg.extend
+ obj = context.view_layer.objects.active
+
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ obj = context.view_layer.objects.active
+ obj_loc = obj.matrix_world.decompose()[0]
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 3:
+ actV = verts[0].co
+ othV = verts[1].co
+ lstV = verts[2].co
+ else:
+ errmsg = f"{PDT_ERR_SEL_3_VERTS} {len(verts)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ vector_delta, radius = arcCentre(actV, othV, lstV)
+ if str(radius) == "inf":
+ errmsg = PDT_ERR_STRIGHT_LINE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ pg.distance = radius
+ if oper == "CU":
+ scene.cursor.location = obj_loc + vector_delta
+ elif oper == "PP":
+ pg.pivot_loc = obj_loc + vector_delta
+ elif oper == "NV":
+ 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()
+ nVert.select_set(True)
+ elif oper == "MV":
+ if obj.mode == "EDIT":
+ if ext_a:
+ for v in [v for v in bm.verts if v.select]:
+ v.co = vector_delta
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(
+ bm, verts=[v for v in bm.verts if v.select], dist=0.0001
+ )
+ else:
+ bm.select_history[-1].co = vector_delta
+ bm.select_history.clear()
+ bmesh.update_edit_mesh(obj.data)
+ elif obj.mode == "OBJECT":
+ context.view_layer.objects.active.location = vector_delta
+ elif oper == "EV":
+ 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)
+ nVert.select_set(True)
+ bm.select_history.clear()
+ bmesh.ops.remove_doubles(
+ bm, verts=[v for v in bm.verts if v.select], dist=0.0001
+ )
+ bmesh.update_edit_mesh(obj.data)
+ else:
+ bm.edges.new([bm.select_history[-1], nVert])
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif obj.mode == "OBJECT":
+ if len(context.view_layer.objects.selected) != 3:
+ errmsg = f"{PDT_ERR_SEL_3_OBJS} {len(context.view_layer.objects.selected)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ actV = context.view_layer.objects.selected[0].matrix_world.decompose()[0]
+ othV = context.view_layer.objects.selected[1].matrix_world.decompose()[0]
+ lstV = context.view_layer.objects.selected[2].matrix_world.decompose()[0]
+ vector_delta, radius = arcCentre(actV, othV, lstV)
+ pg.distance = radius
+ if oper == "CU":
+ scene.cursor.location = vector_delta
+ elif oper == "PP":
+ pg.pivot_loc = vector_delta
+ elif oper == "MV":
+ context.view_layer.objects.active.location = vector_delta
+ else:
+ errmsg = f"{oper} {PDT_ERR_NON_VALID} {PDT_LAB_ARCCENTRE}"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_JoinVerts(Operator):
+ """Join 2 Free Vertices into an Edge."""
+
+ bl_idname = "pdt.join"
+ bl_label = "Join 2 Vertices"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ if ob is None:
+ return False
+ return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
+
+ def execute(self, context):
+ """Joins 2 Free Vertices that do not form part of a Face.
+
+ Joins two vertices that do not form part of a single face
+ It is designed to close open Edge Loops, where a face is not required
+ or to join two disconnected Edges.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ obj = context.view_layer.objects.active
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 2:
+ try:
+ bm.edges.new([verts[-1], verts[-2]])
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return {"FINISHED"}
+ except ValueError:
+ errmsg = PDT_ERR_CONNECTED
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_2_VERTS} {len(verts)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+
+
+class PDT_OT_Fillet(Operator):
+ """Fillet Edges by Vertex, Set Use Verts to False for Extruded Structure."""
+
+ bl_idname = "pdt.fillet"
+ bl_label = "Fillet"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ if ob is None:
+ return False
+ return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
+
+ def execute(self, context):
+ """Create Fillets by Vertex or by Geometry.
+
+ Fillets connected edges, or connected faces
+ Uses:
+ - pg.fillet_radius ; Radius of fillet
+ - pg.fillet_segments ; Number of segments
+ - pg.fillet_profile ; Profile, values 0 to 1
+ - pg.fillet_vertices_only ; Vertices (True), or Face/Edges
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ obj = context.view_layer.objects.active
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 0:
+ errmsg = PDT_ERR_SEL_1_VERT
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ bpy.ops.mesh.bevel(
+ offset_type="OFFSET",
+ offset=pg.fillet_radius,
+ segments=pg.fillet_segments,
+ profile=pg.fillet_profile,
+ vertex_only=pg.fillet_vertices_only,
+ )
+ return {"FINISHED"}
+
+
+class PDT_OT_Angle2(Operator):
+ """Measure Distance and Angle in Working Plane, Also sets Deltas."""
+
+ bl_idname = "pdt.angle2"
+ bl_label = "Measure 2D"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Measures Angle and Offsets between 2 Points in View Plane.
+
+ Uses 2 Selected Vertices to set pg.angle and pg.distance scene variables
+ also sets delta offset from these 2 points using standard Numpy Routines
+ Works in Edit and Oject Modes.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ plane = pg.plane
+ flip_a = pg.flip_angle
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 2:
+ if len(bm.select_history) == 2:
+ actV, othV = checkSelection(2, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_2_VERTIO} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_2_VERTIO} {len(verts)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif obj.mode == "OBJECT":
+ objs = context.view_layer.objects.selected
+ if len(objs) < 2:
+ errmsg = f"{PDT_ERR_SEL_2_OBJS} {len(objs)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ objs_s = [ob for ob in objs if ob.name != obj.name]
+ actV = obj.matrix_world.decompose()[0]
+ othV = objs_s[-1].matrix_world.decompose()[0]
+ if plane == "LO":
+ disV = othV - actV
+ othV = viewCoordsI(disV.x, disV.y, disV.z)
+ actV = Vector((0, 0, 0))
+ v0 = np.array([actV.x + 1, actV.y]) - np.array([actV.x, actV.y])
+ v1 = np.array([othV.x, othV.y]) - np.array([actV.x, actV.y])
+ else:
+ a1, a2, _ = setMode(plane)
+ v0 = np.array([actV[a1] + 1, actV[a2]]) - np.array([actV[a1], actV[a2]])
+ v1 = np.array([othV[a1], othV[a2]]) - np.array([actV[a1], actV[a2]])
+ ang = np.rad2deg(np.arctan2(np.linalg.det([v0, v1]), np.dot(v0, v1)))
+ if flip_a:
+ if ang > 0:
+ pg.angle = ang - 180
+ else:
+ pg.angle = ang + 180
+ else:
+ pg.angle = ang
+ if plane == "LO":
+ pg.distance = sqrt((actV.x - othV.x) ** 2 + (actV.y - othV.y) ** 2)
+ else:
+ pg.distance = sqrt((actV[a1] - othV[a1]) ** 2 + (actV[a2] - othV[a2]) ** 2)
+ pg.cartesian_coords = othV - actV
+ return {"FINISHED"}
+
+
+class PDT_OT_Angle3(Operator):
+ """Measure Distance and Angle in 3D Space."""
+
+ bl_idname = "pdt.angle3"
+ bl_label = "Measure 3D"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Measures Angle and Offsets between 3 Points in World Space, Also sets Deltas.
+
+ Uses 3 Selected Vertices to set pg.angle and pg.distance scene variables
+ also sets delta offset from these 3 points using standard Numpy Routines
+ Works in Edit and Oject Modes.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ pg = context.scene.pdt_pg
+ flip_a = pg.flip_angle
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ verts = [v for v in bm.verts if v.select]
+ if len(verts) == 3:
+ if len(bm.select_history) == 3:
+ actV, othV, lstV = checkSelection(3, bm, obj)
+ if actV is None:
+ errmsg = PDT_ERR_VERT_MODE
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_3_VERTIO} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ else:
+ errmsg = f"{PDT_ERR_SEL_3_VERTIO} {len(verts)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ elif obj.mode == "OBJECT":
+ objs = context.view_layer.objects.selected
+ if len(objs) < 3:
+ errmsg = PDT_ERR_SEL_3_OBJS + str(len(objs))
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ objs_s = [ob for ob in objs if ob.name != obj.name]
+ actV = obj.matrix_world.decompose()[0]
+ othV = objs_s[-1].matrix_world.decompose()[0]
+ lstV = objs_s[-2].matrix_world.decompose()[0]
+ ba = np.array([othV.x, othV.y, othV.z]) - np.array([actV.x, actV.y, actV.z])
+ bc = np.array([lstV.x, lstV.y, lstV.z]) - np.array([actV.x, actV.y, actV.z])
+ cosA = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
+ ang = np.degrees(np.arccos(cosA))
+ if flip_a:
+ if ang > 0:
+ pg.angle = ang - 180
+ else:
+ pg.angle = ang + 180
+ else:
+ pg.angle = ang
+ pg.distance = (actV - othV).length
+ pg.cartesian_coords = othV - actV
+ return {"FINISHED"}
+
+
+class PDT_OT_Origin(Operator):
+ """Move Object Origin to Cursor Location."""
+
+ bl_idname = "pdt.origin"
+ bl_label = "Move Origin"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ """Sets Object Origin in Edit Mode to Cursor Location.
+
+ Keeps geometry static in World Space whilst moving Object Origin
+ Requires cursor location
+ Works in Edit and Object Modes.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ obj = context.view_layer.objects.active
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ obj_loc = obj.matrix_world.decompose()[0]
+ cur_loc = scene.cursor.location
+ diff_v = obj_loc - cur_loc
+ if obj.mode == "EDIT":
+ bm = bmesh.from_edit_mesh(obj.data)
+ for v in bm.verts:
+ v.co = v.co + diff_v
+ obj.location = cur_loc
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ elif obj.mode == "OBJECT":
+ for v in obj.data.vertices:
+ v.co = v.co + diff_v
+ obj.location = cur_loc
+ else:
+ errmsg = f"{PDT_ERR_EDOB_MODE} {obj.mode})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ return {"FINISHED"}
+
+
+class PDT_OT_Taper(Operator):
+ """Taper Vertices at Angle in Chosen Axis Mode."""
+
+ bl_idname = "pdt.taper"
+ bl_label = "Taper"
+ bl_options = {"REGISTER", "UNDO"}
+
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ if ob is None:
+ return False
+ return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
+
+
+ def execute(self, context):
+ """Taper Geometry along World Axes.
+
+ Similar to Shear command except that it shears by angle rather than displacement.
+ Rotates about World Axes and displaces along World Axes, angle must not exceed +-80 degrees.
+ Rotation axis is centred on Active Vertex.
+ Works only in Edit mode.
+
+ Args:
+ context: Blender bpy.context instance.
+
+ Note:
+ Uses pg.taper & pg.angle scene variables
+
+ Returns:
+ Status Set.
+ """
+
+ scene = context.scene
+ pg = scene.pdt_pg
+ tap_ax = pg.taper
+ ang_v = pg.angle
+ obj = context.view_layer.objects.active
+ if ang_v > 80 or ang_v < -80:
+ errmsg = f"{PDT_ERR_TAPER_ANG} {ang_v})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ if obj is None:
+ errmsg = PDT_ERR_NO_ACT_OBJ
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ _, a2, a3 = setAxis(tap_ax)
+ bm = bmesh.from_edit_mesh(obj.data)
+ if len(bm.select_history) >= 1:
+ rotV = bm.select_history[-1]
+ viewV = viewCoords(rotV.co.x, rotV.co.y, rotV.co.z)
+ else:
+ errmsg = f"{PDT_ERR_TAPER_SEL} {len(bm.select_history)})"
+ self.report({"ERROR"}, errmsg)
+ return {"FINISHED"}
+ for v in [v for v in bm.verts if v.select]:
+ if pg.plane == "LO":
+ v_loc = viewCoords(v.co.x, v.co.y, v.co.z)
+ dis_v = sqrt((viewV.x - v_loc.x) ** 2 + (viewV.y - v_loc.y) ** 2)
+ x_loc = dis_v * tan(ang_v * pi / 180)
+ vm = viewDir(x_loc, 0)
+ v.co = v.co - vm
+ else:
+ dis_v = sqrt((rotV.co[a3] - v.co[a3]) ** 2 + (rotV.co[a2] - v.co[a2]) ** 2)
+ v.co[a2] = v.co[a2] - (dis_v * tan(ang_v * pi / 180))
+ bmesh.update_edit_mesh(obj.data)
+ bm.select_history.clear()
+ return {"FINISHED"}