mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00

This fixes a small obscure error in Absolute mode whereby Bmesh was not always found if Absolute mode was used. This became apparent if no object was selected and an attempt was made to add a new vertex, or extrude vertices. Other checks did not trap for this error. These traps were originally stored in the individual functions, now all traps are moved to the command parse function.
1055 lines
36 KiB
Python
1055 lines
36 KiB
Python
# ***** 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
|
|
import math
|
|
from bpy.types import Operator
|
|
from mathutils import Vector
|
|
from .pdt_functions import (
|
|
debug,
|
|
intersection,
|
|
obj_check,
|
|
oops,
|
|
update_sel,
|
|
)
|
|
from .pdt_command_functions import (
|
|
vector_build,
|
|
join_two_vertices,
|
|
set_angle_distance_two,
|
|
set_angle_distance_three,
|
|
origin_to_cursor,
|
|
taper,
|
|
placement_normal,
|
|
placement_arc_centre,
|
|
placement_intersect,
|
|
)
|
|
from .pdt_msg_strings import (
|
|
PDT_ERR_ADDVEDIT,
|
|
PDT_ERR_BADFLETTER,
|
|
PDT_ERR_CHARS_NUM,
|
|
PDT_ERR_DUPEDIT,
|
|
PDT_ERR_EXTEDIT,
|
|
PDT_ERR_FACE_SEL,
|
|
PDT_ERR_FILEDIT,
|
|
PDT_ERR_NON_VALID,
|
|
PDT_ERR_NO_SEL_GEOM,
|
|
PDT_ERR_SEL_1_EDGE,
|
|
PDT_ERR_SEL_1_EDGEM,
|
|
PDT_ERR_SPLITEDIT,
|
|
PDT_ERR_BADMATHS,
|
|
PDT_OBJ_MODE_ERROR,
|
|
PDT_ERR_SEL_4_VERTS,
|
|
PDT_ERR_INT_LINES,
|
|
PDT_LAB_PLANE,
|
|
PDT_ERR_NO_ACT_OBJ,
|
|
)
|
|
from .pdt_bix import add_line_to_bisection
|
|
from .pdt_etof import extend_vertex
|
|
from .pdt_xall import intersect_all
|
|
|
|
from . import pdt_exception
|
|
PDT_SelectionError = pdt_exception.SelectionError
|
|
PDT_InvalidVector = pdt_exception.InvalidVector
|
|
PDT_CommandFailure = pdt_exception.CommandFailure
|
|
PDT_ObjectModeError = pdt_exception.ObjectModeError
|
|
PDT_MathsError = pdt_exception.MathsError
|
|
PDT_IntersectionError = pdt_exception.IntersectionError
|
|
PDT_NoObjectError = pdt_exception.NoObjectError
|
|
|
|
|
|
class PDT_OT_CommandReRun(Operator):
|
|
"""Repeat Current Displayed Command."""
|
|
|
|
bl_idname = "pdt.command_rerun"
|
|
bl_label = "Re-run Current Command"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context):
|
|
"""Repeat Current Command Line Input.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
command_run(self, context)
|
|
return {"FINISHED"}
|
|
|
|
|
|
def command_run(self, context):
|
|
"""Run Command String as input into Command Line.
|
|
|
|
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 'operation' - 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, O = Output (Maths Operation only)
|
|
V = Vertex Bevel, E = Edge Bevel, I = Intersect then 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 expression
|
|
|
|
Example; madegrees(atan(3/4)) - sets PDT Angle to smallest angle of 3,4,5 Triangle;
|
|
(36.8699 degrees)
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
scene = context.scene
|
|
pg = scene.pdt_pg
|
|
command = pg.command.strip()
|
|
|
|
# Check Object Type & Mode First
|
|
obj = context.view_layer.objects.active
|
|
if obj is not None and command[0].upper() not in {"M", "?", "HELP"}:
|
|
if obj.mode not in {"OBJECT", "EDIT"} or obj.type != "MESH":
|
|
pg.error = PDT_OBJ_MODE_ERROR
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_ObjectModeError
|
|
|
|
# Special Cases of Command.
|
|
if command == "?" or command.lower() == "help":
|
|
# fmt: off
|
|
context.window_manager.popup_menu(pdt_help, title="PDT Command Line Help", icon="INFO")
|
|
# fmt: on
|
|
return
|
|
if command == "":
|
|
return
|
|
if command.upper() == "J2V":
|
|
join_two_vertices(context)
|
|
return
|
|
if command.upper() == "AD2":
|
|
set_angle_distance_two(context)
|
|
return
|
|
if command.upper() == "AD3":
|
|
set_angle_distance_three(context)
|
|
return
|
|
if command.upper() == "OTC":
|
|
origin_to_cursor(context)
|
|
return
|
|
if command.upper() == "TAP":
|
|
taper(context)
|
|
return
|
|
if command.upper() == "BIS":
|
|
add_line_to_bisection(context)
|
|
return
|
|
if command.upper() == "ETF":
|
|
extend_vertex(context)
|
|
return
|
|
if command.upper() == "INTALL":
|
|
intersect_all(context)
|
|
return
|
|
if command.upper()[1:] == "NML":
|
|
placement_normal(context, command.upper()[0])
|
|
return
|
|
if command.upper()[1:] == "CEN":
|
|
placement_arc_centre(context, command.upper()[0])
|
|
return
|
|
if command.upper()[1:] == "INT":
|
|
placement_intersect(context, command.upper()[0])
|
|
return
|
|
|
|
# Check Command Length
|
|
if len(command) < 3:
|
|
pg.error = PDT_ERR_CHARS_NUM
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
return
|
|
|
|
# Check First Letter
|
|
operation = command[0].upper()
|
|
if operation 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
|
|
|
|
# Check Second Letter.
|
|
mode = command[1].lower()
|
|
if (
|
|
(operation == "F" and mode not in {"v", "e", "i"})
|
|
or (operation in {"D", "E"} and mode not in {"d", "i"})
|
|
or (operation == "M" and mode not in {"a", "d", "i", "p", "o", "x", "y", "z"})
|
|
or (operation not in {"D", "E", "F", "M"} and mode not in {"a", "d", "i", "p"})
|
|
):
|
|
pg.error = f"'{mode}' {PDT_ERR_NON_VALID} '{operation}'"
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
return
|
|
|
|
# --------------
|
|
# Maths Operation
|
|
if operation == "M":
|
|
try:
|
|
command_maths(context, mode, pg, command[2:], mode)
|
|
return
|
|
except PDT_MathsError:
|
|
return
|
|
|
|
# -----------------------------------------------------
|
|
# Not a Maths Operation, so let's parse the command line
|
|
try:
|
|
pg, values, obj, obj_loc, bm, verts = command_parse(context)
|
|
except PDT_SelectionError:
|
|
return
|
|
|
|
# ---------------------
|
|
# Cursor or Pivot Point
|
|
if operation in {"C", "P"}:
|
|
try:
|
|
move_cursor_pivot(context, pg, operation, mode, obj, verts, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# ------------------------
|
|
# Move Vertices or Objects
|
|
if operation == "G":
|
|
try:
|
|
move_entities(context, pg, operation, mode, obj, bm, verts, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# --------------
|
|
# Add New Vertex
|
|
if operation == "N":
|
|
try:
|
|
add_new_vertex(context, pg, operation, mode, obj, bm, verts, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# -----------
|
|
# Split Edges
|
|
if operation == "S":
|
|
try:
|
|
split_edges(context, pg, operation, mode, obj, obj_loc, bm, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
|
|
# ----------------
|
|
# Extrude Vertices
|
|
if operation == "V":
|
|
try:
|
|
extrude_vertices(context, pg, operation, mode, obj, obj_loc, bm, verts, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# ----------------
|
|
# Extrude Geometry
|
|
if operation == "E":
|
|
try:
|
|
extrude_geometry(context, pg, operation, mode, obj, bm, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# ------------------
|
|
# Duplicate Geometry
|
|
if operation == "D":
|
|
try:
|
|
duplicate_geometry(context, pg, operation, mode, obj, bm, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
# ---------------
|
|
# Fillet Geometry
|
|
if operation == "F":
|
|
try:
|
|
fillet_geometry(context, pg, mode, obj, bm, verts, values)
|
|
except PDT_CommandFailure:
|
|
return
|
|
|
|
|
|
def pdt_help(self, context):
|
|
"""Display PDT Command Line help in a pop-up.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
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="i: Fillet & Intersect 2 Disconnected 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="o: Send Maths Calculation to Output")
|
|
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_maths(context, mode, pg, expression, output_target):
|
|
"""Evaluates Maths Input.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
pg: PDT Parameters Group - our variables
|
|
expression: The Maths component of the command input e.g. sqrt(56)
|
|
output_target: The output variable box on the UI
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
namespace = {}
|
|
namespace.update(vars(math))
|
|
try:
|
|
maths_result = eval(expression, namespace, namespace)
|
|
except:
|
|
pg.error = PDT_ERR_BADMATHS
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_MathsError
|
|
|
|
decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
|
|
if output_target == "x":
|
|
pg.cartesian_coords.x = round(maths_result, decimal_places)
|
|
elif output_target == "y":
|
|
pg.cartesian_coords.y = round(maths_result, decimal_places)
|
|
elif output_target == "z":
|
|
pg.cartesian_coords.z = round(maths_result, decimal_places)
|
|
elif output_target == "d":
|
|
pg.distance = round(maths_result, decimal_places)
|
|
elif output_target == "a":
|
|
pg.angle = round(maths_result, decimal_places)
|
|
elif output_target == "p":
|
|
pg.percent = round(maths_result, decimal_places)
|
|
else:
|
|
# Must be "o"
|
|
pg.maths_output = round(maths_result, decimal_places)
|
|
|
|
|
|
def command_parse(context):
|
|
"""Parse Command Input.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
|
|
Returns:
|
|
pg: PDT Parameters Group - our variables
|
|
values_out: The Output Values as a list of numbers
|
|
obj: The Active Object
|
|
obj_loc: The object's location in 3D space
|
|
bm: The object's Bmesh
|
|
verts: The object's selected vertices, or selected history vertices.
|
|
"""
|
|
scene = context.scene
|
|
pg = scene.pdt_pg
|
|
command = pg.command.strip()
|
|
operation = command[0].upper()
|
|
mode = command[1].lower()
|
|
values = command[2:].split(",")
|
|
mode_sel = pg.select
|
|
obj = context.view_layer.objects.active
|
|
ind = 0
|
|
for v in values:
|
|
try:
|
|
_ = float(v)
|
|
good = True
|
|
except ValueError:
|
|
values[ind] = "0.0"
|
|
ind = ind + 1
|
|
# Apply System Rounding
|
|
decimal_places = context.preferences.addons[__package__].preferences.pdt_input_round
|
|
values_out = [str(round(float(v), decimal_places)) for v in values]
|
|
bm = "No Bmesh"
|
|
obj_loc = Vector((0,0,0))
|
|
verts = []
|
|
|
|
if mode == "a" and operation not in {"C", "P"}:
|
|
# Place new Vetex, or Extrude Vertices by Absolute Coords.
|
|
if mode_sel == 'REL':
|
|
pg.select = 'SEL'
|
|
if obj is not None:
|
|
if obj.mode == "EDIT":
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
obj_loc = obj.matrix_world.decompose()[0]
|
|
verts = []
|
|
else:
|
|
pg.error = PDT_OBJ_MODE_ERROR
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_ObjectModeError
|
|
else:
|
|
pg.error = PDT_ERR_NO_ACT_OBJ
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_NoObjectError
|
|
|
|
|
|
if mode_sel == 'SEL' and mode not in {"a"}:
|
|
# All other options except Cursor or Pivot by Absolute
|
|
# These options require no object, etc.
|
|
bm, good = obj_check(obj, scene, operation)
|
|
if good and obj.mode == 'EDIT':
|
|
obj_loc = obj.matrix_world.decompose()[0]
|
|
if len(bm.select_history) == 0 or operation == "G":
|
|
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")
|
|
raise PDT_SelectionError
|
|
else:
|
|
verts = bm.select_history
|
|
|
|
debug(f"command: {operation}{mode}{values_out}")
|
|
debug(f"obj: {obj}, bm: {bm}, obj_loc: {obj_loc}")
|
|
|
|
return pg, values_out, obj, obj_loc, bm, verts
|
|
|
|
|
|
def move_cursor_pivot(context, pg, operation, mode, obj, verts, values):
|
|
"""Moves Cursor & Pivot Point.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
# Absolute/Global Coordinates, or Delta/Relative Coordinates
|
|
if mode in {"a", "d"}:
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
# Direction/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
# Percent Options
|
|
else:
|
|
# Must be Percent
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 1)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
|
|
scene = context.scene
|
|
mode_sel = pg.select
|
|
obj_loc = Vector((0,0,0))
|
|
if obj is not None:
|
|
obj_loc = obj.matrix_world.decompose()[0]
|
|
|
|
if mode == "a":
|
|
if operation == "C":
|
|
scene.cursor.location = vector_delta
|
|
elif operation == "P":
|
|
pg.pivot_loc = vector_delta
|
|
elif mode in {"d", "i"}:
|
|
if mode_sel == "REL":
|
|
if operation == "C":
|
|
scene.cursor.location = scene.cursor.location + vector_delta
|
|
else:
|
|
pg.pivot_loc = pg.pivot_loc + vector_delta
|
|
elif mode_sel == "SEL":
|
|
if obj.mode == "EDIT":
|
|
if operation == "C":
|
|
scene.cursor.location = verts[-1].co + obj_loc + vector_delta
|
|
else:
|
|
pg.pivot_loc = verts[-1].co + obj_loc + vector_delta
|
|
if obj.mode == "OBJECT":
|
|
if operation == "C":
|
|
scene.cursor.location = obj_loc + vector_delta
|
|
else:
|
|
pg.pivot_loc = obj_loc + vector_delta
|
|
else:
|
|
# Must be Percent
|
|
if obj.mode == "EDIT":
|
|
if operation == "C":
|
|
scene.cursor.location = obj_loc + vector_delta
|
|
else:
|
|
pg.pivot_loc = obj_loc + vector_delta
|
|
if obj.mode == "OBJECT":
|
|
if operation == "C":
|
|
scene.cursor.location = vector_delta
|
|
else:
|
|
pg.pivot_loc = vector_delta
|
|
|
|
|
|
def move_entities(context, pg, operation, mode, obj, bm, verts, values):
|
|
"""Moves Entities.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
bm: The object's Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
obj_loc = obj.matrix_world.decompose()[0]
|
|
|
|
# Absolute/Global Coordinates
|
|
if mode == "a":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
if obj.mode == "EDIT":
|
|
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
|
|
)
|
|
if obj.mode == "OBJECT":
|
|
for ob in context.view_layer.objects.selected:
|
|
ob.location = vector_delta
|
|
|
|
elif mode in {"d", "i"}:
|
|
if mode == "d":
|
|
# Delta/Relative Coordinates
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
else:
|
|
# Direction/Polar Coordinates
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
if obj.mode == "EDIT":
|
|
bmesh.ops.translate(
|
|
bm, verts=[v for v in bm.verts if v.select], vec=vector_delta
|
|
)
|
|
if obj.mode == "OBJECT":
|
|
for ob in context.view_layer.objects.selected:
|
|
ob.location = obj_loc + vector_delta
|
|
# Percent Options Only Other Choice
|
|
else:
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 1)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
if obj.mode == 'EDIT':
|
|
verts[-1].co = vector_delta
|
|
if obj.mode == "OBJECT":
|
|
obj.location = vector_delta
|
|
if obj.mode == 'EDIT':
|
|
bmesh.update_edit_mesh(obj.data)
|
|
bm.select_history.clear()
|
|
|
|
|
|
def add_new_vertex(context, pg, operation, mode, obj, bm, verts, values):
|
|
"""Add New Vertex.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg, operation, mode, obj, bm, verts, values
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
obj_loc = obj.matrix_world.decompose()[0]
|
|
|
|
if not obj.mode == "EDIT":
|
|
pg.error = PDT_ERR_ADDVEDIT
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_SelectionError
|
|
# Absolute/Global Coordinates
|
|
if mode == "a":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
new_vertex = bm.verts.new(vector_delta - obj_loc)
|
|
# Delta/Relative Coordinates
|
|
elif mode == "d":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
new_vertex = bm.verts.new(verts[-1].co + vector_delta)
|
|
# Direction/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
new_vertex = bm.verts.new(verts[-1].co + vector_delta)
|
|
# Percent Options Only Other Choice
|
|
else:
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 1)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
new_vertex = bm.verts.new(vector_delta)
|
|
|
|
for v in [v for v in bm.verts if v.select]:
|
|
v.select_set(False)
|
|
new_vertex.select_set(True)
|
|
bmesh.update_edit_mesh(obj.data)
|
|
bm.select_history.clear()
|
|
|
|
|
|
def split_edges(context, pg, operation, mode, obj, obj_loc, bm, values):
|
|
"""Split Edges.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
obj_loc: The object's location in 3D space
|
|
bm: The object's Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
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":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
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)]
|
|
new_vertex = new_verts[0]
|
|
new_vertex.co = vector_delta - obj_loc
|
|
# Delta/Relative Coordinates
|
|
elif mode == "d":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
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)
|
|
# Directional/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
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)
|
|
# Percent Options
|
|
elif mode == "p":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 1)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
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)]
|
|
new_vertex = new_verts[0]
|
|
new_vertex.co = 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()
|
|
|
|
|
|
def extrude_vertices(context, pg, operation, mode, obj, obj_loc, bm, verts, values):
|
|
"""Extrude Vertices.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
obj_loc: The object's location in 3D space
|
|
bm: The object's Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
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":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
new_vertex = bm.verts.new(vector_delta - obj_loc)
|
|
verts = [v for v in bm.verts if v.select].copy()
|
|
for v in verts:
|
|
bm.edges.new([v, new_vertex])
|
|
v.select_set(False)
|
|
new_vertex.select_set(True)
|
|
bmesh.ops.remove_doubles(
|
|
bm, verts=[v for v in bm.verts if v.select], dist=0.0001
|
|
)
|
|
# Delta/Relative Coordinates
|
|
elif mode == "d":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
for v in verts:
|
|
new_vertex = bm.verts.new(v.co)
|
|
new_vertex.co = new_vertex.co + vector_delta
|
|
bm.edges.new([v, new_vertex])
|
|
v.select_set(False)
|
|
new_vertex.select_set(True)
|
|
# Direction/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
for v in verts:
|
|
new_vertex = bm.verts.new(v.co)
|
|
new_vertex.co = new_vertex.co + vector_delta
|
|
bm.edges.new([v, new_vertex])
|
|
v.select_set(False)
|
|
new_vertex.select_set(True)
|
|
# Percent Options
|
|
elif mode == "p":
|
|
extend_all = pg.extend
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 1)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
verts = [v for v in bm.verts if v.select].copy()
|
|
new_vertex = bm.verts.new(vector_delta)
|
|
if extend_all:
|
|
for v in [v for v in bm.verts if v.select]:
|
|
bm.edges.new([v, new_vertex])
|
|
v.select_set(False)
|
|
else:
|
|
bm.edges.new([verts[-1], new_vertex])
|
|
new_vertex.select_set(True)
|
|
|
|
bmesh.update_edit_mesh(obj.data)
|
|
bm.select_history.clear()
|
|
|
|
|
|
def extrude_geometry(context, pg, operation, mode, obj, bm, values):
|
|
"""Extrude Geometry.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
bm: The object's Bmesh
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
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":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
# Direction/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
|
|
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)
|
|
update_sel(bm, verts_extr, edges_extr, faces_extr)
|
|
bmesh.update_edit_mesh(obj.data)
|
|
bm.select_history.clear()
|
|
|
|
|
|
def duplicate_geometry(context, pg, operation, mode, obj, bm, values):
|
|
"""Duplicate Geometry.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
bm: The object's Bmesh
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
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":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 3)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
# Direction/Polar Coordinates
|
|
elif mode == "i":
|
|
try:
|
|
vector_delta = vector_build(context, pg, obj, operation, values, 2)
|
|
except:
|
|
raise PDT_InvalidVector
|
|
|
|
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)
|
|
update_sel(bm, verts_dupe, edges_dupe, faces_dupe)
|
|
bmesh.update_edit_mesh(obj.data)
|
|
bm.select_history.clear()
|
|
|
|
|
|
def fillet_geometry(context, pg, mode, obj, bm, verts, values):
|
|
"""Fillet Geometry.
|
|
|
|
Args:
|
|
context: Blender bpy.context instance.
|
|
pg: PDT Parameters Group - our variables
|
|
operation: The Operation e.g. Create New Vertex
|
|
mode: The Operation Mode, e.g. a for Absolute
|
|
obj: The Active Object
|
|
bm: The object's Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The parameters passed e.g. 1,4,3 for Cartesian Coordinates
|
|
|
|
Returns:
|
|
Nothing.
|
|
"""
|
|
|
|
if not obj.mode == "EDIT":
|
|
pg.error = PDT_ERR_FILEDIT
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
return
|
|
if mode in {"i", "v"}:
|
|
vert_bool = True
|
|
else:
|
|
# Must be "e"
|
|
vert_bool = False
|
|
# 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(values[0])
|
|
_segments = float(values[1])
|
|
if _segments < 1:
|
|
_segments = 1 # This is a single, flat segment (ignores profile)
|
|
_profile = float(values[2])
|
|
if _profile < 0.0 or _profile > 1.0:
|
|
_profile = 0.5 # This is a circular profile
|
|
if mode == "i":
|
|
# Fillet & Intersect Two Edges
|
|
# Always use Current Selection
|
|
verts = [v for v in bm.verts if v.select]
|
|
edges = [e for e in bm.edges if e.select]
|
|
if len(edges) == 2 and len(verts) == 4:
|
|
plane = pg.plane
|
|
v_active = edges[0].verts[0]
|
|
v_other = edges[0].verts[1]
|
|
v_last = edges[1].verts[0]
|
|
v_first = edges[1].verts[1]
|
|
vector_delta, done = intersection(v_active.co,
|
|
v_other.co,
|
|
v_last.co,
|
|
v_first.co,
|
|
plane
|
|
)
|
|
if not done:
|
|
pg.error = f"{PDT_ERR_INT_LINES} {plane} {PDT_LAB_PLANE}"
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_IntersectionError
|
|
if (v_active.co - vector_delta).length < (v_other.co - vector_delta).length:
|
|
v_active.co = vector_delta
|
|
v_other.select_set(False)
|
|
else:
|
|
v_other.co = vector_delta
|
|
v_active.select_set(False)
|
|
if (v_last.co - vector_delta).length < (v_first.co - vector_delta).length:
|
|
v_last.co = vector_delta
|
|
v_first.select_set(False)
|
|
else:
|
|
v_first.co = vector_delta
|
|
v_last.select_set(False)
|
|
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
|
else:
|
|
pg.error = f"{PDT_ERR_SEL_4_VERTS} {len(verts)} Vert(s), {len(edges)} Edge(s))"
|
|
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
|
|
raise PDT_SelectionError
|
|
|
|
bpy.ops.mesh.bevel(
|
|
offset_type="OFFSET",
|
|
offset=_offset,
|
|
segments=_segments,
|
|
profile=_profile,
|
|
vertex_only=vert_bool
|
|
)
|