mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00
1032 lines
35 KiB
Python
1032 lines
35 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,
|
|
)
|
|
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
|
|
|
|
|
|
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.
|
|
|
|
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 '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
|
|
|
|
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)
|
|
|
|
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:
|
|
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:
|
|
Self: Itself as a reference for action
|
|
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 objects location in 3D space
|
|
bm: The objects 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 = "None"
|
|
obj_loc = Vector((0,0,0))
|
|
verts = []
|
|
|
|
if mode_sel == 'SEL' and mode not in {"a"}:
|
|
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 paramters passed e.g. 1,4,3 for Catrtesan 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 objects Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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 objects location in 3D space
|
|
bm: The objects Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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 objects location in 3D space
|
|
bm: The objects Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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 objects Bmesh
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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 objects Bmesh
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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 objects Bmesh
|
|
verts: The object's selected vertices, or selected history vertices
|
|
values: The paramters passed e.g. 1,4,3 for Catrtesan 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
|
|
elif mode == "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
|
|
)
|