2026-01-01
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -2,25 +2,82 @@ import bpy
|
||||
import math
|
||||
|
||||
|
||||
# Import Custom Icons
|
||||
from ... import icons
|
||||
svg_icons = icons.svg_icons["main"]
|
||||
icon_measure = svg_icons["MEASURE"].icon_id
|
||||
icon_cpu = svg_icons["CPU"].icon_id
|
||||
|
||||
|
||||
#### ------------------------------ PROPERTIES ------------------------------ ####
|
||||
|
||||
class CarverOperatorProperties():
|
||||
class CarverPropsOperator():
|
||||
# OPERATOR-properties
|
||||
mode: bpy.props.EnumProperty(
|
||||
name = "Mode",
|
||||
items = (('DESTRUCTIVE', "Destructive", "Boolean cutters are immediatelly applied and removed after the cut", 'MESH_DATA', 0),
|
||||
('MODIFIER', "Modifier", "Cuts are stored as boolean modifiers and cutters are placed inside the collection", 'MODIFIER_DATA', 1)),
|
||||
default = 'DESTRUCTIVE',
|
||||
items = (('DESTRUCTIVE', "Destructive",
|
||||
"Boolean cutters are immediatelly applied and removed after the cut", 'MESH_DATA', 0),
|
||||
('MODIFIER', "Modifier",
|
||||
"Cuts are stored as boolean modifiers and cutters are placed inside the collection", 'MODIFIER_DATA', 1)),
|
||||
default = 'MODIFIER',
|
||||
)
|
||||
alignment: bpy.props.EnumProperty(
|
||||
name = "Alignment",
|
||||
items = (('SURFACE', "Surface", "Align cutters to the surface normal of the mesh under the mouse", 'SNAP_NORMAL', 0),
|
||||
('VIEW', "View", "Align cutters to the current view", 'VIEW_CAMERA_UNSELECTED', 1),
|
||||
('CURSOR', "3D Cursor", "Align cutters to the 3D cursor orientation", 'ORIENTATION_CURSOR', 2),
|
||||
('GRID', "Grid", "Align cutters to the world grid", 'GRID', 3)),
|
||||
default = 'SURFACE',
|
||||
)
|
||||
depth: bpy.props.EnumProperty(
|
||||
name = "Depth",
|
||||
items = (('VIEW', "View", "Depth is automatically calculated from view orientation", 'VIEW_CAMERA_UNSELECTED', 0),
|
||||
('CURSOR', "Cursor", "Depth is derived from 3D cursors location", 'PIVOT_CURSOR', 1)),
|
||||
default = 'VIEW',
|
||||
items = (('MANUAL', "Manual", "Depth can be manually set after creating a cutter shape", icon_measure, 0),
|
||||
('AUTO', "Auto", "Depth is set automatically to cover selected objects entirely", icon_cpu, 1),
|
||||
('CURSOR', "3D Cursor", "Depth is set to 3D cursors location", 'PIVOT_CURSOR', 2)),
|
||||
default = 'MANUAL',
|
||||
)
|
||||
|
||||
|
||||
class CarverModifierProperties():
|
||||
class CarverPropsShape():
|
||||
# SHAPE-properties
|
||||
orientation: bpy.props.EnumProperty(
|
||||
name = "Orientation",
|
||||
description = "Orientation method for the shape placement",
|
||||
items = (('FACE', "Face Normal", "Orient the shape along the normal of the face"),
|
||||
('CLOSEST_EDGE', "Closest Edge", "Orient the shape along the closest edge of the face"),
|
||||
('LONGEST_EDGE', "Longest Edge", "Orient the shape along the longest edge of the face")),
|
||||
default = 'CLOSEST_EDGE',
|
||||
)
|
||||
offset: bpy.props.FloatProperty(
|
||||
name = "Offset from Surface",
|
||||
description = ("Distance between the shape and the surface of the mesh.\n"
|
||||
"Offset is important for avoiding Z-fighting issues and solver failures"),
|
||||
min = 0.0, soft_max = 0.1,
|
||||
default = 0.01,
|
||||
)
|
||||
align_to_all: bpy.props.BoolProperty(
|
||||
name = "Align to Anything",
|
||||
description = "Use all visible objects for surface alignment, not just selected objects",
|
||||
default = True,
|
||||
)
|
||||
alignment_axis: bpy.props.EnumProperty(
|
||||
name = "Alignment Axis",
|
||||
description = "Which axis of the world grid or 3D cursor should be used for workplane alignment",
|
||||
items = (('X', "X", ""),
|
||||
('Y', "Y", ""),
|
||||
('Z', "Z", "")),
|
||||
default = 'Z',
|
||||
)
|
||||
|
||||
flip_direction: bpy.props.BoolProperty(
|
||||
name = "Flip Direction",
|
||||
description = "Change which way the geometry is extruded",
|
||||
options = {'SKIP_SAVE', 'HIDDEN', 'SKIP_PRESET', },
|
||||
default = False,
|
||||
)
|
||||
|
||||
|
||||
class CarverPropsModifier():
|
||||
# MODIFIER-properties
|
||||
solver: bpy.props.EnumProperty(
|
||||
name = "Solver",
|
||||
@@ -37,7 +94,7 @@ class CarverModifierProperties():
|
||||
)
|
||||
|
||||
|
||||
class CarverCutterProperties():
|
||||
class CarverPropsCutter():
|
||||
# CUTTER-properties
|
||||
hide: bpy.props.BoolProperty(
|
||||
name = "Hide Cutter",
|
||||
@@ -50,6 +107,21 @@ class CarverCutterProperties():
|
||||
"If there is no active object in selection cutters parent might be chosen seemingly randomly"),
|
||||
default = True,
|
||||
)
|
||||
display: bpy.props.EnumProperty(
|
||||
name = "Cutter Display",
|
||||
items = (('WIRE', "Wire", "Display the cutter object as a wireframe"),
|
||||
('BOUNDS', "Bounds", "Display only the bounds of the cutter object")),
|
||||
default = 'BOUNDS'
|
||||
)
|
||||
cutter_origin: bpy.props.EnumProperty(
|
||||
name = "Cutter Origin Point",
|
||||
items = (('CENTER_OBJ', "Bounding Box", "Put the object origin at the center of the cutters bounding box"),
|
||||
('CENTER_MESH', "Geometry", "Put the object origin at the center of the cutters geometry (not including effects)"),
|
||||
('FACE_CENTER', "First Face", "Put the object origin at the center of cutters first face (i.e. shape)"),
|
||||
('MOUSE_INITIAL', "Mouse Click", "Put the object origin at the point where mouse was first clicked"),
|
||||
('CANVAS', "Same as Canvas", "Put the object origin of the cutter to the origin point of the cutter")),
|
||||
default = 'CENTER_MESH',
|
||||
)
|
||||
|
||||
auto_smooth: bpy.props.BoolProperty(
|
||||
name = "Shade Auto Smooth",
|
||||
@@ -66,7 +138,7 @@ class CarverCutterProperties():
|
||||
)
|
||||
|
||||
|
||||
class CarverArrayProperties():
|
||||
class CarverPropsArray():
|
||||
# ARRAY-properties
|
||||
rows: bpy.props.IntProperty(
|
||||
name = "Rows",
|
||||
@@ -74,60 +146,41 @@ class CarverArrayProperties():
|
||||
min = 1, soft_max = 16,
|
||||
default = 1,
|
||||
)
|
||||
rows_gap: bpy.props.FloatProperty(
|
||||
name = "Gap between rows (relative unit)",
|
||||
min = 0, soft_max = 250,
|
||||
default = 50,
|
||||
)
|
||||
rows_direction: bpy.props.EnumProperty(
|
||||
name = "Direction of Rows",
|
||||
items = (('LEFT', "Left", ""),
|
||||
('RIGHT', "Right", "")),
|
||||
default = 'RIGHT',
|
||||
)
|
||||
|
||||
columns: bpy.props.IntProperty(
|
||||
name = "Columns",
|
||||
description = "Number of times shape is duplicated vertically",
|
||||
min = 1, soft_max = 16,
|
||||
default = 1,
|
||||
)
|
||||
columns_direction: bpy.props.EnumProperty(
|
||||
name = "Direction of Rows",
|
||||
items = (('UP', "Up", ""),
|
||||
('DOWN', "Down", "")),
|
||||
default = 'DOWN',
|
||||
)
|
||||
columns_gap: bpy.props.FloatProperty(
|
||||
name = "Gap between columns (relative unit)",
|
||||
min = 0, soft_max = 250,
|
||||
default = 50,
|
||||
gap: bpy.props.FloatProperty(
|
||||
name = "Gap",
|
||||
description = "Spacing between duplicates, both in rows and columns (relative unit)",
|
||||
min = 1, soft_max = 10,
|
||||
default = 1.1,
|
||||
)
|
||||
|
||||
|
||||
class CarverBevelProperties():
|
||||
class CarverPropsBevel():
|
||||
# BEVEL-properties
|
||||
|
||||
use_bevel: bpy.props.BoolProperty(
|
||||
name = "Bevel Cutter",
|
||||
description = "Bevel each side edge of the cutter",
|
||||
default = False,
|
||||
)
|
||||
bevel_profile: bpy.props.EnumProperty(
|
||||
name = "Bevel Profile",
|
||||
items = (('CONVEX', "Convex", "Outside bevel (rounded corners)"),
|
||||
('CONCAVE', "Concave", "Inside bevel")),
|
||||
default = 'CONVEX',
|
||||
)
|
||||
bevel_segments: bpy.props.IntProperty(
|
||||
name = "Bevel Segments",
|
||||
description = "Segments for curved edge",
|
||||
min = 2, soft_max = 32,
|
||||
min = 1, soft_max = 32,
|
||||
default = 8,
|
||||
)
|
||||
bevel_radius: bpy.props.FloatProperty(
|
||||
name = "Bevel Radius",
|
||||
description = "Amout of the bevel (in screen-space units)",
|
||||
min = 0.01, soft_max = 5,
|
||||
default = 1,
|
||||
bevel_width: bpy.props.FloatProperty(
|
||||
name = "Bevel Width",
|
||||
min = 0, soft_max = 5,
|
||||
default = 0.1,
|
||||
)
|
||||
bevel_profile: bpy.props.FloatProperty(
|
||||
name = "Bevel Profile",
|
||||
description = "The bevel profile shape (0.5 = round)",
|
||||
min = 0, max = 1,
|
||||
default = 0.5,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
import bpy
|
||||
import math
|
||||
import os
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from ...functions.mesh import (
|
||||
ensure_attribute,
|
||||
shade_smooth_by_angle,
|
||||
)
|
||||
from ...functions.modifier import (
|
||||
add_modifier_asset,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ CLASSES ------------------------------ ####
|
||||
|
||||
class Selection:
|
||||
"""Storage of viable selected and active object(s) throughout the modal."""
|
||||
|
||||
def __init__(self, selected, active):
|
||||
self.selected: list = selected
|
||||
self.active = active
|
||||
self.modifiers = {}
|
||||
|
||||
|
||||
class Mouse:
|
||||
"""
|
||||
Mouse positions throughout different phases of the modal operator.
|
||||
Each class variable is a 2D vector in screen space (x, y).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.initial = Vector()
|
||||
self.current = Vector()
|
||||
self.extrude = Vector()
|
||||
self.cached = Vector() # Used for custom modifier keys.
|
||||
|
||||
self.current_3d = Vector()
|
||||
self.cached_3d = Vector()
|
||||
|
||||
@classmethod
|
||||
def from_event(self, event):
|
||||
self.initial = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
self.current = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
|
||||
self.current_3d = None
|
||||
return self
|
||||
|
||||
|
||||
class Workplane:
|
||||
"""Local 3D coordinate system used as the drawing plane for creating shapes."""
|
||||
|
||||
def __init__(self, matrix, location, normal):
|
||||
self.matrix: Matrix = matrix # full 4x4 transform matrix.
|
||||
self.location: Vector = location # origin point of the plane in world space.
|
||||
self.normal: Vector = normal # perpendicular direction of the plane.
|
||||
|
||||
|
||||
class Cutter:
|
||||
"""Object created for cutting, as well as it's `bmesh`, and other properties."""
|
||||
|
||||
def __init__(self, obj, mesh, bm, faces, verts):
|
||||
self.obj = obj
|
||||
self.mesh = mesh
|
||||
self.bm = bm
|
||||
self.faces: list = faces
|
||||
self.verts: list = verts
|
||||
self.center = Vector() # Center of the geometry.
|
||||
|
||||
|
||||
# Effects
|
||||
class Effects:
|
||||
|
||||
def __init__(self):
|
||||
self.array = None
|
||||
self.bevel = None
|
||||
self.smooth = None
|
||||
self.weld = None
|
||||
|
||||
def from_invoke(self, cls, context):
|
||||
"""Add modifiers to the cutter object during invoke, if they're enabled on tool level."""
|
||||
|
||||
# Smooth by Angle
|
||||
if cls.auto_smooth:
|
||||
self.add_auto_smooth_modifier(cls, context)
|
||||
|
||||
# Array
|
||||
if cls.rows > 1 or cls.columns > 1:
|
||||
self.add_array_modifier(cls)
|
||||
else:
|
||||
self.array = None
|
||||
|
||||
# Bevel
|
||||
if hasattr(cls, "use_bevel") and cls.use_bevel:
|
||||
self.add_bevel_modifier(cls, affect='VERTICES')
|
||||
else:
|
||||
self.bevel = None
|
||||
|
||||
return self
|
||||
|
||||
def update(self, cls, effect):
|
||||
"""Update bevel modifier during modal."""
|
||||
|
||||
# Update array count.
|
||||
if effect == 'ARRAY_COUNT':
|
||||
if self.array is None:
|
||||
self.add_array_modifier(cls)
|
||||
|
||||
else:
|
||||
if cls.columns > 1 or cls.rows > 1:
|
||||
self.array["Socket_2"] = cls.columns
|
||||
self.array["Socket_3"] = cls.rows
|
||||
|
||||
# Remove modifier if it's no longer needed.
|
||||
if cls.columns == 1 and cls.rows == 1:
|
||||
cls.cutter.obj.modifiers.remove(self.array)
|
||||
self.array = None
|
||||
|
||||
# Update array gap.
|
||||
if effect == 'ARRAY_GAP':
|
||||
if cls.columns > 1 or cls.row > 1:
|
||||
if self.array is not None:
|
||||
self.array["Socket_4"] = cls.gap
|
||||
|
||||
# Force the modifier to update in viewport.
|
||||
self.array.show_viewport = False
|
||||
self.array.show_viewport = True
|
||||
|
||||
# Update bevel width & segments
|
||||
if effect == 'BEVEL':
|
||||
self.bevel.segments = cls.bevel_segments
|
||||
self.bevel.width = cls.bevel_width
|
||||
|
||||
|
||||
# Array
|
||||
def add_array_modifier(self, cls):
|
||||
"""Adds an array modifier(s) on the cutter object."""
|
||||
|
||||
cutter = cls.cutter.obj
|
||||
|
||||
# Load geometry nodes modifier asset.
|
||||
if self.array is None:
|
||||
root = os.path.abspath(os.path.join(__file__, "..", "..", ".."))
|
||||
assets_path = os.path.join(root, "assets.blend")
|
||||
mod = add_modifier_asset(cutter, path=assets_path, asset="cutter_array")
|
||||
|
||||
if not mod:
|
||||
cls.report({'WARNING'}, "Array modifier cannot be loaded for cutter")
|
||||
return
|
||||
|
||||
# Columns
|
||||
if cls.columns > 1:
|
||||
mod["Socket_2"] = cls.columns
|
||||
|
||||
# Rows
|
||||
if cls.rows > 1:
|
||||
mod["Socket_3"] = cls.rows
|
||||
|
||||
# Gap
|
||||
mod["Socket_4"] = cls.gap
|
||||
|
||||
self.array = mod
|
||||
|
||||
|
||||
# Bevel
|
||||
def add_bevel_modifier(self, cls, affect='EDGES'):
|
||||
"""Adds a bevel modifier on the cutter object."""
|
||||
|
||||
cutter = cls.cutter.obj
|
||||
bm = cls.cutter.bm
|
||||
faces = cls.cutter.faces
|
||||
|
||||
mod = cutter.modifiers.new("cutter_bevel", 'BEVEL')
|
||||
mod.limit_method = 'WEIGHT'
|
||||
mod.segments = cls.bevel_segments
|
||||
mod.width = cls.bevel_width
|
||||
mod.profile = cls.bevel_profile
|
||||
|
||||
"""NOTE:
|
||||
In order to allow beveling during the shape creation phase,
|
||||
when we only have one face, we need to bevel vertices instead of edges,
|
||||
and then change it to edges when cutter is manifold (and transfer weights).
|
||||
"""
|
||||
mod.affect = affect
|
||||
if affect == 'EDGES':
|
||||
attr = ensure_attribute(bm, "bevel_weight_edge", 'EDGE')
|
||||
|
||||
# Mark all edges except ones belonging to original and extruded face.
|
||||
for edge in bm.edges:
|
||||
if edge in faces[0].edges:
|
||||
continue
|
||||
if edge in faces[-1].edges:
|
||||
continue
|
||||
edge[attr] = 1.0
|
||||
|
||||
elif affect == 'VERTICES':
|
||||
attr = ensure_attribute(bm, "bevel_weight_vert", 'VERTEX')
|
||||
face = cls.cutter.faces[0]
|
||||
|
||||
# Mark vertices of the original face.
|
||||
verts = [vert for vert in face.verts]
|
||||
for v in verts:
|
||||
v[attr] = 1.0
|
||||
|
||||
# Add Weld modifier (necessary for merging overlapping vertices).
|
||||
# Otherwise live cut produces corrupted booleans because of non-manifold geometry.
|
||||
self.add_weld_modifier(cls)
|
||||
|
||||
self.bevel = mod
|
||||
|
||||
|
||||
def transfer_bevel_weights(self, cls):
|
||||
"""Transfer bevel weights from vertices to edges."""
|
||||
|
||||
if not cls.use_bevel:
|
||||
return
|
||||
|
||||
bm = cls.cutter.bm
|
||||
faces = cls.cutter.faces
|
||||
|
||||
# Ensure default edge weights attribute.
|
||||
edge_attr = ensure_attribute(bm, "bevel_weight_edge", 'EDGE')
|
||||
|
||||
for edge in bm.edges:
|
||||
if edge in faces[0].edges:
|
||||
continue
|
||||
if edge in faces[-1].edges:
|
||||
continue
|
||||
edge[edge_attr] = 1.0
|
||||
|
||||
self.bevel.affect = 'EDGES'
|
||||
|
||||
|
||||
# Smooth by Angle
|
||||
def add_auto_smooth_modifier(self, cls, context):
|
||||
"""Adds a 'Smooth by Angle' modifier on cutter object, a.k.a. Auto Smooth."""
|
||||
|
||||
obj = cls.cutter.obj
|
||||
mesh = cls.cutter.mesh
|
||||
bm = cls.cutter.bm
|
||||
|
||||
modifier_asset_path = "nodes\\geometry_nodes_essentials.blend\\NodeTree\\Smooth by Angle"
|
||||
modifier_asset_file = modifier_asset_path[:modifier_asset_path.find(".blend") + 6]
|
||||
modifier_asset_name = modifier_asset_path.rsplit("\\", 1)[1]
|
||||
|
||||
# Try adding modifier with `bpy.ops` operator(s) first.
|
||||
context_override = {
|
||||
"object": obj,
|
||||
"active_object": obj,
|
||||
"selected_objects": [obj],
|
||||
"selected_editable_objects": [obj],
|
||||
}
|
||||
with context.temp_override(**context_override):
|
||||
try:
|
||||
# Try adding the modifier with `shade_auto_smooth` operator.
|
||||
bpy.ops.object.shade_auto_smooth()
|
||||
except:
|
||||
# Try adding the modifier with path to Essentials library.
|
||||
bpy.ops.object.modifier_add_node_group(asset_library_type="ESSENTIALS",
|
||||
asset_library_identifier="",
|
||||
relative_asset_identifier=modifier_asset_path)
|
||||
|
||||
mod = obj.modifiers.active
|
||||
|
||||
# Try loading the node group manually if `bpy.ops` operators fail.
|
||||
if mod is None:
|
||||
dir = os.path.join(os.path.dirname(bpy.app.binary_path), "5.0", "datafiles", "assets")
|
||||
assets_path = os.path.join(dir, modifier_asset_file)
|
||||
mod = add_modifier_asset(obj, path=assets_path, asset=modifier_asset_name)
|
||||
|
||||
# Resort to destructive editing if everything fails.
|
||||
if mod is None:
|
||||
print("Smooth by Angle modifier couldn't be added.")
|
||||
print("Destructively marking sharp edges and smooth faces in the mesh")
|
||||
shade_smooth_by_angle(bm, mesh, angle=math.degrees(cls.sharp_angle))
|
||||
else:
|
||||
# Set smoothing angle.
|
||||
for face in bm.faces:
|
||||
face.smooth = True
|
||||
bm.to_mesh(mesh)
|
||||
|
||||
mod.use_pin_to_last = True
|
||||
mod["Input_1"] = cls.sharp_angle
|
||||
|
||||
self.smooth = mod
|
||||
|
||||
|
||||
# Weld
|
||||
def add_weld_modifier(self, cls):
|
||||
if self.weld is None:
|
||||
self.weld = cls.cutter.obj.modifiers.new("cutter_weld", 'WELD')
|
||||
return self.weld
|
||||
@@ -7,13 +7,24 @@ from ... import __package__ as base_package
|
||||
def carver_ui_common(context, layout, props):
|
||||
"""Common tool properties for all Carver tools"""
|
||||
|
||||
layout.prop(props, "mode", text="")
|
||||
layout.prop(props, "depth", text="")
|
||||
layout.prop(props, "solver", expand=True)
|
||||
if context.region.type == 'TOOL_HEADER':
|
||||
layout.prop(props, "mode", text="")
|
||||
layout.prop(props, "alignment", text="")
|
||||
layout.prop(props, "depth", text="")
|
||||
layout.prop(props, "solver", expand=True)
|
||||
|
||||
else:
|
||||
# Use labels for Properties editor/sidebar.
|
||||
layout.prop(props, "mode", text="Mode")
|
||||
layout.prop(props, "alignment", text="Alignment")
|
||||
layout.prop(props, "depth", text="Depth")
|
||||
row = layout.row()
|
||||
row.prop(props, "solver", expand=True)
|
||||
layout.separator()
|
||||
|
||||
# Popovers
|
||||
layout.popover("TOPBAR_PT_carver_shape", text="Shape")
|
||||
layout.popover("TOPBAR_PT_carver_array", text="Array")
|
||||
layout.popover("TOPBAR_PT_carver_effects", text="Effects")
|
||||
layout.popover("TOPBAR_PT_carver_cutter", text="Cutter")
|
||||
|
||||
|
||||
@@ -21,7 +32,7 @@ def carver_ui_common(context, layout, props):
|
||||
#### ------------------------------ /popovers/ ------------------------------ ####
|
||||
|
||||
class TOPBAR_PT_carver_shape(bpy.types.Panel):
|
||||
bl_label = "Carver Shape"
|
||||
bl_label = "Cutter Shape"
|
||||
bl_idname = "TOPBAR_PT_carver_shape"
|
||||
bl_region_type = 'HEADER'
|
||||
bl_space_type = 'TOPBAR'
|
||||
@@ -32,12 +43,14 @@ class TOPBAR_PT_carver_shape(bpy.types.Panel):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
prefs = context.preferences.addons[base_package].preferences
|
||||
tool = context.workspace.tools.from_space_view3d_mode('OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH')
|
||||
|
||||
# Box
|
||||
# Box & Circle
|
||||
if tool.idname == "object.carve_box" or tool.idname == "object.carve_circle":
|
||||
props = tool.operator_properties("object.carve_box")
|
||||
if tool.idname == "object.carve_box":
|
||||
props = tool.operator_properties("object.carve_box")
|
||||
else:
|
||||
props = tool.operator_properties("object.carve_circle")
|
||||
|
||||
if tool.idname == "object.carve_circle":
|
||||
layout.prop(props, "subdivision", text="Vertices")
|
||||
@@ -45,29 +58,24 @@ class TOPBAR_PT_carver_shape(bpy.types.Panel):
|
||||
layout.prop(props, "aspect", expand=True)
|
||||
layout.prop(props, "origin", expand=True)
|
||||
|
||||
# bevel
|
||||
if tool.idname == 'object.carve_box':
|
||||
layout.separator()
|
||||
layout.prop(props, "use_bevel", text="Bevel")
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
if prefs.experimental:
|
||||
row.prop(props, "bevel_profile", text="Profile", expand=True)
|
||||
col.prop(props, "bevel_segments", text="Segments")
|
||||
col.prop(props, "bevel_radius", text="Radius")
|
||||
|
||||
if props.use_bevel == False:
|
||||
col.enabled = False
|
||||
if props.alignment == 'SURFACE':
|
||||
layout.prop(props, "orientation")
|
||||
layout.prop(props, "offset", text="Offset")
|
||||
layout.prop(props, "align_to_all")
|
||||
if props.alignment == 'CURSOR':
|
||||
layout.prop(props, "alignment_axis", text="Align to", expand=True)
|
||||
|
||||
# Polyline
|
||||
elif tool.idname == "object.carve_polyline":
|
||||
props = tool.operator_properties("object.carve_polyline")
|
||||
layout.prop(props, "closed")
|
||||
if props.alignment == 'SURFACE':
|
||||
layout.prop(props, "offset", text="Offset")
|
||||
layout.prop(props, "align_to_all")
|
||||
|
||||
|
||||
class TOPBAR_PT_carver_array(bpy.types.Panel):
|
||||
bl_label = "Carver Array"
|
||||
bl_idname = "TOPBAR_PT_carver_array"
|
||||
class TOPBAR_PT_carver_effects(bpy.types.Panel):
|
||||
bl_label = "Cutter Effects"
|
||||
bl_idname = "TOPBAR_PT_carver_effects"
|
||||
bl_region_type = 'HEADER'
|
||||
bl_space_type = 'TOPBAR'
|
||||
bl_category = 'Tool'
|
||||
@@ -78,26 +86,35 @@ class TOPBAR_PT_carver_array(bpy.types.Panel):
|
||||
layout.use_property_decorate = False
|
||||
|
||||
tool = context.workspace.tools.from_space_view3d_mode('OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH')
|
||||
if tool.idname == "object.carve_box" or tool.idname == "object.carve_circle":
|
||||
if tool.idname == "object.carve_box":
|
||||
props = tool.operator_properties("object.carve_box")
|
||||
elif tool.idname == "object.carve_circle":
|
||||
props = tool.operator_properties("object.carve_circle")
|
||||
elif tool.idname == "object.carve_polyline":
|
||||
props = tool.operator_properties("object.carve_polyline")
|
||||
|
||||
# Rows
|
||||
col = layout.column(align=True)
|
||||
col.prop(props, "rows")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "rows_direction", text="Direction", expand=True)
|
||||
col.prop(props, "rows_gap", text="Gap")
|
||||
# Bevel
|
||||
if tool.idname == 'object.carve_box':
|
||||
header, panel = layout.panel("OBJECT_OT_carver_effects_bevel", default_closed=False)
|
||||
header.label(text="Bevel")
|
||||
if panel:
|
||||
panel.prop(props, "use_bevel", text="Side Bevel")
|
||||
col = panel.column(align=True)
|
||||
col.prop(props, "bevel_segments", text="Segments")
|
||||
col.prop(props, "bevel_width", text="Radius")
|
||||
col.prop(props, "bevel_profile", text="Profile", slider=True)
|
||||
|
||||
# Columns
|
||||
layout.separator()
|
||||
col = layout.column(align=True)
|
||||
col.prop(props, "columns")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "columns_direction", text="Direction", expand=True)
|
||||
col.prop(props, "columns_gap", text="Gap")
|
||||
if props.use_bevel == False:
|
||||
col.enabled = False
|
||||
|
||||
# Array
|
||||
header, panel = layout.panel("OBJECT_OT_carver_effects_array", default_closed=False)
|
||||
header.label(text="Array")
|
||||
if panel:
|
||||
col = panel.column(align=True)
|
||||
col.prop(props, "columns")
|
||||
col.prop(props, "rows")
|
||||
col.prop(props, "gap")
|
||||
|
||||
class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
bl_label = "Carver Cutter"
|
||||
@@ -112,23 +129,31 @@ class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
layout.use_property_decorate = False
|
||||
|
||||
tool = context.workspace.tools.from_space_view3d_mode('OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH')
|
||||
if tool.idname == "object.carve_box" or tool.idname == "object.carve_circle":
|
||||
if tool.idname == "object.carve_box":
|
||||
props = tool.operator_properties("object.carve_box")
|
||||
elif tool.idname == "object.carve_circle":
|
||||
props = tool.operator_properties("object.carve_circle")
|
||||
elif tool.idname == "object.carve_polyline":
|
||||
props = tool.operator_properties("object.carve_polyline")
|
||||
|
||||
# modifier_&_cutter
|
||||
col = layout.column()
|
||||
row = col.row()
|
||||
row.prop(props, "display", text="Display", expand=True)
|
||||
col.prop(props, "pin", text="Pin Modifier")
|
||||
if props.mode == 'MODIFIER':
|
||||
col.prop(props, "parent")
|
||||
col.prop(props, "hide")
|
||||
col.prop(props, "cutter_origin", text="Origin")
|
||||
|
||||
# auto_smooth
|
||||
layout.separator()
|
||||
col = layout.column(align=True)
|
||||
col.prop(props, "auto_smooth", text="Auto Smooth")
|
||||
col.prop(props, "sharp_angle")
|
||||
col1 = layout.column()
|
||||
col1.prop(props, "sharp_angle")
|
||||
if not props.auto_smooth:
|
||||
col1.enabled = False
|
||||
|
||||
|
||||
|
||||
@@ -136,7 +161,7 @@ class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
|
||||
classes = [
|
||||
TOPBAR_PT_carver_shape,
|
||||
TOPBAR_PT_carver_array,
|
||||
TOPBAR_PT_carver_effects,
|
||||
TOPBAR_PT_carver_cutter,
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user