3D Print Toolbox: Add hollow out #105194

Merged
Mikhail Rachinskiy merged 9 commits from usfreitas/blender-addons:hollow into main 2024-03-18 12:24:30 +01:00
3 changed files with 83 additions and 72 deletions
Showing only changes of commit f9f897a6d7 - Show all commits

View File

@ -122,22 +122,29 @@ class SceneProperties(PropertyGroup):
max=math.radians(90.0), max=math.radians(90.0),
) )
hollow_offset: FloatProperty( hollow_offset: FloatProperty(
name = "Offset", name="Offset",
description = "Surface offset in relation to original mesh. Negative -> inwards", description="Surface offset in relation to original mesh",
default = -5.0, default=1.0,
subtype = 'DISTANCE', subtype='DISTANCE',
min=0.0,
step=1,
) )
hollow_resolution: FloatProperty( hollow_voxel_size: FloatProperty(
name = "Resolution", name="Voxel size",
description = "Resolution of the voxel grid used for hollowing", description="Size of the voxel used for volume evaluation. Lower values preserve finer details",
default = 5.0, default=1.0,
min = 0.01, min=0.0001,
subtype = 'DISTANCE', step=1,
subtype='DISTANCE',
) )
hollow_create: BoolProperty( make_hollow_duplicate: BoolProperty(
name = "Create Hollow Copy", name="Hollow Duplicate",
description = "Create a hollow copy of the target object, with applied modifiers and scale", description="Create hollowed out copy of the object",
default = False )
make_hollow_inside: BoolProperty(
name="Inside",
description="Offset surface inside of the object",
default=True,
) )
@ -146,7 +153,7 @@ classes = (
ui.VIEW3D_PT_print3d_analyze, ui.VIEW3D_PT_print3d_analyze,
ui.VIEW3D_PT_print3d_cleanup, ui.VIEW3D_PT_print3d_cleanup,
ui.VIEW3D_PT_print3d_transform, ui.VIEW3D_PT_print3d_edit,
ui.VIEW3D_PT_print3d_export, ui.VIEW3D_PT_print3d_export,
operators.MESH_OT_print3d_info_volume, operators.MESH_OT_print3d_info_volume,

View File

@ -826,27 +826,34 @@ class MESH_OT_print3d_export(Operator):
class MESH_OT_print3d_hollow(Operator): class MESH_OT_print3d_hollow(Operator):
bl_idname = "mesh.print3d_hollow" bl_idname = "mesh.print3d_hollow"
bl_label = "Create offset surface" bl_label = "Hollow"
bl_description = "Create offset surface for hollowing objects" bl_description = "Create offset surface"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
offset: FloatProperty( offset: FloatProperty(
name = "Offset", name="Offset",
description = "Surface offset in relation to original mesh. Negative -> inwards", description="Surface offset in relation to original mesh",
default = -5.0, default=1.0,
subtype = 'DISTANCE', subtype='DISTANCE',
min=0.0,
step=1,
) )
resolution: FloatProperty( voxel_size: FloatProperty(
name = "Resolution", name="Voxel size",
MikhailRachinskiy marked this conversation as resolved

Enum values are supposed to be in UPPERCASE


I noticed inconsistent use of double vs single quotation marks, Blender code guidelines recommends: single quotes when using enum values, double quotes everything else.

Enum values are supposed to be in `UPPERCASE` --- I noticed inconsistent use of double vs single quotation marks, Blender code guidelines recommends: single quotes when using enum values, double quotes everything else.
Review

Sorry, my bad. It should be fixed now.

Sorry, my bad. It should be fixed now.
description = "Resolution of the voxel grid used for hollowing", description="Size of the voxel used for volume evaluation. Lower values preserve finer details",
default = 5.0, default=5.0,
MikhailRachinskiy marked this conversation as resolved

default=1

default=1
min = 0.01, min=0.0001,
subtype = 'DISTANCE', step=1,
subtype='DISTANCE',
) )
create_hollow: BoolProperty( make_hollow_duplicate: BoolProperty(
name = "Create Hollow Copy", name="Hollow Duplicate",
description = "Create a hollow copy of the target object, with applied modifiers and scale", description="Create hollowed out copy of the object",
default = False )
make_hollow_inside: BoolProperty(
name="Inside",
description="Offset surface inside of the object",
default=True,
) )
MikhailRachinskiy marked this conversation as resolved

It's better to not create unnecessary local variables, especially when they are rarely used.

It's better to not create unnecessary local variables, especially when they are rarely used.
def execute(self, context): def execute(self, context):
@ -877,62 +884,64 @@ class MESH_OT_print3d_hollow(Operator):
tris.shape = (-1, 3) tris.shape = (-1, 3)
# Generate VDB levelset # Generate VDB levelset
half_width = max(3.0, math.ceil(abs(self.offset) / self.resolution) + 2.0) # half_width has to envelop offset half_width = max(3.0, math.ceil(abs(self.offset) / self.voxel_size) + 2.0) # half_width has to envelop offset
trans = vdb.Transform() trans = vdb.Transform()
trans.scale(self.resolution) trans.scale(self.voxel_size)
levelset = vdb.FloatGrid.createLevelSetFromPolygons(verts, triangles=tris, transform=trans, halfWidth=half_width) levelset = vdb.FloatGrid.createLevelSetFromPolygons(verts, triangles=tris, transform=trans, halfWidth=half_width)
# Generate offset surface # Generate offset surface
newverts, newquads = levelset.convertToQuads(self.offset) if self.make_hollow_inside:
newverts, newquads = levelset.convertToQuads(-self.offset)
else:
newverts, newquads = levelset.convertToQuads(self.offset)
polys = list(newquads) polys = list(newquads)
# Instantiate new object in Blender # Instantiate new object in Blender
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
mesh_offset = bpy.data.meshes.new(mesh_target.name + ' offset') mesh_offset = bpy.data.meshes.new(mesh_target.name + ' offset')
mesh_offset.from_pydata(newverts, [], polys) mesh_offset.from_pydata(newverts, [], polys)
obj_offset = bpy.data.objects.new(obj.name + ' offset', mesh_offset)
bpy.context.collection.objects.link(obj_offset)
if not self.create_hollow: # For some reason OpenVDB has inverted normals
# For some reason OpenVDB has inverted normals mesh_offset.flip_normals()
mesh_offset.flip_normals() obj_offset = bpy.data.objects.new(obj.name + ' offset', mesh_offset)
# Translate surface object to target object's position obj_offset.matrix_world.translation = obj.matrix_world.translation
obj_offset.matrix_world.translation = obj.matrix_world.translation bpy.context.collection.objects.link(obj_offset)
# This mesh will not be used anymore obj_offset.select_set(True)
bpy.data.meshes.remove(mesh_target) context.view_layer.objects.active = obj_offset
else:
# Create a copy of the target object with applied modifiers and scale if self.make_hollow_duplicate:
obj_hollow = bpy.data.objects.new(obj.name + " hollow", mesh_target) obj_hollow = bpy.data.objects.new(obj.name + " hollow", mesh_target)
bpy.context.collection.objects.link(obj_hollow) bpy.context.collection.objects.link(obj_hollow)
if self.offset < 0.0: obj_hollow.matrix_world.translation = obj.matrix_world.translation
# Offset surface already has normals as they should, see above
pass
else:
# Offset surface is outside, correct normals, see above
mesh_offset.flip_normals()
# Original surface is inside, flip its normals
mesh_target.flip_normals()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
obj_offset.select_set(True)
obj_hollow.select_set(True) obj_hollow.select_set(True)
if self.make_hollow_inside:
mesh_offset.flip_normals()
else:
mesh_target.flip_normals()
context.view_layer.objects.active = obj_hollow context.view_layer.objects.active = obj_hollow
bpy.ops.object.join() bpy.ops.object.join()
else:
bpy.data.meshes.remove(mesh_target)
# Translate hollow object to target object's position print_3d = context.scene.print_3d
obj_hollow.matrix_world.translation = obj.matrix_world.translation print_3d.hollow_offset = self.offset
print_3d.hollow_voxel_size = self.voxel_size
print_3d.make_hollow_duplicate = self.make_hollow_duplicate
print_3d.make_hollow_inside = self.make_hollow_inside
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
print_3d = context.scene.print_3d print_3d = context.scene.print_3d
self.offset = print_3d.hollow_offset self.offset = print_3d.hollow_offset
self.resolution = print_3d.hollow_resolution self.voxel_size = print_3d.hollow_voxel_size
self.create_hollow = print_3d.hollow_create self.make_hollow_duplicate = print_3d.make_hollow_duplicate
self.make_hollow_inside = print_3d.make_hollow_inside
if context.mode == 'EDIT_MESH': if context.mode == 'EDIT_MESH':
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
wm = context.window_manager
return self.execute(context) return wm.invoke_props_dialog(self)

View File

@ -102,8 +102,8 @@ class VIEW3D_PT_print3d_cleanup(View3DPrintPanel, Panel):
# layout.operator("mesh.print3d_clean_thin", text="Wall Thickness") # layout.operator("mesh.print3d_clean_thin", text="Wall Thickness")
class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel): class VIEW3D_PT_print3d_edit(View3DPrintPanel, Panel):
bl_label = "Transform" bl_label = "Edit"
bl_options = {"DEFAULT_CLOSED"} bl_options = {"DEFAULT_CLOSED"}
def draw(self, context): def draw(self, context):
@ -111,6 +111,7 @@ class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel):
print_3d = context.scene.print_3d print_3d = context.scene.print_3d
layout.operator("mesh.print3d_hollow")
layout.label(text="Scale To") layout.label(text="Scale To")
row = layout.row(align=True) row = layout.row(align=True)
row.operator("mesh.print3d_scale_to_volume", text="Volume") row.operator("mesh.print3d_scale_to_volume", text="Volume")
@ -118,12 +119,6 @@ class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel):
row = layout.row(align=True) row = layout.row(align=True)
row.operator("mesh.print3d_align_to_xy", text="Align XY") row.operator("mesh.print3d_align_to_xy", text="Align XY")
row.prop(print_3d, "use_alignxy_face_area") row.prop(print_3d, "use_alignxy_face_area")
layout.label(text="Hollow")
col = layout.column(align=True)
col.prop(print_3d, "hollow_create")
col.prop(print_3d, "hollow_offset")
col.prop(print_3d, "hollow_resolution")
col.operator("mesh.print3d_hollow", text="Create Offset Surface")
class VIEW3D_PT_print3d_export(View3DPrintPanel, Panel): class VIEW3D_PT_print3d_export(View3DPrintPanel, Panel):