import bpy
import bmesh
import bpy.utils.previews
from math import radians
from ... utility import addon, collections, object, math , modifier, operator_override, context_copy
from ... utils.blender_ui import get_dpi_factor
from ... utils.context import ExecutionContext
from bpy.props import EnumProperty, FloatProperty, BoolProperty
from mathutils import Vector, Matrix, Euler
class HOPS_OT_Conv_To_Shape(bpy.types.Operator):
bl_idname = "hops.to_shape"
bl_label = "To_Shape"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """To_Shape
Convert selection to a myriad of shapes including empties
LMB - Converts selection to single primitive
LMB+SHIFT - Creates primitive per object
LMB+CTRL - Parent selection to empties
"""
#general
individual: bpy.props.BoolProperty(
name="Individual", description="Create shape per object using its local space", default = False)
equalize: bpy.props.BoolProperty(
name="Equalize", description="Make all dimensions equal", default = False)
equalize_radius: bpy.props.BoolProperty(
name="Radius only", description="Make all but longest dimension equal", default = False)
modified: bpy.props.BoolProperty(
name="Modified",
description="Take the bounding box dimensions from the modified object",
default=True)
alignment: bpy.props.EnumProperty(
name="Alignment",
description="What axis to allign along",
items=[
('AUTO_OBJECT', "AUTO_OBJECT", "Allign along longest object dimension"),
('AUTO_MESH', "AUTO_MESH", "Allign along longest mesh dimension"),
('X', "X", "Allign along X axis"),
('Y', "Y", "Allign along Y axis"),
('Z', "Z", "Allign along Z axis"),
],
default='AUTO_OBJECT')
scale: bpy.props.FloatProperty(
name="Scale",
description="Scale",
default=1)
copy_1st_bvl: bpy.props.BoolProperty(
name="Copy 1st bevel",
description="Copy 1st bevel of targe object",
default=False)
copy_1st_bvl_angle_only: bpy.props.BoolProperty(
name="Angle only",
description="Only consider Angle limit mode Bevels",
default=False)
parent_shape: bpy.props.BoolProperty(
name="Parent shape to selection",
description="Parent shape to selectio",
default=False)
parent_shape_inverse: bpy.props.BoolProperty(
name="Inverse",
description="Invert parenting",
default=False)
##cylinder
cyl_segments: bpy.props.IntProperty(
name="Segments",
description="Number of segments",
default=32,
min=3)
cyl_diameter1: bpy.props.FloatProperty(
name="Diameter 1",
description="Diameter 1",
default=1)
cyl_diameter2: bpy.props.FloatProperty(
name="Diameter 2",
description="Diameter 2",
default=1)
##sphere
sphere_segments: bpy.props.IntProperty(
name="Segments",
description="Nmber of segments",
default=32,
min=3)
sphere_rings: bpy.props.IntProperty(
name="Rings",
description="Number of rings",
default=16,
min=3)
sphere_diameter: bpy.props.FloatProperty(
name="Diameter",
description="Diameter",
default=1)
##plane
plane_alignment: bpy.props.EnumProperty(
name="Plane axis",
description="What side to create planeo on",
items=[
('+X', "+X", "Create a plane on the +X axis"),
('+Y', "+Y", "Create a plane on the +Y axis"),
('+Z', "+Z", "Create a plane on the +Z axis"),
('-X', "-X", "Create a plane on the -X axis"),
('-Y', "-Y", "Create a plane on the -Y axis"),
('-Z', "-Z", "Create a plane on the -Z axis")],
default='+X')
plane_offset: bpy.props.FloatProperty(
name="Offset",
description="Offset plane from selection",
default=0)
# empty
empty_display: bpy.props.EnumProperty(
name="Display type",
description="Empty display type",
items=[
('PLAIN_AXES', 'PLAIN_AXES', 'PLAIN_AXES'),
('SINGLE_ARROW', 'SINGLE_ARROW', 'SINGLE_ARROW'),
('CIRCLE', 'CIRCLE', 'CIRCLE'),
('CUBE', 'CUBE', 'CUBE'),
('SPHERE', 'SPHERE', 'SPHERE'),
('ARROWS', 'ARROWS', 'ARROWS'),
('CONE', 'CONE', 'CONE') ],
default='PLAIN_AXES')
# Convex_Hull
dissolve_angle: bpy.props.FloatProperty(
name = "Dissolve Angles",
description = "Dissolve faces below this angle",
default = 5,
min = 0,
max = 180
)
# Decap
decap_thickness: bpy.props.FloatProperty(
name = "Thickness",
description = "Relative thickness of remaining piece",
default = 0.9,
min = 0,
max = 1
)
decap_center: bpy.props.FloatProperty(
name = "Center",
description = "Relative center of remaining piece",
default = 0.5,
min = 0,
max = 1
)
decap_fill_pos : bpy.props.BoolProperty(
name = "Fill+",
description = "Fill with N-gon on positive side",
default = True,
)
decap_fill_neg : bpy.props.BoolProperty(
name = "Fill-",
description = "Fill with N-gon on negative side",
default = True,
)
decap_axis: bpy.props.EnumProperty(
name="Axis",
description="Slicing axis",
items=[
('X', "X", "X"),
('Y', "Y", "Y"),
('Z', "Z", "Z"),
],
default='Z')
decap_hide_orig : bpy.props.BoolProperty(
name = "Hide original",
description = "Hide original out of the way",
default = False,
)
decap_keep_caps : bpy.props.BoolProperty(
name = "Keep caps",
description = "Keep caps as sepearate objects",
default = False,
)
decap_keep_caps_array : bpy.props.BoolProperty(
name = "Array-compatible",
description = "Adjust caps to work as array caps",
default = False,
)
decap_solidify : bpy.props.BoolProperty(
name = 'Solidify',
description = 'Solidify 0 thickness slice to match original',
default = False
)
decap_solidify_multi : bpy.props.FloatProperty(
name = 'Solidify multiplier',
description = 'Solidify multiplier',
default = 1
)
#selector
primitive_type: bpy.props.EnumProperty(
name="Primitive",
description="Primitive type",
items=[
('Cube', "Cube", "Cube"),
('Plane', "Plane", "Plane"),
('Cylinder', "Cylinder", "Cylinder"),
('Sphere', "Sphere", "Sphere"),
# ('Monkey', "Monkey", "Monkey"),
('Empty', 'Empty', 'Empty'),
('Convex_Hull', 'Convex Hull', 'Convex Hull'),
('Decap', 'Decap', 'Create visual copy and decaps it'),
],
default='Cube')
def draw(self, context):
self.layout.prop(self, 'primitive_type')
self.layout.prop(self, 'individual')
if self.primitive_type not in {'Convex_Hull','Decap'}:
self.layout.prop(self, 'equalize')
if self.equalize:
self.layout.prop(self, 'equalize_radius')
self.layout.prop(self, 'modified')
self.layout.prop(self, 'scale')
self.layout.prop(self, 'parent_shape')
if self.parent_shape:
self.layout.prop(self, 'parent_shape_inverse')
if self.primitive_type not in self.no_bevel_club :
self.layout.prop(self, 'copy_1st_bvl')
if self.copy_1st_bvl:
self.layout.prop(self, 'copy_1st_bvl_angle_only')
if self.primitive_type == 'Cylinder':
self.layout.prop(self, 'cyl_segments')
self.layout.prop(self, 'cyl_diameter1')
self.layout.prop(self, 'cyl_diameter2')
self.layout.prop(self, 'alignment')
elif self.primitive_type == 'Plane':
self.layout.prop(self, "plane_alignment")
self.layout.prop(self, "plane_offset")
elif self.primitive_type == 'Sphere':
self.layout.prop(self, "sphere_segments")
self.layout.prop(self, "sphere_rings")
self.layout.prop(self, "sphere_diameter")
self.layout.prop(self, 'alignment')
elif self.primitive_type == 'Empty':
self.layout.prop(self, "empty_display")
elif self.primitive_type == 'Convex_Hull':
self.layout.prop(self, "dissolve_angle")
elif self.primitive_type == 'Decap':
self.layout.prop(self, "decap_axis")
self.layout.prop(self, "decap_thickness")
if not self.decap_thickness:
self.layout.prop(self, 'decap_solidify')
self.layout.prop(self, 'decap_solidify_multi')
self.layout.prop(self, "decap_center")
self.layout.prop(self, "decap_fill_pos")
self.layout.prop(self, "decap_fill_neg")
self.layout.prop(self, 'decap_hide_orig')
self.layout.prop(self, 'decap_keep_caps')
if self.decap_keep_caps:
self.layout.prop(self, 'decap_keep_caps_array')
@classmethod
def poll(cls, context):
return context.active_object or context.selected_objects
def invoke (self, context, event):
if event.shift:
self.individual = True
else:
self.individual = False
if event.ctrl:
self.primitive_type = 'Empty'
self.individual = True
self.parent_shape = True
self.parent_shape_inverse = True
self.empty_display = 'CUBE'
self.no_bevel_club = {'Plane', 'Empty', 'Convex_Hull', 'Decap'}
bpy.context.view_layer.update()
self.active_obj_name = context.active_object.name if context.active_object else ''
self.selected = set(context.selected_objects)
if context.active_object and context.mode =='EDIT_MESH':
self.selected.add(context.active_object)
if context.mode == 'EDIT_MESH':
self.get_coords = self.get_coords_edit
self.deselect = lambda obj: None
self.set_active = lambda primitive : None
self.mesh_update = lambda obj: obj.update_from_editmode()
else:
self.get_coords = self.get_coords_obj
self.deselect = lambda obj: obj.select_set(False)
self.set_active = lambda primitive :set_active(primitive)
self.mesh_update = lambda obj: None
self.object_params = []
for obj in self.selected:
self.mesh_update(obj)
obj_data = Obj_data()
obj_data.object_name = obj.name
obj_data.object_matrix = obj.matrix_world.copy()
obj_data.bounds_base = self.get_coords(context, obj)
obj_data.bounds_final = self.get_coords(context, obj, modified=True)
obj_data.bounds_decap_base = self.get_coords_obj(context, obj)
obj_data.bounds_decap_final = self.get_coords_obj(context, obj, modified=True)
if len (obj_data.bounds_base)<2 or len(obj_data.bounds_final)<2 :
continue
if obj.type == 'EMPTY':
obj_data.dimensions_base = Vector((1,1,1))
obj_data.dimensions_final = Vector((1,1,1))
else:
obj_data.dimensions_base = math.dimensions(obj_data.bounds_base)
obj_data.dimensions_final = math.dimensions(obj_data.bounds_final)
obj_data.dimensions_decap_base = math.dimensions(obj_data.bounds_decap_base)
obj_data.dimensions_decap_final = math.dimensions(obj_data.bounds_decap_final)
obj_data.has_data = obj.type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'META'}
obj_data.center_base = math.coords_to_center(obj_data.bounds_base)
obj_data.center_final = math.coords_to_center(obj_data.bounds_final)
obj_data.center_decap_base = math.coords_to_center(obj_data.bounds_decap_base)
obj_data.center_decap_final = math.coords_to_center(obj_data.bounds_decap_final)
self.object_params.append(obj_data)
if not self.object_params:
return {'CANCELLED'}
self.created_shapes = set()
return self.execute(context)
def execute(self, context):
self.created_shapes.clear()
if self.individual:
for obj_data in self.object_params:
obj = bpy.data.objects[obj_data.object_name]
if self.modified:
mesh_location = obj_data.center_final
dimensions = obj_data.dimensions_final
else:
mesh_location = obj_data.center_base
dimensions = obj_data.dimensions_base
if self.primitive_type == 'Convex_Hull':
if not obj.data : continue
primitive = self.convex_hull(context, center = mesh_location , objects = [obj], scale_object = obj.matrix_world.to_scale())
primitive.matrix_world = matrix_transfrom (obj.matrix_world, scale= Vector ((1,1,1)) )
self.set_origin(primitive, obj.matrix_world @ mesh_location)
elif self.primitive_type == 'Decap':
if not obj.data : continue
primitives = self.decap(context, center = mesh_location , objects = [obj], dimensions=dimensions, scale_object = obj.matrix_world.to_scale() , world=False )
primitive = primitives[0]
offset_vectors = [Vector((0,0,0,)), Vector((0,0,0,)), Vector((0,0,0,)),]
if self.decap_keep_caps and self.decap_keep_caps_array:
axis_vect = [self.decap_axis == a for a in 'XYZ']
elem_mult = lambda vect1, vect2 : Vector(a*b for a,b in zip(vect1, vect2))
array_offset = elem_mult(primitive.dimensions, axis_vect)
offset_vectors = [
Vector((0,0,0,)),
+array_offset,
-array_offset
]
matrix = matrix_transfrom (obj.matrix_world, scale= Vector ((1,1,1)) )
loc = obj.matrix_world @ mesh_location
for prim, offset in zip (primitives, offset_vectors):
prim.matrix_world = matrix.copy()
prim.matrix_world.translation = loc + (matrix.to_quaternion() @ offset)
prim.data.transform(Matrix.Translation(-(matrix.inverted() @ loc)- offset) )
if not len(prim.data.vertices) and prim is not primitive:
me = prim.data
bpy.data.objects.remove(prim)
bpy.data.meshes.remove(me)
continue
self.created_shapes.add(prim)
elif self.primitive_type == 'Empty':
location = obj.matrix_world @ mesh_location
primitive = self.add_empty(context, obj, dimensions)
primitive.empty_display_size *= max (obj.matrix_world.to_scale())
primitive.empty_display_size *= self.scale
primitive.matrix_world = matrix_transfrom (obj.matrix_world, location=location, scale= Vector ((1,1,1)) )
else:
primitive = self.pirmitive_add(context, obj, mesh_location, dimensions, scale_object= obj.matrix_world.to_scale())
primitive.matrix_world = matrix_transfrom (obj.matrix_world, scale= Vector ((1,1,1)) )
self.center_origin(primitive,)
self.deselect(obj)
self.set_active(primitive)
self.created_shapes.add(primitive)
if self.parent_shape:
if self.parent_shape_inverse:
set_parent(obj, primitive)
else:
set_parent (primitive, obj)
else:
bbox_array=[]
bounds_attr = 'bounds'
if self.primitive_type == 'Decap':
bounds_attr += '_decap'
if self.modified:
bounds_attr += '_final'
else:
bounds_attr += '_base'
for obj_data in self.object_params:
obj = bpy.data.objects[obj_data.object_name]
bounds = getattr(obj_data, bounds_attr)
bbox_array.extend([obj_data.object_matrix @ vec for vec in bounds])
self.deselect(obj)
if len (bbox_array) <2:
return {'CANCELLED'}
bounds = math.coords_to_bounds(bbox_array)
mesh_location = math.coords_to_center(bounds)
dimensions = math.dimensions(bbox_array)
if self.primitive_type == 'Convex_Hull':
valid_objects = [bpy.data.objects[obj_data.object_name] for obj_data in self.object_params if obj_data.has_data ]
if not valid_objects: return {'FINISHED'}
primitive = self.convex_hull(context, center = mesh_location , objects = valid_objects, world=True)
self.set_origin(primitive, mesh_location)
elif self.primitive_type == 'Decap':
valid_objects = [bpy.data.objects[obj_data.object_name] for obj_data in self.object_params if obj_data.has_data ]
if not valid_objects: return {'FINISHED'}
primitives = self.decap(context, dimensions=dimensions, center = mesh_location , objects = valid_objects, world=True)
primitive = primitives[0]
offset_vectors = [Vector((0,0,0,)), Vector((0,0,0,)), Vector((0,0,0,)),]
if self.decap_keep_caps and self.decap_keep_caps_array:
axis_vect = [self.decap_axis == a for a in 'XYZ']
elem_mult = lambda vect1, vect2 : Vector(a*b for a,b in zip(vect1, vect2))
array_offset = elem_mult(primitive.dimensions, axis_vect)
offset_vectors = [
Vector((0,0,0,)),
+array_offset,
-array_offset
]
for prim, offset in zip (primitives, offset_vectors):
prim.matrix_world = Matrix()
prim.matrix_world.translation = mesh_location + offset
prim.data.transform(Matrix.Translation(- mesh_location - offset) )
if not len(prim.data.vertices) and prim is not primitive:
me = prim.data
bpy.data.objects.remove(prim)
bpy.data.meshes.remove(me)
continue
self.created_shapes.add(prim)
elif self.primitive_type == 'Empty':
primitive = self.add_empty(context, obj, dimensions)
primitive.empty_display_size *= self.scale
primitive.matrix_world = matrix_transfrom ( Matrix(), location=mesh_location, scale= Vector ((1,1,1)) )
else:
primitive = self.pirmitive_add(context, obj, mesh_location, dimensions)
self.center_origin(primitive)
if self.parent_shape:
if self.parent_shape_inverse:
for obj_data in self.object_params:
obj = bpy.data.objects[obj_data.object_name]
set_parent(obj, primitive)
elif self.active_obj_name:
set_parent (primitive, bpy.data.objects[self.active_obj_name])
self.set_active(primitive)
self.created_shapes.add(primitive)
override = context_copy(context)
override['edit_object'] = None
override['selected_objects'] = list(self.created_shapes)
override['mode'] = 'OBJECT'
operator_override(context, bpy.ops.hops.draw_wire_mesh_launcher, override, 'INVOKE_DEFAULT', target='SELECTED')
return {'FINISHED'}
def pirmitive_add (self, context, object, vector, dimensions, scale_object = Vector((1,1,1)), ) :
primitive = self.primitive_type
primitive_mesh = bpy.data.meshes.new(primitive)
primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)
col = collections.find_collection(context, object)
col.objects.link(primitive_obj)
bm = bmesh.new()
fit_matrix = math.get_sca_matrix(scale_object) @ Matrix.Translation(vector) @ math.get_sca_matrix(dimensions)
scale_center = Vector((0,0,0))
max_vec = fit_matrix @ Vector((0.5,0.5,0.5))
min_vec = fit_matrix @ Vector((-0.5,-0.5,-0.5))
final_dimesnions = math.dimensions( (min_vec, max_vec) )
final_dim_max = axle = max(final_dimesnions)
if primitive == 'Cube':
bmesh.ops.create_cube(bm)
elif primitive == 'Cylinder':
bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=self.cyl_segments, radius1=self.cyl_diameter1/2, radius2=self.cyl_diameter2/2, depth =1)
elif primitive == 'Monkey':
bmesh.ops.create_monkey(bm, matrix = math.get_sca_matrix([0.5, 0.5 ,0.5]))
elif primitive == 'Plane':
bm = self.plane_mesh ()
bm.faces.ensure_lookup_table()
scale_center = bm.faces[0].calc_center_median()
elif primitive == 'Sphere':
bmesh.ops.create_uvsphere (bm, u_segments = self.sphere_segments, v_segments = self.sphere_rings, radius = self.sphere_diameter/2 )
if primitive in {'Cylinder', 'Sphere'}:
XYZ = 'XYZ'
if self.alignment == 'AUTO_OBJECT':
index = list(final_dimesnions).index(axle)
alignment = XYZ[index]
elif self.alignment == 'AUTO_MESH':
index = list(dimensions).index(max(dimensions))
alignment = XYZ[index]
else:
alignment = self.alignment
index = XYZ.index(self.alignment)
if alignment == 'X':
eul = Euler((0.0, radians(90.0), 0.0), XYZ)
elif alignment == 'Y':
eul = Euler((radians(-90.0), 0.0, 0.0), XYZ)
elif alignment == 'Z':
eul = Euler((0.0, 0.0, 0.0), XYZ)
axle = final_dimesnions[index]
bmesh.ops.transform(bm, matrix = math.get_rot_matrix(eul.to_quaternion()), verts = bm.verts)
bmesh.ops.transform(bm, matrix = fit_matrix , verts = bm.verts)
scale_vector = Vector((1,1,1))
if self.equalize:
if self.equalize_radius:
dims = list(final_dimesnions)
dims.remove(axle)
max_dim = max(dims)
else:
axle = max_dim = final_dim_max
for i in range(3):
if final_dimesnions[i] == axle:
continue
scale_vector[i] = max_dim/final_dimesnions[i] if final_dimesnions[i] else final_dimesnions[i]
scale_vector*=self.scale
bmesh.ops.scale(bm, vec = scale_vector, verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ scale_center)) )
if self.copy_1st_bvl and primitive not in self.no_bevel_club:
if self.individual:
source = object
else:
source = bpy.data.objects[self.active_obj_name] if self.active_obj_name else None
if source:
bevels = [mod for mod in source.modifiers if mod.type =='BEVEL']
if bevels:
source_bvl = bevels[0]
if self.copy_1st_bvl_angle_only:
angles = [bvl for bvl in bevels if bvl.limit_method =='ANGLE']
if angles:
source_bvl = angles[0]
else:
source_bvl = None
if source_bvl:
stored_source = modifier.stored(source_bvl)
modifier.new(primitive_obj, name=source_bvl.name, mod = stored_source)
del stored_source
dest_bvl = primitive_obj.modifiers[0]
if dest_bvl.limit_method not in {'NONE', 'ANGLE'}:
dest_bvl.limit_method = 'ANGLE'
dest_bvl.angle_limit = radians(30)
if addon.preference().behavior.auto_smooth:
primitive_mesh.use_auto_smooth = True
primitive_mesh.auto_smooth_angle = getattr(object.data, 'auto_smooth_angle', radians(30))
for face in bm.faces:
face.smooth = True
bm.to_mesh(primitive_mesh)
bm. free()
return primitive_obj
def convex_hull(self, context, center = Vector((0, 0, 0)) , objects = [], scale_object = Vector((1,1,1)), world = False):
primitive = self.primitive_type
primitive_mesh = bpy.data.meshes.new(primitive)
primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)
col = collections.find_collection(context, objects[0])
col.objects.link(primitive_obj)
bm = bmesh.new()
if self.modified:
for obj in objects:
self.mesh_update(obj)
smooth_store = getattr(obj.data, 'use_auto_smooth', False)
if smooth_store : obj.data.use_auto_smooth = False
depsgraph = context.evaluated_depsgraph_get()
eval_obj = obj.evaluated_get(depsgraph)
temp_mesh = eval_obj.to_mesh()
if smooth_store : obj.data.use_auto_smooth = smooth_store
primitive_mesh.use_auto_smooth = smooth_store
primitive_mesh.auto_smooth_angle = temp_mesh.auto_smooth_angle
matrix = obj.matrix_world if world else Matrix()
temp_mesh.transform(matrix)
bm.from_mesh(temp_mesh)
obj.to_mesh_clear()
else:
for obj in objects:
self.mesh_update(obj)
smooth_store = getattr(obj.data, 'use_auto_smooth', False)
if smooth_store : obj.data.use_auto_smooth = False
temp_mesh = obj.to_mesh()
if smooth_store : obj.data.use_auto_smooth = smooth_store
primitive_mesh.use_auto_smooth = smooth_store
primitive_mesh.auto_smooth_angle = temp_mesh.auto_smooth_angle
matrix = obj.matrix_world if world else Matrix()
temp_mesh.transform(matrix)
bm.from_mesh(temp_mesh)
obj.to_mesh_clear()
if context.mode != 'OBJECT':
verts = [v for v in bm.verts if not v.select]
bmesh.ops.delete(bm, geom = verts, context = 'VERTS')
ret= bmesh.ops.convex_hull(bm, input =bm.verts, use_existing_faces = True)
filter = set(ret['geom'])
del_verts = [v for v in bm.verts if v not in filter]
bmesh.ops.delete(bm, geom = del_verts, context = 'VERTS')
bmesh.ops.dissolve_limit(bm, angle_limit =radians(self.dissolve_angle) , use_dissolve_boundaries = False, verts = bm.verts , edges = bm.edges)
fit_matrix = math.get_sca_matrix(scale_object)
bmesh.ops.transform(bm, matrix = fit_matrix, verts = bm.verts)
scale = Vector((self.scale,self.scale,self.scale))
bmesh.ops.scale(bm, vec = scale , verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ center)) )
bm.to_mesh(primitive_mesh)
bm.free()
return primitive_obj
def decap(self, context, objects = [], dimensions= Vector((1, 1, 1)) , center = Vector((0, 0, 0)), scale_object = Vector((1,1,1)), world = False):
source = bmesh.new()
smooth = False
smooth_angle = radians(30)
if self.modified:
for obj in objects:
self.mesh_update(obj)
smooth_store = getattr(obj.data, 'use_auto_smooth', False)
if smooth_store : obj.data.use_auto_smooth = False
depsgraph = context.evaluated_depsgraph_get()
eval_obj = obj.evaluated_get(depsgraph)
temp_mesh = eval_obj.to_mesh()
if smooth_store : obj.data.use_auto_smooth = smooth_store
smooth = smooth_store
smooth_angle = temp_mesh.auto_smooth_angle
matrix = obj.matrix_world if world else Matrix()
temp_mesh.transform(matrix)
source.from_mesh(temp_mesh)
obj.to_mesh_clear()
obj.hide_set(self.decap_hide_orig and context.mode == 'OBJECT')
else:
for obj in objects:
self.mesh_update(obj)
smooth_store = getattr(obj.data, 'use_auto_smooth', False)
if smooth_store : obj.data.use_auto_smooth = False
temp_mesh = obj.to_mesh()
if smooth_store : obj.data.use_auto_smooth = smooth_store
smooth = smooth_store
smooth_angle = temp_mesh.auto_smooth_angle
matrix = obj.matrix_world if world else Matrix()
temp_mesh.transform(matrix)
source.from_mesh(temp_mesh)
obj.to_mesh_clear()
obj.hide_set(self.decap_hide_orig and context.mode == 'OBJECT')
bms = []
bm_main = source.copy()
bms.append ( (bm_main, '') )
slicer(bm_main, dimensions=dimensions, center=center , axis = self.decap_axis,
slice_thick=self.decap_thickness, cut_offset=self.decap_center,
fill_neg=self.decap_fill_neg, fill_pos=self.decap_fill_pos)
if self.decap_keep_caps:
neg_thick = (self.decap_center - self.decap_thickness/2)*2
pos_thick = (1 - (self.decap_center + self.decap_thickness/2))*2
bm_pos = source.copy()
slicer(bm_pos, dimensions=dimensions, center=center , axis = self.decap_axis,
slice_thick=pos_thick, cut_offset= 1,
fill_neg=self.decap_fill_pos, fill_pos=False)
if bm_pos.verts:
bms.append( (bm_pos, '_pos') )
else:
bm_pos.free()
bms.append( (bmesh.new(), '_pos') )
bm_neg = source.copy()
slicer(bm_neg, dimensions=dimensions, center=center , axis = self.decap_axis,
slice_thick=neg_thick, cut_offset= 0,
fill_neg=False, fill_pos=self.decap_fill_neg)
if bm_neg.verts:
bms.append( (bm_neg, '_neg') )
else:
bm_neg.free()
bms.append((bmesh.new(), '_neg' ))
scale = Vector((self.scale,self.scale,self.scale))
fit_matrix = math.get_sca_matrix(scale_object)
if self.decap_solidify and not self.decap_thickness and bms[0][0].faces:
bm = bms[0][0]
index = 'XYZ'.index(self.decap_axis)
axis_vector = Vector([self.decap_axis == v for v in 'XYZ'])
elem_multi = lambda vect1, vect2 : Vector([a*b for a,b in zip(vect1, vect2)])
offset = elem_multi(dimensions, axis_vector) * self.decap_center * self.decap_solidify_multi
bmesh.ops.transform(bm, verts = bm.verts ,matrix = matrix.Translation(-offset))
for face in bm.faces:
face.normal = axis_vector
bmesh.ops.solidify(bm, geom = bm.faces, thickness= -dimensions[index]* self.decap_solidify_multi )
for bm, _ in bms:
bmesh.ops.transform(bm, matrix = fit_matrix, verts = bm.verts)
bmesh.ops.scale(bm, vec = scale , verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ center)) )
primitives = []
for bm, suffix in bms:
primitive = self.primitive_type + suffix
primitive_mesh = bpy.data.meshes.new(primitive)
primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)
col = collections.find_collection(context, objects[0])
col.objects.link(primitive_obj)
primitive_obj.select_set(True)
primitive_mesh.use_auto_smooth = smooth
primitive_mesh.auto_smooth_angle = smooth_angle
bm.to_mesh(primitive_mesh)
bm.free()
primitives.append(primitive_obj)
source.free()
return primitives
def get_coords_obj(self, context, obj, matrix = Matrix(), modified = False):
if modified:
bb = [matrix @ Vector(v) for v in obj.bound_box]
else:
tmp = bpy.data.objects.new("Bounding Box", obj.data)
bb = [matrix @ Vector (v) for v in tmp.bound_box]
bpy.data.objects.remove(tmp)
return bb
def get_coords_edit (self, context, obj, matrix = Matrix(), modified = False ):
coords = []
if modified:
depsgraph = context.evaluated_depsgraph_get()
obj_eval = obj.evaluated_get(depsgraph)
data_eval = obj_eval.to_mesh()
coords = [matrix @ v.co for v in data_eval.vertices if v.select]
obj_eval.to_mesh_clear()
if not coords :
bm = bmesh.from_edit_mesh(obj.data)
coords = [matrix @ v.co for v in bm.verts if v.select]
return coords
def set_origin (self, obj, center):
obj.data.transform(Matrix.Translation( -(obj.matrix_world.inverted() @ center)))
obj.matrix_world = matrix_transfrom (obj.matrix_world, location= center)
def center_origin(self, obj, center = None) :
center = object.center(obj) if not center else center
obj.data.transform(Matrix.Translation(-center))
obj.matrix_world = matrix_transfrom (obj.matrix_world, location=obj.matrix_world @ center)
def plane_mesh (self):
bm = bmesh.new()
bmesh.ops.create_cube(bm)
if self.plane_alignment == '+X':
verts = [v for v in bm.verts if v.co[0]==-0.5]
elif self.plane_alignment == '+Y':
verts = [v for v in bm.verts if v.co[1]==-0.5]
elif self.plane_alignment == '+Z':
verts = [v for v in bm.verts if v.co[2]==-0.5]
elif self.plane_alignment == '-X':
verts = [v for v in bm.verts if v.co[0]==0.5]
elif self.plane_alignment == '-Y':
verts = [v for v in bm.verts if v.co[1]==0.5]
elif self.plane_alignment == '-Z':
verts = [v for v in bm.verts if v.co[2]==0.5]
bmesh.ops.delete(bm, geom = verts, context = 'VERTS')
offset = self.plane_offset
if self.plane_alignment in {'-X', '-Y', '-Z'}:
offset*= -1
if self.plane_alignment in {'+X', '-X'}:
bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((offset,0,0))), verts = bm.verts)
elif self.plane_alignment in {'+Y', '-Y'}:
bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((0,offset,0))), verts = bm.verts)
elif self.plane_alignment in {'+Z', '-Z'}:
bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((0,0,offset))), verts = bm.verts)
return bm
def add_empty(self, context, object, dimensions):
primitive = self.primitive_type
primitive_obj = bpy.data.objects.new(primitive, None)
col = collections.find_collection(context, object)
col.objects.link(primitive_obj)
primitive_obj.empty_display_type = self.empty_display
primitive_obj.empty_display_size = max (dimensions)*0.6
return primitive_obj
def set_parent (chlid, parent):
buffer = chlid.matrix_world.copy()
chlid.parent = parent
chlid.matrix_parent_inverse = parent.matrix_world.inverted()
chlid.matrix_world = buffer
def matrix_transfrom (matrix, location = None, rotation = None, scale = None):
loc, rot, sca = matrix.decompose()
loc = location if location else loc
rot = rotation if rotation else rot
sca = scale if scale else sca
mat_loc = Matrix.Translation(loc)
mat_rot = rot.to_matrix().to_4x4()
mat_sca = math.get_sca_matrix (sca)
return mat_loc @ mat_rot @ mat_sca
def apply_scale_matrix(obj):
obj.data.transform(math.get_sca_matrix(obj.scale))
obj.matrix_world = matrix_transfrom(obj.matrix_world, scale= Vector((1,1,1,)) )
def set_active(obj):
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
def slicer(bm, dimensions = Vector((1,1,1,)), center = Vector((0,0,0,)) , axis = 'Z', slice_thick = 0.1, cut_offset = 0.5, fill_neg = True, fill_pos = True ):
slice_thick /=2
index = 'XYZ'.index(axis)
dimension = dimensions[index]
normal = Vector( [axis == s for s in 'XYZ'] )
offset_unit = normal*dimension
bottom = center -(offset_unit/2)
positive_slice = bottom + (offset_unit * slice_thick) + (offset_unit * cut_offset)
negative_slice = bottom - (offset_unit * slice_thick) + (offset_unit * cut_offset)
if slice_thick:
positive_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
dist = 0, plane_co =positive_slice , plane_no = normal,
use_snap_center= False, clear_outer = True, clear_inner= False)
if fill_pos:
positive_edges = [e for e in positive_cut['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
ret = bmesh.ops.holes_fill(bm, edges = positive_edges)
negative_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
dist = 0, plane_co =negative_slice , plane_no = normal,
use_snap_center= False, clear_outer = False, clear_inner= True)
if fill_neg:
negative_edges = [e for e in negative_cut['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
bmesh.ops.holes_fill(bm, edges = negative_edges)
else:
plane_co = (positive_slice + negative_slice)/2
normal_signed = normal *-1 if plane_co[index]>0.5 else normal
center_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
dist = 0, plane_co = plane_co, plane_no = normal_signed,
use_snap_center= False, clear_outer = True, clear_inner= True)
bmesh.ops.remove_doubles(bm, verts = bm.verts, dist = 0.0001)
if fill_neg or fill_pos:
bmesh.ops.holes_fill(bm, edges = bm.edges)
class Obj_data():
def __init__(self):
self.object_name = None
self.object_matrix = None
self.has_data = False
self.dimensions_base = None
self.bounds_base = None
self.dimensions_final = None
self.bounds_final = None
self.center_base = None
self.center_final = None