update addons (for 5.2)
This commit is contained in:
@@ -21,12 +21,12 @@ else:
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
"""NOTE: Order of modules is important because of dependancies. Don't change without a reason."""
|
||||
modules = [
|
||||
modules = (
|
||||
carver_circle,
|
||||
carver_box,
|
||||
carver_polyline,
|
||||
ui,
|
||||
]
|
||||
)
|
||||
|
||||
main_tools = [
|
||||
carver_box.OBJECT_WT_carve_box,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import bpy
|
||||
import os
|
||||
from mathutils import Vector
|
||||
from .. import __file__ as base_file
|
||||
|
||||
from ..constants import (
|
||||
ICONS_PATH,
|
||||
)
|
||||
from ..functions.view import (
|
||||
redraw_regions,
|
||||
)
|
||||
|
||||
from .common.base import (
|
||||
CarverBase,
|
||||
)
|
||||
from .common.properties import (
|
||||
CarverPropsArray,
|
||||
CarverPropsBevel,
|
||||
)
|
||||
from .common.types import (
|
||||
@@ -34,7 +39,7 @@ class OBJECT_WT_carve_box(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
bl_icon = os.path.join(os.path.dirname(base_file), "icons", "tool_icons", "ops.object.carver_box")
|
||||
bl_icon = os.path.join(ICONS_PATH, "dat", "ops.object.carver_box")
|
||||
bl_keymap = (
|
||||
("object.carve_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": None}),
|
||||
("object.carve_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True}, {"properties": None}),
|
||||
@@ -58,9 +63,7 @@ class MESH_WT_carve_box(OBJECT_WT_carve_box):
|
||||
|
||||
#### ------------------------------ OPERATORS ------------------------------ ####
|
||||
|
||||
class OBJECT_OT_carve_box(CarverBase,
|
||||
CarverPropsArray,
|
||||
CarverPropsBevel):
|
||||
class OBJECT_OT_carve_box(CarverBase, CarverPropsBevel):
|
||||
bl_idname = "object.carve_box"
|
||||
bl_label = "Box Carve"
|
||||
bl_description = description
|
||||
@@ -70,6 +73,7 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
# SHAPE-properties
|
||||
shape = 'BOX'
|
||||
|
||||
# NOTE: There are registered on operator level because they need to be overriden or hidden per-tool.
|
||||
aspect: bpy.props.EnumProperty(
|
||||
name = "Aspect",
|
||||
description = "The initial aspect",
|
||||
@@ -115,8 +119,8 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
# cached_variables
|
||||
"""Important for storing context as it was when operator was invoked (untouched by the modal)."""
|
||||
self.phase = "DRAW"
|
||||
self.initial_origin = self.origin # Initial shape origin.
|
||||
self.initial_aspect = self.aspect # Initial shape aspect.
|
||||
self._initial_origin = self.origin # Initial shape origin.
|
||||
self._initial_aspect = self.aspect # Initial shape aspect.
|
||||
self._stored_phase = "DRAW"
|
||||
|
||||
# Add Draw Handler
|
||||
@@ -130,12 +134,11 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
|
||||
|
||||
def modal(self, context, event):
|
||||
redraw_regions(context)
|
||||
|
||||
# Status Bar Text
|
||||
self.status(context)
|
||||
|
||||
# find_the_limit_of_the_3d_viewport_region
|
||||
self.redraw_region(context)
|
||||
|
||||
# Modifier Keys
|
||||
self.event_aspect(context, event)
|
||||
self.event_origin(context, event)
|
||||
@@ -151,7 +154,6 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
if self.phase != "BEVEL":
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
# Mouse Move
|
||||
if event.type == 'MOUSEMOVE':
|
||||
self.mouse.current = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
@@ -164,7 +166,6 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
elif self.phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
|
||||
# Confirm
|
||||
elif event.type == 'LEFTMOUSE':
|
||||
# Confirm Shape
|
||||
@@ -178,7 +179,7 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
min_distance = 5
|
||||
|
||||
if delta_x < min_distance or delta_y < min_distance:
|
||||
self.finalize(context, clean_up=True, abort=True)
|
||||
self.finalize(context, abort=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
self.extrude_cutter(context)
|
||||
@@ -188,18 +189,15 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
if self.depth != 'MANUAL':
|
||||
self.confirm(context)
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
# Confirm Depth
|
||||
if self.phase == "EXTRUDE" and event.value == 'PRESS':
|
||||
self.confirm(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Cancel
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
self.finalize(context, clean_up=True, abort=True)
|
||||
self.finalize(context, abort=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
@@ -305,9 +303,9 @@ class OBJECT_OT_carve_box(CarverBase,
|
||||
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
classes = [
|
||||
classes = (
|
||||
OBJECT_OT_carve_box,
|
||||
]
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import bpy
|
||||
import os
|
||||
from .. import __file__ as base_file
|
||||
|
||||
from ..constants import (
|
||||
ICONS_PATH,
|
||||
)
|
||||
|
||||
from .common.ui import (
|
||||
carver_ui_common,
|
||||
@@ -20,7 +23,7 @@ class OBJECT_WT_carve_circle(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
bl_icon = os.path.join(os.path.dirname(base_file), "icons", "tool_icons", "ops.object.carver_circle")
|
||||
bl_icon = os.path.join(ICONS_PATH, "dat", "ops.object.carver_circle")
|
||||
bl_keymap = (
|
||||
("object.carve_circle", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": None}),
|
||||
("object.carve_circle", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True}, {"properties": None}),
|
||||
@@ -51,6 +54,7 @@ class OBJECT_OT_carve_circle(OBJECT_OT_carve_box):
|
||||
# SHAPE-properties
|
||||
shape = 'CIRCLE'
|
||||
|
||||
# NOTE: There are registered on operator level because they need to be overriden or hidden per-tool.
|
||||
subdivision: bpy.props.IntProperty(
|
||||
name = "Circle Subdivisions",
|
||||
description = "Number of vertices that will make up the circular shape that will be extruded into a cylinder",
|
||||
@@ -76,9 +80,9 @@ class OBJECT_OT_carve_circle(OBJECT_OT_carve_box):
|
||||
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
classes = [
|
||||
classes = (
|
||||
OBJECT_OT_carve_circle,
|
||||
]
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import bpy
|
||||
import math
|
||||
import os
|
||||
from mathutils import Vector
|
||||
from bpy_extras import view3d_utils
|
||||
from .. import __file__ as base_file
|
||||
|
||||
from ..constants import (
|
||||
ICONS_PATH,
|
||||
)
|
||||
from ..functions.view import (
|
||||
redraw_regions,
|
||||
)
|
||||
|
||||
from .common.base import (
|
||||
CarverBase,
|
||||
)
|
||||
from .common.properties import (
|
||||
CarverPropsArray,
|
||||
)
|
||||
from .common.types import (
|
||||
Selection,
|
||||
Mouse,
|
||||
@@ -35,7 +37,7 @@ class OBJECT_WT_carve_polyline(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_context_mode = 'OBJECT'
|
||||
|
||||
bl_icon = os.path.join(os.path.dirname(base_file), "icons", "tool_icons", "ops.object.carver_polyline")
|
||||
bl_icon = os.path.join(ICONS_PATH, "dat", "ops.object.carver_polyline")
|
||||
bl_keymap = (
|
||||
("object.carve_polyline", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None),
|
||||
("object.carve_polyline", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True}, None),
|
||||
@@ -57,8 +59,7 @@ class MESH_WT_carve_polyline(OBJECT_WT_carve_polyline):
|
||||
|
||||
#### ------------------------------ OPERATORS ------------------------------ ####
|
||||
|
||||
class OBJECT_OT_carve_polyline(CarverBase,
|
||||
CarverPropsArray):
|
||||
class OBJECT_OT_carve_polyline(CarverBase):
|
||||
bl_idname = "object.carve_polyline"
|
||||
bl_label = "Polyline Carve"
|
||||
bl_description = description
|
||||
@@ -108,12 +109,11 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
|
||||
|
||||
def modal(self, context, event):
|
||||
redraw_regions(context)
|
||||
|
||||
# Status Bar Text
|
||||
self.status(context)
|
||||
|
||||
# find_the_limit_of_the_3d_viewport_region
|
||||
self.redraw_region(context)
|
||||
|
||||
# Modifier Keys
|
||||
self.event_array(context, event)
|
||||
self.event_move(context, event)
|
||||
@@ -124,7 +124,6 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
if self.phase != "BEVEL":
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
# Mouse Move
|
||||
if event.type == 'MOUSEMOVE':
|
||||
self.mouse.current = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||
@@ -146,12 +145,15 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
elif self.phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
|
||||
# Add Points & Confirm
|
||||
elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
|
||||
if self.phase == "DRAW":
|
||||
# Confirm Shape (if clicked on the first vert)
|
||||
if self._distance_from_first > 75:
|
||||
# Add Point
|
||||
if self._distance_from_first < 75:
|
||||
self._insert_polyline_point()
|
||||
|
||||
# Confirm Shape (if clicked near the first vert).
|
||||
else:
|
||||
verts = self.cutter.verts
|
||||
if len(verts) > 3:
|
||||
self._remove_polyline_point(context, jump_mouse=False)
|
||||
@@ -165,17 +167,12 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
else:
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
# Add Point
|
||||
else:
|
||||
self._insert_polyline_point()
|
||||
|
||||
# Confirm Depth
|
||||
if self.phase == "EXTRUDE":
|
||||
self.confirm(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Confirm
|
||||
# Enter (Confirm)
|
||||
elif event.type == 'RET':
|
||||
verts = self.cutter.verts
|
||||
if len(verts) > 2:
|
||||
@@ -195,18 +192,14 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
if self.phase == "EXTRUDE" and event.value == 'PRESS':
|
||||
self.confirm(context)
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
self.report({'WARNING'}, "At least three points are required to make a polygonal shape")
|
||||
|
||||
|
||||
# Remove Last Point
|
||||
if event.type == 'BACK_SPACE' and event.value == 'PRESS':
|
||||
self._remove_polyline_point(context)
|
||||
|
||||
|
||||
# Cancel
|
||||
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||
self.finalize(context, clean_up=True, abort=True)
|
||||
self.finalize(context, abort=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
@@ -301,7 +294,7 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
context.workspace.status_text_set(modal_keys_extrude)
|
||||
|
||||
|
||||
# Polyline-specific features.
|
||||
# Polyline-specific methods.
|
||||
def _insert_polyline_point(self):
|
||||
"""Inserts a new vertex in the cutter geometry and connects it to the previous last one."""
|
||||
|
||||
@@ -383,15 +376,16 @@ class OBJECT_OT_carve_polyline(CarverBase,
|
||||
context.region_data,
|
||||
vert_world)
|
||||
if screen_pos:
|
||||
context.window.cursor_warp(int(screen_pos.x), int(screen_pos.y))
|
||||
x, y = int(screen_pos.x), int(screen_pos.y)
|
||||
context.window.cursor_warp(x, y)
|
||||
|
||||
|
||||
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
classes = [
|
||||
classes = (
|
||||
OBJECT_OT_carve_polyline,
|
||||
]
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import math
|
||||
import mathutils
|
||||
from bpy_extras import view3d_utils
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from .types import (
|
||||
Effects,
|
||||
from ...functions.cutter import (
|
||||
make_cutter,
|
||||
)
|
||||
from .properties import (
|
||||
CarverPropsOperator,
|
||||
CarverPropsShape,
|
||||
CarverPropsModifier,
|
||||
CarverPropsCutter,
|
||||
)
|
||||
|
||||
from ...functions.draw import (
|
||||
draw_shader,
|
||||
draw_bmesh_faces,
|
||||
draw_circle_around_point,
|
||||
)
|
||||
from ...functions.math import (
|
||||
distance_from_point_to_segment,
|
||||
region_2d_to_plane_3d,
|
||||
region_2d_to_line_3d,
|
||||
draw_circle_billboard,
|
||||
)
|
||||
from ...functions.mesh import (
|
||||
is_instanced_mesh,
|
||||
extrude_face,
|
||||
are_intersecting,
|
||||
raycast,
|
||||
)
|
||||
from ...functions.modifier import (
|
||||
add_boolean_modifier,
|
||||
@@ -35,300 +24,44 @@ from ...functions.modifier import (
|
||||
get_modifiers_to_apply,
|
||||
)
|
||||
from ...functions.object import (
|
||||
set_cutter_properties,
|
||||
delete_empty_collection,
|
||||
delete_cutter,
|
||||
set_object_origin,
|
||||
)
|
||||
from ...functions.poll import (
|
||||
is_linked,
|
||||
is_instanced_data,
|
||||
set_object_origin,
|
||||
delete_object,
|
||||
)
|
||||
from ...functions.scene import (
|
||||
delete_empty_collection,
|
||||
raycast,
|
||||
)
|
||||
from ...functions.view import (
|
||||
distance_from_point_to_segment,
|
||||
region_2d_to_ray_3d,
|
||||
region_2d_to_plane_3d,
|
||||
)
|
||||
|
||||
from .events import (
|
||||
CarverEvents,
|
||||
)
|
||||
from .properties import (
|
||||
CarverPropsOperator,
|
||||
CarverPropsShape,
|
||||
CarverPropsModifier,
|
||||
CarverPropsCutter,
|
||||
CarverPropsArray,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ /base/ ------------------------------ ####
|
||||
|
||||
class CarverEvents():
|
||||
|
||||
def _custom_modifier_event(self, context, event, modifier,
|
||||
cursor='NONE', store_values=False, restore_mouse=True,
|
||||
postprocess=None):
|
||||
"""Creates custom modifier event when key is held and hides cursor until it's released"""
|
||||
|
||||
# Initialize Modifier Phase
|
||||
if event.value == 'PRESS':
|
||||
if self.phase in ("DRAW", "EXTRUDE"):
|
||||
self._stored_phase = self.phase
|
||||
self.phase = modifier
|
||||
self.mouse.cached = self.mouse.current
|
||||
context.window.cursor_set(cursor)
|
||||
|
||||
if store_values:
|
||||
# Store center of the geometry as a Vector.
|
||||
verts = [v.co for v in self.cutter.bm.verts]
|
||||
center = sum(verts, Vector()) / len(verts)
|
||||
self.cutter.center = self.cutter.obj.matrix_world @ center
|
||||
|
||||
# End Modifier Phase
|
||||
elif event.value == 'RELEASE':
|
||||
if self.phase == modifier:
|
||||
context.window.cursor_set('MUTE')
|
||||
if restore_mouse:
|
||||
context.window.cursor_warp(int(self.mouse.cached[0]), int(self.mouse.cached[1]) + 100)
|
||||
self.mouse.current = self.mouse.cached
|
||||
self.phase = self._stored_phase
|
||||
|
||||
if postprocess is not None:
|
||||
postprocess(self)
|
||||
|
||||
return self._stored_phase
|
||||
|
||||
|
||||
# Individual Events
|
||||
def event_aspect(self, context, event):
|
||||
"""Modifier keys for changing aspect of the shape"""
|
||||
|
||||
if event.shift and self.phase == "DRAW":
|
||||
if self.initial_aspect == 'FREE':
|
||||
self.aspect = 'FIXED'
|
||||
elif self.initial_aspect == 'FIXED':
|
||||
self.aspect = 'FREE'
|
||||
else:
|
||||
self.aspect = self.initial_aspect
|
||||
|
||||
|
||||
def event_origin(self, context, event):
|
||||
"""Modifier keys for changing the origin of the shape"""
|
||||
|
||||
if event.alt and self.phase == "DRAW":
|
||||
if self.initial_origin == 'EDGE':
|
||||
self.origin = 'CENTER'
|
||||
elif self.initial_origin == 'CENTER':
|
||||
self.origin = 'EDGE'
|
||||
else:
|
||||
self.origin = self.initial_origin
|
||||
|
||||
|
||||
def event_rotate(self, context, event):
|
||||
"""Modifier keys for rotating the shape"""
|
||||
|
||||
def _remove_rotate_phase_properties(self):
|
||||
del self._stored_mouse_pos_3d
|
||||
del self._stored_rotation
|
||||
del self._stored_cutter_center
|
||||
del self._stored_cutter_euler
|
||||
|
||||
# Restore origin at edge (first vertex).
|
||||
if self.origin == 'EDGE':
|
||||
point = self.cutter.bm.verts[0].co
|
||||
set_object_origin(self.cutter.obj, self.cutter.bm, point='CUSTOM', custom=point)
|
||||
|
||||
|
||||
# Set correct phase.
|
||||
if event.type == 'R':
|
||||
stored_phase = self._custom_modifier_event(context, event, "ROTATE",
|
||||
cursor='MOVE_X', store_values=True, restore_mouse=False,
|
||||
postprocess=_remove_rotate_phase_properties)
|
||||
|
||||
if self.phase == "ROTATE":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
# Project current mouse position onto the workplane.
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
if current_mouse_pos_3d is not None:
|
||||
# Store values.
|
||||
obj = self.cutter.obj
|
||||
if not hasattr(self, '_stored_mouse_pos_3d'):
|
||||
self._stored_mouse_pos_3d = current_mouse_pos_3d.copy()
|
||||
self._stored_rotation = self.rotation
|
||||
self._stored_cutter_center = self.cutter.center
|
||||
self._stored_cutter_euler = obj.rotation_euler.copy()
|
||||
|
||||
# Calculate angle and direction.
|
||||
to_start = (self._stored_mouse_pos_3d - self._stored_cutter_center).normalized()
|
||||
to_current = (current_mouse_pos_3d - self._stored_cutter_center).normalized()
|
||||
|
||||
angle = to_start.angle(to_current)
|
||||
cross = to_start.cross(to_current)
|
||||
if cross.dot(self.workplane.normal) < 0:
|
||||
angle = -angle
|
||||
|
||||
if abs(angle) > 0.0001:
|
||||
self.rotation = self._stored_rotation + angle
|
||||
|
||||
# Offset the object location when drawing from edge to move rotation pivot to center.
|
||||
if self.origin == 'EDGE':
|
||||
set_object_origin(obj, self.cutter.bm, point='CENTER_OBJ')
|
||||
|
||||
# Calculate rotation amount.
|
||||
rotation_total = Matrix.Rotation(self.rotation, 4, self.workplane.normal)
|
||||
rotation_stored = Matrix.Rotation(self._stored_rotation, 4, self.workplane.normal)
|
||||
rotation_matrix = rotation_total @ rotation_stored.inverted()
|
||||
new_rot = rotation_matrix @ self._stored_cutter_euler.to_matrix().to_4x4()
|
||||
|
||||
# Rotate.
|
||||
obj.rotation_euler = new_rot.to_euler()
|
||||
|
||||
|
||||
def event_bevel(self, context, event):
|
||||
"""Modifier keys for beveling the shape"""
|
||||
|
||||
def _remove_empty_bevel_modifier(self):
|
||||
bevel = self.effects.bevel
|
||||
if bevel.width == 0:
|
||||
self.cutter.obj.modifiers.remove(bevel)
|
||||
self.effects.bevel = None
|
||||
|
||||
if self.effects.weld is not None:
|
||||
self.cutter.obj.modifiers.remove(self.effects.weld)
|
||||
self.effects.weld = None
|
||||
|
||||
|
||||
if self.shape != 'BOX':
|
||||
return
|
||||
|
||||
# Set correct phase.
|
||||
if event.type == 'B':
|
||||
stored_phase = self._custom_modifier_event(context, event, "BEVEL",
|
||||
cursor='PICK_AREA', store_values=True,
|
||||
postprocess=_remove_empty_bevel_modifier)
|
||||
|
||||
if self.phase == "BEVEL":
|
||||
self.use_bevel = True
|
||||
|
||||
# Initialize bevel effect if it doesn't exist.
|
||||
if self.effects.bevel is None:
|
||||
self.bevel_width = 0
|
||||
affect = 'VERTICES' if stored_phase == "DRAW" else 'EDGES'
|
||||
self.effects.add_bevel_modifier(self, affect=affect)
|
||||
|
||||
# Force the geometry to update.
|
||||
if stored_phase == "DRAW":
|
||||
self.update_cutter_shape(context)
|
||||
elif stored_phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
# Calculate bevel width.
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
self.mouse.cached_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, self.mouse.cached, self.cutter.center)
|
||||
self.mouse.current_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, self.mouse.current, self.cutter.center)
|
||||
d = (self.cutter.center - self.mouse.current_3d).length - (self.cutter.center - self.mouse.cached_3d).length
|
||||
self.bevel_width = d * 0.2
|
||||
|
||||
# Adjust bevel segments.
|
||||
if event.type == 'WHEELUPMOUSE':
|
||||
self.bevel_segments += 1
|
||||
elif event.type == 'WHEELDOWNMOUSE':
|
||||
self.bevel_segments -= 1
|
||||
|
||||
# Update modifier.
|
||||
self.effects.update(self, 'BEVEL')
|
||||
|
||||
|
||||
def event_array(self, context, event):
|
||||
"""Modifier keys for creating the array of the shape"""
|
||||
|
||||
# Add duplicates.
|
||||
if event.type == 'LEFT_ARROW' and event.value == 'PRESS':
|
||||
self.columns -= 1
|
||||
if event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
|
||||
self.columns += 1
|
||||
if event.type == 'DOWN_ARROW' and event.value == 'PRESS':
|
||||
self.rows -= 1
|
||||
if event.type == 'UP_ARROW' and event.value == 'PRESS':
|
||||
self.rows += 1
|
||||
|
||||
if event.type in ['LEFT_ARROW',
|
||||
'RIGHT_ARROW',
|
||||
'DOWN_ARROW',
|
||||
'UP_ARROW',] and event.value == 'PRESS':
|
||||
self.effects.update(self, 'ARRAY_COUNT')
|
||||
|
||||
# Force the geometry to update.
|
||||
if self.phase == "DRAW":
|
||||
self.update_cutter_shape(context)
|
||||
elif self.phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
# Adjust gap.
|
||||
if (self.rows > 1 or self.columns > 1) and (event.type == 'A'):
|
||||
stored_phase = self._custom_modifier_event(context, event, "ARRAY", cursor='MUTE',
|
||||
store_values=True, restore_mouse=True)
|
||||
|
||||
if self.phase == "ARRAY":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
self.mouse.cached_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, self.mouse.cached, self.cutter.center)
|
||||
self.mouse.current_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, self.mouse.current, self.cutter.center)
|
||||
d = (self.cutter.center - self.mouse.current_3d).length - (self.cutter.center - self.mouse.cached_3d).length
|
||||
self.gap = 1 + (d * 0.2)
|
||||
|
||||
self.effects.update(self, 'ARRAY_GAP')
|
||||
|
||||
|
||||
def event_flip(self, context, event):
|
||||
"""Modifier keys for flipping the direction of extrusion."""
|
||||
|
||||
if event.type == 'F' and event.value == 'PRESS':
|
||||
if self.phase == 'EXTRUDE':
|
||||
self.flip_direction = not self.flip_direction
|
||||
|
||||
|
||||
def event_move(self, context, event):
|
||||
"""Modifier keys for moving the cutter shape."""
|
||||
|
||||
def _remove_move_phase_properties(self):
|
||||
del self._stored_cutter_location
|
||||
self.mouse.cached_3d = None
|
||||
|
||||
|
||||
if event.type == 'SPACE':
|
||||
stored_phase = self._custom_modifier_event(context, event, "MOVE",
|
||||
cursor='SCROLL_XY', restore_mouse=False,
|
||||
postprocess=_remove_move_phase_properties)
|
||||
|
||||
if self.phase == "MOVE":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
# Project current mouse position onto the workplane.
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
if current_mouse_pos_3d is not None:
|
||||
if not hasattr(self, '_stored_cutter_location'):
|
||||
self.mouse.cached_3d = current_mouse_pos_3d.copy()
|
||||
self._stored_cutter_location = self.cutter.obj.location.copy()
|
||||
|
||||
offset = current_mouse_pos_3d - self.mouse.cached_3d
|
||||
self.cutter.obj.location = self._stored_cutter_location + offset
|
||||
|
||||
#### ------------------------------ CLASSES ------------------------------ ####
|
||||
|
||||
class CarverBase(bpy.types.Operator,
|
||||
CarverEvents,
|
||||
CarverPropsOperator,
|
||||
CarverPropsShape,
|
||||
CarverPropsModifier,
|
||||
CarverPropsCutter):
|
||||
CarverPropsCutter,
|
||||
CarverPropsArray):
|
||||
"""Base class for Carver operators."""
|
||||
|
||||
def redraw_region(self, context):
|
||||
"""Redraw the region to find the limits of the 3D viewport."""
|
||||
|
||||
region_types = {'WINDOW', 'UI'}
|
||||
for area in context.window.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
for region in area.regions:
|
||||
if not region_types or region.type in region_types:
|
||||
region.tag_redraw()
|
||||
|
||||
|
||||
# Core Methods
|
||||
def validate_selection(self, context):
|
||||
"""Filter out selection to get the list of viable canvases."""
|
||||
|
||||
@@ -349,10 +82,14 @@ class CarverBase(bpy.types.Operator,
|
||||
continue
|
||||
|
||||
if self.mode == 'DESTRUCTIVE':
|
||||
if is_instanced_data(obj):
|
||||
if is_instanced_mesh(obj.data):
|
||||
self.report({'WARNING'}, f"Modifiers cannot be applied to {obj.name} because it has instanced object data")
|
||||
continue
|
||||
|
||||
if obj.data.shape_keys:
|
||||
self.report({'WARNING'}, f"Modifiers cannot be applied to {obj.name} because it has shape keys")
|
||||
continue
|
||||
|
||||
selected.append(obj)
|
||||
|
||||
# Ensure the active object.
|
||||
@@ -367,11 +104,10 @@ class CarverBase(bpy.types.Operator,
|
||||
return selected, active
|
||||
|
||||
|
||||
# Core Methods
|
||||
def calculate_workplane(self, context):
|
||||
"""
|
||||
Calculates matrix, location (origin point), and normal (direction)
|
||||
of the workplane based on the active alignment method.
|
||||
of the workplane based on the chosen alignment method.
|
||||
"""
|
||||
|
||||
if self.alignment == 'SURFACE':
|
||||
@@ -393,7 +129,7 @@ class CarverBase(bpy.types.Operator,
|
||||
|
||||
|
||||
def create_cutter(self, context):
|
||||
"""Creates a cutter object with correct properties & initializes `bmesh` geometry."""
|
||||
"""Creates a cutter object with correct properties & initializes `bmesh`."""
|
||||
|
||||
# Create the Mesh & bmesh
|
||||
mesh = bpy.data.meshes.new(name="boolean_cutter")
|
||||
@@ -402,15 +138,15 @@ class CarverBase(bpy.types.Operator,
|
||||
# Create the Object
|
||||
obj = bpy.data.objects.new("boolean_cutter", mesh)
|
||||
obj.matrix_world = self.workplane.matrix
|
||||
set_cutter_properties(context, obj, "Difference", collection=True,
|
||||
display='WIRE' if self.shape == 'POLYLINE' else self.display)
|
||||
make_cutter(context, obj, "Difference", collection=True,
|
||||
display='WIRE' if self.shape == 'POLYLINE' else self.display)
|
||||
obj.booleans.carver = True
|
||||
|
||||
# Initial Rotation
|
||||
if self.rotation != 0:
|
||||
rotation_matrix = Matrix.Rotation(self.rotation, 4, self.workplane.normal)
|
||||
temp_matrix_world = rotation_matrix @ obj.matrix_world
|
||||
obj.rotation_euler = temp_matrix_world.to_euler()
|
||||
rotation_matrix = rotation_matrix @ obj.matrix_world
|
||||
obj.rotation_euler = rotation_matrix.to_euler()
|
||||
|
||||
# Create Verts
|
||||
if self.shape == 'BOX':
|
||||
@@ -471,18 +207,21 @@ class CarverBase(bpy.types.Operator,
|
||||
# Draw Line
|
||||
if self.phase in ("BEVEL", "ROTATE", "ARRAY"):
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(context.region, context.region_data,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
if current_mouse_pos_3d is not None:
|
||||
vertices = [self.cutter.center, current_mouse_pos_3d]
|
||||
if vertices is not None:
|
||||
draw_shader('LINES', (0.00, 0.00, 0.00), 1.0, vertices)
|
||||
|
||||
# Draw Circle around First Vertex
|
||||
# Draw circle around first vertex.
|
||||
if self.shape == 'POLYLINE' and self.phase == 'DRAW':
|
||||
verts = self.cutter.verts
|
||||
if len(verts) > 3:
|
||||
vertices = draw_circle_around_point(context, obj, verts[0], self._distance_from_first, 4)
|
||||
vertices = draw_circle_billboard(context,
|
||||
obj.matrix_world @ verts[0].co,
|
||||
radius=self._distance_from_first,
|
||||
segments=4)
|
||||
if len(vertices) > 0:
|
||||
draw_shader('LINE_LOOP', (0, 0, 0), 1.0, vertices)
|
||||
|
||||
@@ -497,7 +236,7 @@ class CarverBase(bpy.types.Operator,
|
||||
bm = self.cutter.bm
|
||||
face = self.cutter.faces[0]
|
||||
|
||||
# Get the mouse positon on the workplane.
|
||||
# Get the mouse positon x, y on the workplane.
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
@@ -582,7 +321,6 @@ class CarverBase(bpy.types.Operator,
|
||||
if self.alignment == 'CURSOR' and self.depth == 'CURSOR':
|
||||
self.depth = 'AUTO'
|
||||
|
||||
|
||||
# Push the extruded face towards the furthest point of the collective bounding box.
|
||||
if self.depth == 'AUTO':
|
||||
corners = []
|
||||
@@ -599,10 +337,9 @@ class CarverBase(bpy.types.Operator,
|
||||
offset = self.offset if self.alignment == 'SURFACE' else 0.1
|
||||
|
||||
for v in face.verts:
|
||||
if self.depth == 'AUTO':
|
||||
v.co += normal * (furthest_corner - offset)
|
||||
v.co += normal * (furthest_corner - offset)
|
||||
|
||||
# Push the extruded face towards the plane of 3D cursor.
|
||||
# Push the extruded face towards the plane of the 3D cursor.
|
||||
elif self.depth == 'CURSOR':
|
||||
local_cursor = self.cutter.obj.matrix_world.inverted() @ context.scene.cursor.location
|
||||
for v in extruded_verts:
|
||||
@@ -647,20 +384,24 @@ class CarverBase(bpy.types.Operator,
|
||||
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
normal = self.cutter.obj.matrix_world.to_3x3().inverted() @ self.workplane.normal
|
||||
|
||||
# Find the point on 3D line (along the normal) closest to the cursor.
|
||||
# and calculate the distance between that and the extrude origin.
|
||||
closest_points = region_2d_to_line_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
self._extrude_origin, self.workplane.normal)
|
||||
# Convert the mouse position into a 3D ray and find closest points between...
|
||||
# that ray and the line coming from the extrude origin along the workplane normal.
|
||||
ray_origin, ray_direction = region_2d_to_ray_3d(region, rv3d, self.mouse.current)
|
||||
closest_points = mathutils.geometry.intersect_line_line(ray_origin,
|
||||
ray_origin + ray_direction,
|
||||
self._extrude_origin,
|
||||
self._extrude_origin + self.workplane.normal)
|
||||
|
||||
if closest_points is None:
|
||||
return
|
||||
|
||||
# Calculate the distance between the extrude origin and mouse position.
|
||||
offset_vector = closest_points[1] - self._extrude_origin
|
||||
distance = offset_vector.dot(self.workplane.normal)
|
||||
|
||||
# Offset vertices of the extruded face from their their original coordinates.
|
||||
normal = self.cutter.obj.matrix_world.to_3x3().inverted() @ self.workplane.normal
|
||||
if distance is not None:
|
||||
for v, vert_co in zip(self._extrude_faces[-1].verts, self._extrude_verts_co):
|
||||
offset = normal * distance
|
||||
@@ -695,68 +436,71 @@ class CarverBase(bpy.types.Operator,
|
||||
ray_obj_matrix = ray.obj.matrix_world
|
||||
mesh = ray.obj.evaluated_get(context.view_layer.depsgraph).to_mesh()
|
||||
|
||||
if mesh is not None:
|
||||
# create_temporary_bmesh
|
||||
temp_bm = bmesh.new()
|
||||
temp_bm.from_mesh(mesh)
|
||||
temp_bm.faces.ensure_lookup_table()
|
||||
if mesh is None:
|
||||
self.alignment = 'VIEW'
|
||||
return None, None, None
|
||||
|
||||
face = temp_bm.faces[ray.index]
|
||||
# Create temporary `bmesh`.
|
||||
temp_bm = bmesh.new()
|
||||
temp_bm.from_mesh(mesh)
|
||||
temp_bm.faces.ensure_lookup_table()
|
||||
|
||||
if self.orientation == 'FACE':
|
||||
# Get the tangent, normal, and bitangent from the face normal.
|
||||
tangent = face.calc_tangent_edge()
|
||||
normal = face.normal
|
||||
bitangent = normal.cross(tangent)
|
||||
face = temp_bm.faces[ray.index]
|
||||
|
||||
elif self.orientation in ('CLOSEST_EDGE', 'LONGEST_EDGE'):
|
||||
# Get the tangent, normal, and bitangent from the longest or the closest edge of the face.
|
||||
if self.orientation == 'FACE':
|
||||
# Get the tangent, normal, and bitangent from the face normal.
|
||||
tangent = face.calc_tangent_edge()
|
||||
normal = face.normal
|
||||
bitangent = normal.cross(tangent)
|
||||
|
||||
if self.orientation == 'LONGEST_EDGE':
|
||||
lengths = [
|
||||
(ray_obj_matrix @ edge.verts[0].co - ray_obj_matrix @ edge.verts[1].co).length
|
||||
for edge in face.edges
|
||||
]
|
||||
longest_edge = sorted(zip(lengths, face.edges), key=lambda x: x[0], reverse=True)[0][1]
|
||||
edge = longest_edge
|
||||
elif self.orientation in ('CLOSEST_EDGE', 'LONGEST_EDGE'):
|
||||
# Get the tangent, normal, and bitangent from the longest or the closest edge of the face.
|
||||
|
||||
elif self.orientation == 'CLOSEST_EDGE':
|
||||
distances = [
|
||||
distance_from_point_to_segment(
|
||||
ray.location,
|
||||
ray_obj_matrix @ edge.verts[0].co,
|
||||
ray_obj_matrix @ edge.verts[1].co,
|
||||
)
|
||||
for edge in face.edges
|
||||
]
|
||||
closest_edge = sorted(zip(distances, face.edges), key=lambda x: x[0])[0][1]
|
||||
edge = closest_edge
|
||||
if self.orientation == 'LONGEST_EDGE':
|
||||
lengths = [
|
||||
(ray_obj_matrix @ edge.verts[0].co - ray_obj_matrix @ edge.verts[1].co).length
|
||||
for edge in face.edges
|
||||
]
|
||||
longest_edge = sorted(zip(lengths, face.edges), key=lambda x: x[0], reverse=True)[0][1]
|
||||
edge = longest_edge
|
||||
|
||||
# Get the loop (face corner) for the edge that is also in the face.
|
||||
face_corner = next(loop for loop in edge.link_loops if loop.face == face)
|
||||
elif self.orientation == 'CLOSEST_EDGE':
|
||||
distances = [
|
||||
distance_from_point_to_segment(
|
||||
ray.location,
|
||||
ray_obj_matrix @ edge.verts[0].co,
|
||||
ray_obj_matrix @ edge.verts[1].co,
|
||||
)
|
||||
for edge in face.edges
|
||||
]
|
||||
closest_edge = sorted(zip(distances, face.edges), key=lambda x: x[0])[0][1]
|
||||
edge = closest_edge
|
||||
|
||||
start = face_corner.vert
|
||||
end = face_corner.link_loop_next.vert
|
||||
direction = (end.co - start.co)
|
||||
# Get the loop (face corner) for the edge that is also in the face.
|
||||
face_corner = next(loop for loop in edge.link_loops if loop.face == face)
|
||||
|
||||
tangent = edge.calc_tangent(face_corner)
|
||||
normal = direction.cross(tangent)
|
||||
bitangent = normal.cross(tangent)
|
||||
start = face_corner.vert
|
||||
end = face_corner.link_loop_next.vert
|
||||
direction = (end.co - start.co)
|
||||
|
||||
# Construct Matrix
|
||||
matrix = Matrix.Identity(4)
|
||||
matrix[0].xyz = (ray_obj_matrix.to_3x3() @ tangent).normalized()
|
||||
matrix[1].xyz = (ray_obj_matrix.to_3x3() @ bitangent).normalized()
|
||||
matrix[2].xyz = (ray_obj_matrix.to_3x3() @ normal).normalized()
|
||||
matrix[3].xyz = ray.location + (ray.normal * self.offset)
|
||||
tangent = edge.calc_tangent(face_corner)
|
||||
normal = direction.cross(tangent)
|
||||
bitangent = normal.cross(tangent)
|
||||
|
||||
# destroy_temporary_bmesh
|
||||
temp_bm.free()
|
||||
del mesh
|
||||
# Construct Matrix
|
||||
matrix = Matrix.Identity(4)
|
||||
matrix[0].xyz = (ray_obj_matrix.to_3x3() @ tangent).normalized()
|
||||
matrix[1].xyz = (ray_obj_matrix.to_3x3() @ bitangent).normalized()
|
||||
matrix[2].xyz = (ray_obj_matrix.to_3x3() @ normal).normalized()
|
||||
matrix[3].xyz = ray.location + (ray.normal * self.offset)
|
||||
|
||||
matrix = matrix.transposed()
|
||||
location = ray.location + (ray.normal * self.offset)
|
||||
normal = ray.normal
|
||||
# destroy_temporary_bmesh
|
||||
temp_bm.free()
|
||||
del mesh
|
||||
|
||||
matrix = matrix.transposed()
|
||||
location = ray.location + (ray.normal * self.offset)
|
||||
normal = ray.normal
|
||||
|
||||
return matrix, location, normal
|
||||
|
||||
@@ -776,8 +520,7 @@ class CarverBase(bpy.types.Operator,
|
||||
context.scene.cursor.location)
|
||||
else:
|
||||
# Put the location at the closest point of the bounding box of all selected objects.
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse.initial)
|
||||
ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse.initial)
|
||||
ray_origin, ray_direction = region_2d_to_ray_3d(region, rv3d, self.mouse.initial)
|
||||
|
||||
corners = []
|
||||
for obj in self.objects.selected:
|
||||
@@ -803,8 +546,7 @@ class CarverBase(bpy.types.Operator,
|
||||
matrix = Matrix.Identity(4)
|
||||
normal = matrix.col[2].xyz
|
||||
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse.initial)
|
||||
ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse.initial)
|
||||
ray_origin, ray_direction = region_2d_to_ray_3d(region, rv3d, self.mouse.initial)
|
||||
t = (matrix.translation.z - ray_origin.z) / ray_direction.z
|
||||
location = ray_origin + ray_direction * t
|
||||
matrix.translation = location
|
||||
@@ -851,7 +593,6 @@ class CarverBase(bpy.types.Operator,
|
||||
|
||||
cutter = self.cutter.obj
|
||||
|
||||
# Add Modifier(s)
|
||||
for obj in self.objects.selected:
|
||||
mod = add_boolean_modifier(self, context, obj,
|
||||
cutter, "DIFFERENCE",
|
||||
@@ -877,7 +618,7 @@ class CarverBase(bpy.types.Operator,
|
||||
obj.modifiers.remove(mod)
|
||||
|
||||
if not intersecting_canvases:
|
||||
self.finalize(context, clean_up=True)
|
||||
self.finalize(context)
|
||||
return
|
||||
|
||||
# Select all faces of the cutter so that newly created faces in canvas
|
||||
@@ -920,7 +661,7 @@ class CarverBase(bpy.types.Operator,
|
||||
for obj in intersecting_canvases:
|
||||
obj.booleans.canvas = True
|
||||
|
||||
self.finalize(context)
|
||||
self.finalize(context, clean_up=False)
|
||||
return
|
||||
|
||||
elif self.mode == 'DESTRUCTIVE':
|
||||
@@ -930,11 +671,11 @@ class CarverBase(bpy.types.Operator,
|
||||
modifiers = get_modifiers_to_apply(context, obj, [modifiers])
|
||||
apply_modifiers(context, obj, modifiers, force_clean=True)
|
||||
|
||||
self.finalize(context, clean_up=True)
|
||||
self.finalize(context)
|
||||
return
|
||||
|
||||
|
||||
def finalize(self, context, clean_up=False, abort=False):
|
||||
def finalize(self, context, clean_up=True, abort=False):
|
||||
"""
|
||||
Finalize and clean-up after the operation ends.
|
||||
Regardless of whether it was confirmed or cancelled.
|
||||
@@ -943,15 +684,19 @@ class CarverBase(bpy.types.Operator,
|
||||
# Operation was aborted, or successfully finished in the Destructive mode.
|
||||
# Delete everything created by the operator (i.e. cutter).
|
||||
if clean_up:
|
||||
delete_cutter(self.cutter.obj)
|
||||
delete_object(self.cutter.obj)
|
||||
self.cutter.bm.free()
|
||||
delete_empty_collection()
|
||||
delete_empty_collection(context)
|
||||
|
||||
# Remove modifiers added by the operator.
|
||||
if abort:
|
||||
for obj, mod in self.objects.modifiers.items():
|
||||
obj.modifiers.remove(mod)
|
||||
|
||||
# Clean-up temporary changes made by operator.
|
||||
if self.effects.array:
|
||||
self.effects.array.use_pin_to_last = False
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._handler, 'WINDOW')
|
||||
context.workspace.status_text_set(None)
|
||||
context.window.cursor_set('DEFAULT' if context.mode == 'OBJECT' else 'CROSSHAIR')
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
import bpy
|
||||
from bpy_extras import view3d_utils
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from ...functions.object import (
|
||||
set_object_origin,
|
||||
)
|
||||
from ...functions.view import (
|
||||
region_2d_to_plane_3d,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ CLASSES ------------------------------ ####
|
||||
|
||||
class CarverEvents():
|
||||
|
||||
# Private Methods.
|
||||
def _custom_modifier_event(self, context, event, modifier,
|
||||
cursor='NONE', store_values=False, restore_mouse=True,
|
||||
postprocess=None):
|
||||
"""Creates a custom modifier event when the key is held down."""
|
||||
|
||||
# Initialize Modifier Phase
|
||||
if event.value == 'PRESS':
|
||||
if self.phase in ("DRAW", "EXTRUDE"):
|
||||
self._stored_phase = self.phase
|
||||
self.phase = modifier
|
||||
self.mouse.cached = self.mouse.current
|
||||
context.window.cursor_set(cursor)
|
||||
|
||||
if store_values:
|
||||
# Store center of the geometry as a Vector.
|
||||
verts = [v.co for v in self.cutter.bm.verts]
|
||||
center = sum(verts, Vector()) / len(verts)
|
||||
self.cutter.center = self.cutter.obj.matrix_world @ center
|
||||
|
||||
# End Modifier Phase
|
||||
elif event.value == 'RELEASE':
|
||||
if self.phase == modifier:
|
||||
context.window.cursor_set('MUTE')
|
||||
if restore_mouse:
|
||||
context.window.cursor_warp(int(self.mouse.cached[0]), int(self.mouse.cached[1]) + 100)
|
||||
self.mouse.current = self.mouse.cached
|
||||
self.phase = self._stored_phase
|
||||
|
||||
if postprocess is not None:
|
||||
postprocess(self)
|
||||
|
||||
return self._stored_phase
|
||||
|
||||
|
||||
# Public Methods.
|
||||
def event_aspect(self, context, event):
|
||||
"""Modifier key for changing the aspect of the shape."""
|
||||
|
||||
if event.shift and self.phase == "DRAW":
|
||||
if self._initial_aspect == 'FREE':
|
||||
self.aspect = 'FIXED'
|
||||
elif self._initial_aspect == 'FIXED':
|
||||
self.aspect = 'FREE'
|
||||
else:
|
||||
self.aspect = self._initial_aspect
|
||||
|
||||
|
||||
def event_origin(self, context, event):
|
||||
"""Modifier key for changing the origin of the shape."""
|
||||
|
||||
if event.alt and self.phase == "DRAW":
|
||||
if self._initial_origin == 'EDGE':
|
||||
self.origin = 'CENTER'
|
||||
elif self._initial_origin == 'CENTER':
|
||||
self.origin = 'EDGE'
|
||||
else:
|
||||
self.origin = self._initial_origin
|
||||
|
||||
|
||||
def event_rotate(self, context, event):
|
||||
"""Modifier key for rotating the shape."""
|
||||
|
||||
def _remove_rotate_phase_properties(self):
|
||||
del self._stored_mouse_pos_3d
|
||||
del self._stored_rotation
|
||||
del self._stored_cutter_center
|
||||
del self._stored_cutter_euler
|
||||
|
||||
# Restore origin at edge (first vertex).
|
||||
if self.origin == 'EDGE':
|
||||
self.cutter.bm.verts.ensure_lookup_table()
|
||||
point = self.cutter.bm.verts[0].co
|
||||
set_object_origin(self.cutter.obj, self.cutter.bm, point='CUSTOM', custom=point)
|
||||
|
||||
|
||||
# Set correct phase.
|
||||
if event.type == 'R':
|
||||
_rm = False if self._stored_phase == "DRAW" else True
|
||||
stored_phase = self._custom_modifier_event(context, event, "ROTATE",
|
||||
cursor='MOVE_X', store_values=True, restore_mouse=_rm,
|
||||
postprocess=_remove_rotate_phase_properties)
|
||||
|
||||
if self.phase == "ROTATE":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
# Project current mouse position onto the workplane.
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
if current_mouse_pos_3d is not None:
|
||||
# Store values.
|
||||
obj = self.cutter.obj
|
||||
if not hasattr(self, '_stored_mouse_pos_3d'):
|
||||
self._stored_mouse_pos_3d = current_mouse_pos_3d.copy()
|
||||
self._stored_rotation = self.rotation
|
||||
self._stored_cutter_center = self.cutter.center
|
||||
self._stored_cutter_euler = obj.rotation_euler.copy()
|
||||
|
||||
# Calculate angle and direction.
|
||||
to_start = (self._stored_mouse_pos_3d - self._stored_cutter_center).normalized()
|
||||
to_current = (current_mouse_pos_3d - self._stored_cutter_center).normalized()
|
||||
|
||||
angle = to_start.angle(to_current)
|
||||
cross = to_start.cross(to_current)
|
||||
if cross.dot(self.workplane.normal) < 0:
|
||||
angle = -angle
|
||||
|
||||
if abs(angle) > 0.0001:
|
||||
self.rotation = self._stored_rotation + angle
|
||||
|
||||
# Offset the object location when drawing from edge to move rotation pivot to center.
|
||||
if self.origin == 'EDGE':
|
||||
set_object_origin(obj, self.cutter.bm, point='CENTER_OBJ')
|
||||
|
||||
# Calculate rotation amount.
|
||||
rotation_total = Matrix.Rotation(self.rotation, 4, self.workplane.normal)
|
||||
rotation_stored = Matrix.Rotation(self._stored_rotation, 4, self.workplane.normal)
|
||||
rotation_matrix = rotation_total @ rotation_stored.inverted()
|
||||
new_rot = rotation_matrix @ self._stored_cutter_euler.to_matrix().to_4x4()
|
||||
|
||||
# Rotate.
|
||||
obj.rotation_euler = new_rot.to_euler()
|
||||
|
||||
|
||||
def event_bevel(self, context, event):
|
||||
"""Modifier key for beveling the shape."""
|
||||
|
||||
def _remove_empty_bevel_modifier(self):
|
||||
bevel = self.effects.bevel
|
||||
if bevel.width == 0:
|
||||
self.cutter.obj.modifiers.remove(bevel)
|
||||
self.effects.bevel = None
|
||||
|
||||
if self.effects.weld is not None:
|
||||
self.cutter.obj.modifiers.remove(self.effects.weld)
|
||||
self.effects.weld = None
|
||||
|
||||
|
||||
if self.shape != 'BOX':
|
||||
return
|
||||
|
||||
# Set correct phase.
|
||||
if event.type == 'B':
|
||||
stored_phase = self._custom_modifier_event(context, event, "BEVEL",
|
||||
cursor='PICK_AREA', store_values=True,
|
||||
postprocess=_remove_empty_bevel_modifier)
|
||||
|
||||
if self.phase == "BEVEL":
|
||||
self.use_bevel = True
|
||||
|
||||
# Initialize bevel effect if it doesn't exist.
|
||||
if self.effects.bevel is None:
|
||||
self.bevel_width = 0
|
||||
affect = 'VERTICES' if stored_phase == "DRAW" else 'EDGES'
|
||||
self.effects.add_bevel_modifier(self, affect=affect)
|
||||
|
||||
# Force the geometry to update.
|
||||
if stored_phase == "DRAW":
|
||||
self.update_cutter_shape(context)
|
||||
elif stored_phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
# Calculate bevel width.
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
self.mouse.cached_3d = view3d_utils.region_2d_to_location_3d(region, rv3d,
|
||||
self.mouse.cached,
|
||||
self.cutter.center)
|
||||
self.mouse.current_3d = view3d_utils.region_2d_to_location_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
self.cutter.center)
|
||||
d = (self.cutter.center - self.mouse.current_3d).length - (self.cutter.center - self.mouse.cached_3d).length
|
||||
self.bevel_width = d * 0.2
|
||||
|
||||
# Adjust bevel segments.
|
||||
if event.type == 'WHEELUPMOUSE':
|
||||
self.bevel_segments += 1
|
||||
elif event.type == 'WHEELDOWNMOUSE':
|
||||
self.bevel_segments -= 1
|
||||
|
||||
# Update modifier.
|
||||
self.effects.update(self, "BEVEL")
|
||||
|
||||
|
||||
def event_array(self, context, event):
|
||||
"""Modifier key for arraying the shape."""
|
||||
|
||||
# Add duplicates.
|
||||
if event.type == 'LEFT_ARROW' and event.value == 'PRESS':
|
||||
self.columns -= 1
|
||||
if event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
|
||||
self.columns += 1
|
||||
if event.type == 'DOWN_ARROW' and event.value == 'PRESS':
|
||||
self.rows -= 1
|
||||
if event.type == 'UP_ARROW' and event.value == 'PRESS':
|
||||
self.rows += 1
|
||||
|
||||
if event.type in ['LEFT_ARROW',
|
||||
'RIGHT_ARROW',
|
||||
'DOWN_ARROW',
|
||||
'UP_ARROW',] and event.value == 'PRESS':
|
||||
self.effects.update(self, "ARRAY_COUNT")
|
||||
|
||||
# Force the geometry to update.
|
||||
if self.phase == "DRAW":
|
||||
self.update_cutter_shape(context)
|
||||
elif self.phase == "EXTRUDE":
|
||||
self.set_extrusion_depth(context)
|
||||
|
||||
# Adjust gap.
|
||||
if (self.rows > 1 or self.columns > 1) and (event.type == 'A'):
|
||||
stored_phase = self._custom_modifier_event(context, event, "ARRAY",
|
||||
cursor='MUTE', store_values=True)
|
||||
|
||||
if self.phase == "ARRAY":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
self.mouse.cached_3d = view3d_utils.region_2d_to_location_3d(region, rv3d,
|
||||
self.mouse.cached,
|
||||
self.cutter.center)
|
||||
self.mouse.current_3d = view3d_utils.region_2d_to_location_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
self.cutter.center)
|
||||
d = (self.cutter.center - self.mouse.current_3d).length - (self.cutter.center - self.mouse.cached_3d).length
|
||||
self.gap = 1 + (d * 0.2)
|
||||
|
||||
self.effects.update(self, "ARRAY_GAP")
|
||||
|
||||
|
||||
def event_flip(self, context, event):
|
||||
"""Modifier key for flipping the direction of extrusion."""
|
||||
|
||||
if event.type == 'F' and event.value == 'PRESS':
|
||||
if self.phase == 'EXTRUDE':
|
||||
self.flip_direction = not self.flip_direction
|
||||
|
||||
|
||||
def event_move(self, context, event):
|
||||
"""Modifier key for moving the shape."""
|
||||
|
||||
def _remove_move_phase_properties(self):
|
||||
del self._stored_cutter_location
|
||||
self.mouse.cached_3d = None
|
||||
|
||||
|
||||
if event.type == 'SPACE':
|
||||
stored_phase = self._custom_modifier_event(context, event, "MOVE",
|
||||
cursor='SCROLL_XY', restore_mouse=False,
|
||||
postprocess=_remove_move_phase_properties)
|
||||
|
||||
if self.phase == "MOVE":
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
# Project current mouse position onto the workplane.
|
||||
current_mouse_pos_3d = region_2d_to_plane_3d(region, rv3d,
|
||||
self.mouse.current,
|
||||
(self.workplane.location, self.workplane.normal))
|
||||
if current_mouse_pos_3d is not None:
|
||||
if not hasattr(self, '_stored_cutter_location'):
|
||||
self.mouse.cached_3d = current_mouse_pos_3d.copy()
|
||||
self._stored_cutter_location = self.cutter.obj.location.copy()
|
||||
|
||||
offset = current_mouse_pos_3d - self.mouse.cached_3d
|
||||
self.cutter.obj.location = self._stored_cutter_location + offset
|
||||
@@ -1,40 +1,59 @@
|
||||
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
|
||||
from ...ui.icons import (
|
||||
get_custom_icon,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ PROPERTIES ------------------------------ ####
|
||||
|
||||
class CarverPropsOperator():
|
||||
|
||||
# NOTE: Defining enum items inside a function is necessary to allow correct import of custom icons.
|
||||
# Without this, classes (and their properties) are registered before `register` function of modules
|
||||
# are called, resulting in empty preview collections.
|
||||
def get_depth_items(self, context):
|
||||
icon_measure = get_custom_icon("MEASURE")
|
||||
icon_cpu = get_custom_icon("CPU")
|
||||
|
||||
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),
|
||||
]
|
||||
return items
|
||||
|
||||
# OPERATOR-properties
|
||||
mode: bpy.props.EnumProperty(
|
||||
name = "Mode",
|
||||
items = (('DESTRUCTIVE', "Destructive",
|
||||
"Boolean cutters are immediatelly applied and removed after the cut", 'MESH_DATA', 0),
|
||||
"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)),
|
||||
"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)),
|
||||
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 = (('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',
|
||||
items = get_depth_items,
|
||||
default = 0,
|
||||
)
|
||||
|
||||
|
||||
@@ -72,7 +91,7 @@ class CarverPropsShape():
|
||||
flip_direction: bpy.props.BoolProperty(
|
||||
name = "Flip Direction",
|
||||
description = "Change which way the geometry is extruded",
|
||||
options = {'SKIP_SAVE', 'HIDDEN', 'SKIP_PRESET', },
|
||||
options = {'SKIP_SAVE', 'HIDDEN'},
|
||||
default = False,
|
||||
)
|
||||
|
||||
@@ -88,7 +107,7 @@ class CarverPropsModifier():
|
||||
)
|
||||
pin: bpy.props.BoolProperty(
|
||||
name = "Pin Boolean Modifier",
|
||||
description = ("Boolean modifier will be placed first in modifier stack, above other modifier (if there are any).\n"
|
||||
description = ("Always make new Boolean modifiers first in the modifier stack.\n"
|
||||
"NOTE: Order of modifiers can drastically affect the result (especially in destructive mode)"),
|
||||
default = True,
|
||||
)
|
||||
@@ -98,13 +117,13 @@ class CarverPropsCutter():
|
||||
# CUTTER-properties
|
||||
hide: bpy.props.BoolProperty(
|
||||
name = "Hide Cutter",
|
||||
description = ("Hide cutter objects in the viewport after they're created."),
|
||||
description = ("Hide cutter objects in the viewport after they are created."),
|
||||
default = True,
|
||||
)
|
||||
parent: bpy.props.BoolProperty(
|
||||
name = "Parent to Canvas",
|
||||
description = ("Cutters will be parented to active object being cut, even if cutting multiple objects.\n"
|
||||
"If there is no active object in selection cutters parent might be chosen seemingly randomly"),
|
||||
description = ("Parent new cutters to active object (even if they are cutting multiple objects).\n"
|
||||
"If there is no active object in the selection, parent might be chosen randomly"),
|
||||
default = True,
|
||||
)
|
||||
display: bpy.props.EnumProperty(
|
||||
@@ -115,24 +134,29 @@ class CarverPropsCutter():
|
||||
)
|
||||
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")),
|
||||
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",
|
||||
description = ("Cutter object will be shaded smooth with sharp edges (above specified degrees) marked as sharp\n"
|
||||
"NOTE: This is a one time operator. 'Smooth by Angle' modifier will not be added on cutter"),
|
||||
description = ("Add 'Smooth by Angle' modifier to cutter object"),
|
||||
default = True,
|
||||
)
|
||||
sharp_angle: bpy.props.FloatProperty(
|
||||
name = "Angle",
|
||||
description = "Maximum face angle for sharp edges",
|
||||
subtype = "ANGLE",
|
||||
precision = 3,
|
||||
min = 0, max = math.pi,
|
||||
default = 0.523599,
|
||||
)
|
||||
@@ -142,13 +166,13 @@ class CarverPropsArray():
|
||||
# ARRAY-properties
|
||||
rows: bpy.props.IntProperty(
|
||||
name = "Rows",
|
||||
description = "Number of times shape is duplicated horizontally",
|
||||
description = "Number of times the shape is duplicated horizontally",
|
||||
min = 1, soft_max = 16,
|
||||
default = 1,
|
||||
)
|
||||
columns: bpy.props.IntProperty(
|
||||
name = "Columns",
|
||||
description = "Number of times shape is duplicated vertically",
|
||||
description = "Number of times the shape is duplicated vertically",
|
||||
min = 1, soft_max = 16,
|
||||
default = 1,
|
||||
)
|
||||
|
||||
@@ -9,13 +9,14 @@ from ...functions.mesh import (
|
||||
)
|
||||
from ...functions.modifier import (
|
||||
add_modifier_asset,
|
||||
update_modifier_input,
|
||||
)
|
||||
|
||||
|
||||
#### ------------------------------ CLASSES ------------------------------ ####
|
||||
|
||||
class Selection:
|
||||
"""Storage of viable selected and active object(s) throughout the modal."""
|
||||
"""Storage of viable cannvas objects and their Boolean modifiers."""
|
||||
|
||||
def __init__(self, selected, active):
|
||||
self.selected: list = selected
|
||||
@@ -24,10 +25,7 @@ class Selection:
|
||||
|
||||
|
||||
class Mouse:
|
||||
"""
|
||||
Mouse positions throughout different phases of the modal operator.
|
||||
Each class variable is a 2D vector in screen space (x, y).
|
||||
"""
|
||||
"""Mouse positions throughout different phases of the modal operator."""
|
||||
|
||||
def __init__(self):
|
||||
self.initial = Vector()
|
||||
@@ -99,17 +97,17 @@ class Effects:
|
||||
return self
|
||||
|
||||
def update(self, cls, effect):
|
||||
"""Update bevel modifier during modal."""
|
||||
"""Update cutter modifiers during modal."""
|
||||
|
||||
# Update array count.
|
||||
if effect == '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
|
||||
update_modifier_input(self.array, "Socket_2", cls.columns)
|
||||
update_modifier_input(self.array, "Socket_3", cls.rows)
|
||||
|
||||
# Remove modifier if it's no longer needed.
|
||||
if cls.columns == 1 and cls.rows == 1:
|
||||
@@ -117,12 +115,12 @@ class Effects:
|
||||
self.array = None
|
||||
|
||||
# Update array gap.
|
||||
if effect == 'ARRAY_GAP':
|
||||
if cls.columns > 1 or cls.row > 1:
|
||||
if effect == "ARRAY_GAP":
|
||||
if cls.columns > 1 or cls.rows > 1:
|
||||
if self.array is not None:
|
||||
self.array["Socket_4"] = cls.gap
|
||||
update_modifier_input(self.array, "Socket_4", cls.gap)
|
||||
|
||||
# Force the modifier to update in viewport.
|
||||
# NOTE: Force the modifier to update in viewport when key is pressed.
|
||||
self.array.show_viewport = False
|
||||
self.array.show_viewport = True
|
||||
|
||||
@@ -150,15 +148,16 @@ class Effects:
|
||||
|
||||
# Columns
|
||||
if cls.columns > 1:
|
||||
mod["Socket_2"] = cls.columns
|
||||
update_modifier_input(mod, "Socket_2", cls.columns)
|
||||
|
||||
# Rows
|
||||
if cls.rows > 1:
|
||||
mod["Socket_3"] = cls.rows
|
||||
update_modifier_input(mod, "Socket_3", cls.rows)
|
||||
|
||||
# Gap
|
||||
mod["Socket_4"] = cls.gap
|
||||
update_modifier_input(mod, "Socket_4", cls.gap)
|
||||
|
||||
mod.use_pin_to_last = True
|
||||
self.array = mod
|
||||
|
||||
|
||||
@@ -233,7 +232,7 @@ class Effects:
|
||||
|
||||
# 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."""
|
||||
"""Adds a 'Smooth by Angle' modifier on the cutter object (a.k.a. Auto Smooth)."""
|
||||
|
||||
obj = cls.cutter.obj
|
||||
mesh = cls.cutter.mesh
|
||||
@@ -262,7 +261,7 @@ class Effects:
|
||||
|
||||
mod = obj.modifiers.active
|
||||
|
||||
# Try loading the node group manually if `bpy.ops` operators fail.
|
||||
# Try loading the node group manually if `bpy.ops` operators failed.
|
||||
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)
|
||||
@@ -274,13 +273,13 @@ class Effects:
|
||||
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.
|
||||
# Set smoothing angle (necessary for all methods except `shade_auto_smooth()`).
|
||||
for face in bm.faces:
|
||||
face.smooth = True
|
||||
bm.to_mesh(mesh)
|
||||
|
||||
mod.use_pin_to_last = True
|
||||
mod["Input_1"] = cls.sharp_angle
|
||||
update_modifier_input(mod, "Input_1", cls.sharp_angle)
|
||||
|
||||
self.smooth = mod
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import bpy
|
||||
from ... import __package__ as base_package
|
||||
|
||||
|
||||
#### ------------------------------ /toolbar/ ------------------------------ ####
|
||||
@@ -14,7 +13,7 @@ def carver_ui_common(context, layout, props):
|
||||
layout.prop(props, "solver", expand=True)
|
||||
|
||||
else:
|
||||
# Use labels for Properties editor/sidebar.
|
||||
# NOTE: Use labels for Properties editor/sidebar.
|
||||
layout.prop(props, "mode", text="Mode")
|
||||
layout.prop(props, "alignment", text="Alignment")
|
||||
layout.prop(props, "depth", text="Depth")
|
||||
@@ -43,20 +42,17 @@ class TOPBAR_PT_carver_shape(bpy.types.Panel):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
tool = context.workspace.tools.from_space_view3d_mode('OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH')
|
||||
mode = 'OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH'
|
||||
tool = context.workspace.tools.from_space_view3d_mode(mode)
|
||||
props = tool.operator_properties(tool.idname)
|
||||
|
||||
# Box & Circle
|
||||
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")
|
||||
else:
|
||||
props = tool.operator_properties("object.carve_circle")
|
||||
|
||||
if tool.idname == "object.carve_circle":
|
||||
layout.prop(props, "subdivision", text="Vertices")
|
||||
layout.prop(props, "rotation")
|
||||
layout.prop(props, "aspect", expand=True)
|
||||
layout.prop(props, "origin", expand=True)
|
||||
layout.prop(props, "aspect", expand=True)
|
||||
|
||||
if props.alignment == 'SURFACE':
|
||||
layout.prop(props, "orientation")
|
||||
@@ -67,7 +63,6 @@ class TOPBAR_PT_carver_shape(bpy.types.Panel):
|
||||
|
||||
# Polyline
|
||||
elif tool.idname == "object.carve_polyline":
|
||||
props = tool.operator_properties("object.carve_polyline")
|
||||
if props.alignment == 'SURFACE':
|
||||
layout.prop(props, "offset", text="Offset")
|
||||
layout.prop(props, "align_to_all")
|
||||
@@ -85,13 +80,9 @@ class TOPBAR_PT_carver_effects(bpy.types.Panel):
|
||||
layout.use_property_split = True
|
||||
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":
|
||||
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")
|
||||
mode = 'OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH'
|
||||
tool = context.workspace.tools.from_space_view3d_mode(mode)
|
||||
props = tool.operator_properties(tool.idname)
|
||||
|
||||
# Bevel
|
||||
if tool.idname == 'object.carve_box':
|
||||
@@ -116,6 +107,7 @@ class TOPBAR_PT_carver_effects(bpy.types.Panel):
|
||||
col.prop(props, "rows")
|
||||
col.prop(props, "gap")
|
||||
|
||||
|
||||
class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
bl_label = "Carver Cutter"
|
||||
bl_idname = "TOPBAR_PT_carver_cutter"
|
||||
@@ -128,25 +120,25 @@ class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
layout.use_property_split = True
|
||||
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":
|
||||
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")
|
||||
mode = 'OBJECT' if context.mode == 'OBJECT' else 'EDIT_MESH'
|
||||
tool = context.workspace.tools.from_space_view3d_mode(mode)
|
||||
props = tool.operator_properties(tool.idname)
|
||||
|
||||
# modifier_&_cutter
|
||||
# Modifier properties.
|
||||
col = layout.column()
|
||||
col.prop(props, "pin", text="Pin Modifier")
|
||||
col.separator()
|
||||
|
||||
# Cutter object properties.
|
||||
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
|
||||
# Auto Smooth.
|
||||
layout.separator()
|
||||
col = layout.column(align=True)
|
||||
col.prop(props, "auto_smooth", text="Auto Smooth")
|
||||
@@ -159,11 +151,11 @@ class TOPBAR_PT_carver_cutter(bpy.types.Panel):
|
||||
|
||||
#### ------------------------------ REGISTRATION ------------------------------ ####
|
||||
|
||||
classes = [
|
||||
classes = (
|
||||
TOPBAR_PT_carver_shape,
|
||||
TOPBAR_PT_carver_effects,
|
||||
TOPBAR_PT_carver_cutter,
|
||||
]
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
|
||||
Reference in New Issue
Block a user