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 147 additions and 3 deletions

View File

@ -121,6 +121,14 @@ class SceneProperties(PropertyGroup):
min=0.0,
max=math.radians(90.0),
)
hollow_offset: FloatProperty(
name="Offset",
description="Surface offset in relation to original mesh",
default=1.0,
subtype='DISTANCE',
min=0.0,
step=1,
)
classes = (
@ -128,7 +136,7 @@ classes = (
ui.VIEW3D_PT_print3d_analyze,
ui.VIEW3D_PT_print3d_cleanup,
ui.VIEW3D_PT_print3d_transform,
ui.VIEW3D_PT_print3d_edit,
ui.VIEW3D_PT_print3d_export,
operators.MESH_OT_print3d_info_volume,
@ -149,6 +157,7 @@ classes = (
operators.MESH_OT_print3d_scale_to_bounds,
operators.MESH_OT_print3d_align_to_xy,
operators.MESH_OT_print3d_export,
operators.MESH_OT_print3d_hollow,
)

View File

@ -12,6 +12,8 @@ from bpy.types import Operator
from bpy.props import (
IntProperty,
FloatProperty,
BoolProperty,
EnumProperty,
)
import bmesh
@ -818,3 +820,135 @@ class MESH_OT_print3d_export(Operator):
return {'FINISHED'}
return {'CANCELLED'}
# ------
# Hollow out
class MESH_OT_print3d_hollow(Operator):
bl_idname = "mesh.print3d_hollow"
bl_label = "Hollow"
bl_description = "Create offset surface"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
offset_direction: EnumProperty(
items=[
('INSIDE', "Inside", "Offset surface inside of object"),
('OUTSIDE', "Outside", "Offset surface outside of object"),
],
name="Offset Direction",
description="Where the offset surface is created relative to the object",
default='INSIDE',
)
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.
offset: FloatProperty(
name="Offset",
MikhailRachinskiy marked this conversation as resolved

default=1

default=1
description="Surface offset in relation to original mesh",
default=1.0,
subtype='DISTANCE',
min=0.0,
step=1,
)
voxel_size: FloatProperty(
name="Voxel size",
description="Size of the voxel used for volume evaluation. Lower values preserve finer details",
default=1.0,
min=0.0001,
step=1,
subtype='DISTANCE',
)
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.
make_hollow_duplicate: BoolProperty(
name="Hollow Duplicate",
description="Create hollowed out copy of the object",
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "offset_direction", expand=True)
layout.prop(self, "offset")
layout.prop(self, "voxel_size")
layout.prop(self, "make_hollow_duplicate")
def execute(self, context):
import numpy as np
import pyopenvdb as vdb
if not self.offset:
return {'FINISHED'}
# Get target mesh with modifiers
obj = context.active_object
depsgraph = context.evaluated_depsgraph_get()
mesh_target = bpy.data.meshes.new_from_object(obj.evaluated_get(depsgraph))
# Apply scale, but avoid translating the mesh
mat = obj.matrix_world.copy()
mat.translation = 0, 0, 0
mesh_target.transform(mat)
# Read mesh to numpy arrays
nverts = len(mesh_target.vertices)
ntris = len(mesh_target.loop_triangles)
verts = np.zeros(3 * nverts, dtype=np.float32)
tris = np.zeros(3 * ntris, dtype=np.int32)
mesh_target.vertices.foreach_get("co", verts)
verts.shape = (-1, 3)
mesh_target.loop_triangles.foreach_get("vertices", tris)
tris.shape = (-1, 3)
# Generate VDB levelset
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.scale(self.voxel_size)
levelset = vdb.FloatGrid.createLevelSetFromPolygons(verts, triangles=tris, transform=trans, halfWidth=half_width)
# Generate offset surface
if self.offset_direction == 'INSIDE':
newverts, newquads = levelset.convertToQuads(-self.offset)
if newquads.size == 0:
self.report({"ERROR"}, "Make sure target mesh has closed surface and offset value is less than half of target thickness")
return {'FINISHED'}
else:
newverts, newquads = levelset.convertToQuads(self.offset)
polys = list(newquads)
# 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.from_pydata(newverts, [], polys)
# For some reason OpenVDB has inverted normals
mesh_offset.flip_normals()
obj_offset = bpy.data.objects.new(obj.name + " offset", mesh_offset)
obj_offset.matrix_world.translation = obj.matrix_world.translation
bpy.context.collection.objects.link(obj_offset)
obj_offset.select_set(True)
context.view_layer.objects.active = obj_offset
if self.make_hollow_duplicate:
obj_hollow = bpy.data.objects.new(obj.name + " hollow", mesh_target)
bpy.context.collection.objects.link(obj_hollow)
obj_hollow.matrix_world.translation = obj.matrix_world.translation
obj_hollow.select_set(True)
if self.offset_direction == 'INSIDE':
mesh_offset.flip_normals()
else:
mesh_target.flip_normals()
context.view_layer.objects.active = obj_hollow
bpy.ops.object.join()
else:
bpy.data.meshes.remove(mesh_target)
return {'FINISHED'}
def invoke(self, context, event):
if context.mode == 'EDIT_MESH':
bpy.ops.object.mode_set(mode='OBJECT')
wm = context.window_manager
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")
class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel):
bl_label = "Transform"
class VIEW3D_PT_print3d_edit(View3DPrintPanel, Panel):
bl_label = "Edit"
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
@ -111,6 +111,7 @@ class VIEW3D_PT_print3d_transform(View3DPrintPanel, Panel):
print_3d = context.scene.print_3d
layout.operator("mesh.print3d_hollow")
layout.label(text="Scale To")
row = layout.row(align=True)
row.operator("mesh.print3d_scale_to_volume", text="Volume")