AI找相似
打印配置(0)
模型文件(0)
发布时间:2026-06-01 07:10:38
0
0
I found a few ‘Fractal Fidgets’ on printables but the gaps seemed too tight.
This seemed like such a simple object that I took the plunge and attempted to build a blender python script to programmatically create them with wall thickness, height, radius, gap size as some of the parameters.
I started with stars, but added polygons and an option for the center to be filled and additionally with an option to add a 'handle' (only works if center is filled).
The uploaded stl files are created from the script below. I used ChatGPT to assist in making the script, but the 3D files themselves were not AI generated.
Enjoy!
import bpy
import bmesh
import math
from mathutils import Vector
def create_star_edges(name='star', num_points=5, outer_radius=1.0, inner_radius=0.5, location=(0, 0, 0), fill='NOTHING', collection_name='Scene Collection'):
"""
Adds a star mesh to the scene.
Args:
num_points (int): The number of points on the star.
outer_radius (float): The radius of the outer points of the star.
inner_radius (float): The radius of the inner points of the star.
location (tuple): The (x, y, z) location to place the star.
fill (str): 'NOTHING' (default) or 'NGON'
"""
# --- Make sure we are in Object Mode and have a clean selection ---
if bpy.context.object is not None and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
# Add a circle mesh
bpy.ops.mesh.primitive_circle_add(
vertices=num_points * 2, # Double the vertices for inner and outer points
radius=outer_radius,
fill_type=fill,
location=location
)
# Get the newly created object
obj = bpy.context.active_object
if obj is None:
# Bail out clearly if the operator failed
raise RuntimeError("Failed to create star: primitive_circle_add did not create an active object")
obj.name = name
obj.data.name = f"{name}_Mesh"
# Switch to edit mode
bpy.ops.object.mode_set(mode='EDIT')
# Create a BMesh from the object's mesh data
bm = bmesh.from_edit_mesh(obj.data)
# Deselect all vertices initially
for v in bm.verts:
v.select = False
# Select every other vertex (the inner points)
for i, v in enumerate(bm.verts):
if i % 2 != 0: # Select odd-indexed vertices
v.select = True
# Scale the selected vertices (inner points)
bpy.ops.transform.resize(value=(inner_radius / outer_radius,
inner_radius / outer_radius,
1))
# Update the mesh and switch back to object mode
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
# --- Ensure collection exists and link the object into it ---
if collection_name:
coll = bpy.data.collections.get(collection_name)
if coll is None:
coll = bpy.data.collections.new(collection_name)
# Link the new collection under the scene's master collection
bpy.context.scene.collection.children.link(coll)
else:
coll = bpy.context.scene.collection
# Link object into the target collection if needed
if obj.name not in coll.objects:
coll.objects.link(obj)
# Optionally remove from the scene master collection to avoid duplicates
master = bpy.context.scene.collection
if coll is not master and obj.name in master.objects:
master.objects.unlink(obj)
return obj
def create_polygon_edges(name="Polygon", num_points=6, radius=1.0, location=(0, 0, 0), close_loop=True, collection_name='Scene Collection'):
"""
Creates an edge-only polygon with num_points points.
Args:
name (str): Name of the new mesh object.
num_points (int): Number of polygon vertices.
radius (float): Radius of the polygon in XY.
location (tuple): (x,y,z) location of the object.
close_loop (bool): Connect last→first.
Returns:
bpy.types.Object: The created object.
"""
if num_points < 2:
raise ValueError("num_points must be >= 2")
# --- Make sure we are in Object Mode and have a clean selection ---
if bpy.context.object is not None and bpy.context.object.mode != 'OBJECT':
try:
bpy.ops.object.mode_set(mode='OBJECT')
except RuntimeError:
# In some non-standard contexts mode_set can fail; not fatal here
pass
try:
bpy.ops.object.select_all(action='DESELECT')
except RuntimeError:
# Can fail if no suitable view-layer context, safe to ignore
pass
# Create empty mesh and object
mesh = bpy.data.meshes.new(f"{name}_Mesh")
obj = bpy.data.objects.new(name, mesh)
obj.location = location
# Build polygon vertices
verts = []
for i in range(num_points):
angle = (2 * math.pi * i) / num_points
x = radius * math.cos(angle)
y = radius * math.sin(angle)
verts.append((x, y, 0.0))
# Build the mesh
bm = bmesh.new()
bm_verts = [bm.verts.new(v) for v in verts]
bm.verts.ensure_lookup_table()
# Create edges between consecutive verts
for i in range(num_points - 1):
bm.edges.new((bm_verts[i], bm_verts[i+1]))
# Close loop if requested
if close_loop and num_points > 2:
bm.edges.new((bm_verts[-1], bm_verts[0]))
bm.to_mesh(mesh)
bm.free()
# --- Ensure collection exists and link the object into it ---
if collection_name:
coll = bpy.data.collections.get(collection_name)
if coll is None:
coll = bpy.data.collections.new(collection_name)
# Link the new collection under the scene's master collection
bpy.context.scene.collection.children.link(coll)
else:
coll = bpy.context.scene.collection
# Link object into the target collection if needed
if obj.name not in coll.objects:
coll.objects.link(obj)
# Optionally remove from the scene master collection to avoid duplicates
master = bpy.context.scene.collection
if coll is not master and obj.name in master.objects:
master.objects.unlink(obj)
return obj
def extrude_edges(edge_obj, height=4.0, cap=False):
"""
Extrudes all edges of a mesh in the Z direction.
After extrusion, only the newly created faces (walls and optional caps)
are selected, and their normals are made consistent/outward.
"""
# Make sure the object is active
bpy.context.view_layer.objects.active = edge_obj
edge_obj.select_set(True)
# Switch to Edit Mode
bpy.ops.object.mode_set(mode='EDIT')
# Work with BMesh
bm = bmesh.from_edit_mesh(edge_obj.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
# --- EXTRUDE EDGES USING BMESH ---
# Extrude all edges (or you could filter to selected ones)
res = bmesh.ops.extrude_edge_only(bm, edges=bm.edges)
extruded_geom = res["geom"]
extruded_verts = [ele for ele in extruded_geom
if isinstance(ele, bmesh.types.BMVert)]
new_side_faces = [ele for ele in extruded_geom
if isinstance(ele, bmesh.types.BMFace)]
# Move extruded verts up in Z
for v in extruded_verts:
v.co.z += height
new_cap_faces = []
# --- OPTIONAL: CAP TOP & BOTTOM ---
if cap:
bm.edges.ensure_lookup_table()
boundary_edges = [e for e in bm.edges if e.is_boundary]
if boundary_edges:
cap_res = bmesh.ops.edgeloop_fill(bm, edges=boundary_edges)
new_cap_faces = cap_res.get("faces", [])
# --- NORMALS ---
# Recalculate normals for the *whole* mesh.
# This gives Blender the best chance of deciding what "outside" means.
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
# OPTIONAL: enforce "top faces point +Z".
# This is useful if your mesh is a simple extrude along +Z.
if cap and bm.faces:
# Pick the face with the highest center.z as "top"
top_face = max(bm.faces, key=lambda f: f.calc_center_median().z)
if top_face.normal.z < 0.0:
# Flip all faces if the top is pointing down
bmesh.ops.reverse_faces(bm, faces=bm.faces)
# Push changes back to mesh and leave Edit Mode
bmesh.update_edit_mesh(edge_obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
def duplicate_object(obj, new_name=None, collection_name='Scene Collection'):
new_obj = obj.copy() # duplicate object
new_obj.data = obj.data.copy() # copy the mesh data (important!)
if new_name:
new_obj.name = new_name
new_obj.data.name = f"{new_name}_Mesh"
# bpy.context.collection.objects.link(new_obj)
# Ensure collection exists
if collection_name in bpy.data.collections:
coll = bpy.data.collections[collection_name]
else:
coll = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(coll)
# Link object into collection
coll.objects.link(new_obj)
return new_obj
def radial_displace_verts(obj, dr=0.1, center=(0.0, 0.0), z_range=[-0.1, 0.1]):
"""
Move vertices radially in the XY plane by a fixed displacement dr.
Optionally only for vertices whose Z is in z_range = [z_min, z_max].
"""
# Make sure it's the active object
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Enter Edit Mode
bpy.ops.object.mode_set(mode='EDIT')
# BMesh access
bm = bmesh.from_edit_mesh(obj.data)
cx, cy = center
# Optional: clear previous selection
for v in bm.verts:
v.select = False
for v in bm.verts:
if z_range:
z = v.co.z
if z < z_range[0] or z > z_range[1]:
continue
# Mark as selected (if you want to see them)
v.select = True
# Current position relative to center
dx = v.co.x - cx
dy = v.co.y - cy
r = math.hypot(dx, dy)
# If very close to center, skip to avoid division by zero
if r < 1e-8:
continue
# New radius is old radius + fixed displacement
new_r = r + dr
scale = new_r / r
# Apply radial scaling in XY around center
v.co.x = cx + dx * scale
v.co.y = cy + dy * scale
# z stays unchanged
# Push changes back to the mesh
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
def extrude_faces_along_normals(obj, distance=0.1):
"""
Extrudes all faces of a mesh along their individual normals
by the given distance, using bmesh (no operators that depend on context).
Args:
obj (bpy.types.Object): Mesh object to modify.
distance (float): Extrusion distance along face normals.
"""
bpy.ops.object.select_all(action='DESELECT')
# Make sure it's active
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Go to Edit Mode
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
for f in bm.faces:
f.select = False
# Optionally: select all faces (for visual feedback)
for f in bm.faces:
f.select = True
bpy.ops.mesh.extrude_region_shrink_fatten(
MESH_OT_extrude_region={
"use_normal_flip":False,
"use_dissolve_ortho_edges":False,
"mirror":False
},
TRANSFORM_OT_shrink_fatten={
"value":distance,
"use_even_offset":False,
"mirror":False,
"use_proportional_edit":False,
"proportional_edit_falloff":'SMOOTH',
"proportional_size":1,
"use_proportional_connected":False,
"use_proportional_projected":False,
"snap":False,
"release_confirm":False,
"use_accurate":True
}
)
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
def scale_object_xy_by_max_radius(obj, dr, z_range = None):
"""
Scales an object in X and Y so that the maximum radial distance
(in the XY plane) of any vertex from the object's centroid
becomes target_max_radius.
Args:
obj (bpy.types.Object): Mesh object to scale.
target_max_radius (float): Desired max radius after scaling.
"""
if obj.type != 'MESH':
print("Object is not a mesh.")
return
mesh = obj.data
if len(mesh.vertices) == 0:
print("Mesh has no vertices.")
return
# --- Compute centroid in object local space ---
sum_x = 0.0
sum_y = 0.0
sum_z = 0.0
for v in mesh.vertices:
co = v.co
sum_x += co.x
sum_y += co.y
sum_z += co.z
n = len(mesh.vertices)
cx = sum_x / n
cy = sum_y / n
cz = sum_z / n # not used, but computed for completeness
# --- Compute max radial distance in XY from centroid ---
max_r = 0.0
for v in mesh.vertices:
dx = v.co.x - cx
dy = v.co.y - cy
r = math.hypot(dx, dy)
if r > max_r:
max_r = r
if max_r < 1e-8:
print("Max radius is zero or extremely small; cannot scale meaningfully.")
return
# --- Scale factor so that max_r -> target_max_radius ---
scale = (max_r + dr) / max_r
# Make sure it's active
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Go to Edit Mode
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)
# Optional: clear previous selection
for v in bm.verts:
v.select = False
for v in bm.verts:
if z_range:
z = v.co.z
if z < z_range[0] or z > z_range[1]:
continue
# Mark as selected (if you want to see them)
v.select = True
# Apply radial scaling in XY around center
v.co.x = v.co.x * scale
v.co.y = v.co.y * scale
# z stays unchanged
# Push changes back to the mesh
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
def add_mirror_modifier_z(obj, name="Mirror_Z"):
"""
Adds a Mirror modifier to the given object,
mirroring across the Z axis (Z plane).
"""
# Ensure the object is active
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Create the modifier
mod = obj.modifiers.new(name=name, type='MIRROR')
# Enable Z-axis mirroring (disable X/Y unless wanted)
mod.use_axis[0] = False # X
mod.use_axis[1] = False # Y
mod.use_axis[2] = True # Z
# Optional: avoid clip issues
mod.use_clip = True
return mod
#def delete_interior_faces(obj):
# """
# Deletes interior faces on a mesh object, e.g. the interface faces
# created after applying a mirror modifier.
# Args:
# obj (bpy.types.Object): Mesh object to operate on.
# """
# if obj is None or obj.type != 'MESH':
# raise TypeError("delete_interior_faces expects a mesh object")
# # Make sure this object is active + selected
# bpy.context.view_layer.objects.active = obj
# obj.select_set(True)
# # Ensure we're in Edit Mode
# if obj.mode != 'EDIT':
# bpy.ops.object.mode_set(mode='EDIT')
# # Make sure we're in face-select mode (needed for interior selection)
# try:
# bpy.ops.mesh.select_mode(type='FACE')
# except RuntimeError:
# # In weird contexts this can fail; not usually an issue in normal use
# pass
# # DESELECT everything, then let Blender select only interior faces
# bpy.ops.mesh.select_all(action='DESELECT')
# bpy.ops.mesh.select_interior_faces()
# # Delete selected faces (which should now be only interior faces)
# bpy.ops.mesh.delete(type='FACE')
# # (Optional) make normals consistent after deletion
# try:
# bpy.ops.mesh.normals_make_consistent(inside=False)
# except RuntimeError:
# pass
# # Back to Object Mode for cleanliness
# bpy.ops.object.mode_set(mode='OBJECT')
def delete_interior_faces(obj):
"""
Deletes 'interface' faces in a mesh, i.e. faces that share edges with
more than two linked faces (typical after mirror/boolean overlap).
This is often more reliable than mesh.select_interior_faces() for
cleaning up internal walls after booleans/mirrors.
"""
if obj is None or obj.type != 'MESH':
raise TypeError("delete_interface_faces expects a mesh object")
# Make object active/selected
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Go to Edit Mode
if obj.mode != 'EDIT':
bpy.ops.object.mode_set(mode='EDIT')
# Get BMesh
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
# 1) Collect edges that have "too many" faces (non-manifold overlaps)
bad_edges = {e for e in bm.edges if len(e.link_faces) > 2}
if not bad_edges:
# Nothing to do
bmesh.update_edit_mesh(obj.data, loop_triangles=False, destructive=False)
bpy.ops.object.mode_set(mode='OBJECT')
return
# 2) Collect faces that are fully bounded by those bad edges
interior_faces = set()
for f in bm.faces:
# Face must use at least one bad edge, and *all* its edges must be bad
if any(e in bad_edges for e in f.edges) and all(e in bad_edges for e in f.edges):
interior_faces.add(f)
# 3) Delete only those faces
if interior_faces:
bmesh.ops.delete(bm, geom=list(interior_faces), context='FACES')
bmesh.update_edit_mesh(obj.data, loop_triangles=False, destructive=False)
# Back to Object Mode (optional)
bpy.ops.object.mode_set(mode='OBJECT')
def delete_object(obj):
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.delete()
def boolean_cylinder_on_object(obj, operation='UNION', cylinder_radius=None, cylinder_height=None, name_suffix="_Cyl"):
"""
Adds a Z-aligned cylinder centered on the mesh's centroid and
applies a Boolean operation between the object and the cylinder.
Args:
obj (bpy.types.Object): Target mesh object.
operation (str): 'UNION', 'DIFFERENCE', or 'INTERSECT'.
cylinder_radius (float or None): Cylinder radius. If None, uses max XY radius of the mesh.
cylinder_height (float or None): Cylinder height. If None, uses object bounding-box height.
name_suffix (str): Suffix for the temporary cylinder object name.
"""
if obj.type != 'MESH':
print(f"Object {obj.name} is not a mesh.")
return
# Make sure we're in Object Mode
if bpy.context.object and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Build a BMesh copy to analyze geometry (local space)
temp_mesh = obj.data
bm = bmesh.new()
bm.from_mesh(temp_mesh)
bm.verts.ensure_lookup_table()
if not bm.verts:
print(f"Object {obj.name} has no vertices.")
bm.free()
return
# --- Compute centroid and max XY radius in local space ---
sum_vec = Vector((0.0, 0.0, 0.0))
max_r = 0.0
for v in bm.verts:
sum_vec += v.co
r = math.hypot(v.co.x, v.co.y)
if r > max_r:
max_r = r
centroid_local = sum_vec / len(bm.verts)
bm.free()
# Default radius: max radial distance in XY
if cylinder_radius is None:
cylinder_radius = max_r
# Default height: use bounding box Z size
if cylinder_height is None:
# obj.bound_box is in local space; get z extent
zs = [v[2] for v in obj.bound_box]
bb_height = max(zs) - min(zs)
# Fallback if extremely thin
cylinder_height = bb_height if bb_height > 1e-5 else 1.0
# Convert local centroid to world space
world_center = obj.matrix_world @ centroid_local
# --- Create cylinder, axis aligned to Z ---
bpy.ops.mesh.primitive_cylinder_add(
radius=cylinder_radius,
depth=cylinder_height,
location=(
world_center.x,
world_center.y,
world_center.z + cylinder_height / 2.0
)
)
cyl_obj = bpy.context.active_object
cyl_obj.name = f"{obj.name}{name_suffix}"
# --- Add Boolean modifier to target object ---
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
op = operation.upper()
if op not in {'UNION', 'DIFFERENCE', 'INTERSECT'}:
op = 'UNION'
bool_mod = obj.modifiers.new(name="Cyl_Boolean", type='BOOLEAN')
bool_mod.operation = op
bool_mod.object = cyl_obj
# Apply Boolean modifier
bpy.ops.object.modifier_apply(modifier=bool_mod.name)
# Delete the temporary cylinder
bpy.ops.object.select_all(action='DESELECT')
cyl_obj.select_set(True)
bpy.ops.object.delete()
def join_objects_in_collection(collection_name, new_name="JoinedObject"):
"""
Joins all MESH objects inside a collection into one mesh object.
Args:
collection_name (str): Name of the collection.
new_name (str): Name for the resulting joined object.
Returns:
bpy.types.Object or None
"""
# Get the collection
if collection_name not in bpy.data.collections:
print(f"Collection '{collection_name}' does not exist.")
return None
coll = bpy.data.collections[collection_name]
# Filter to mesh objects only
mesh_objs = [obj for obj in coll.objects if obj.type == 'MESH']
if len(mesh_objs) < 2:
print("Collection needs at least 2 mesh objects to join.")
return None
# Ensure we are in Object Mode
if bpy.context.object and bpy.context.object.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
# Deselect everything
bpy.ops.object.select_all(action='DESELECT')
# First mesh becomes the active one
active_obj = mesh_objs[0]
bpy.context.view_layer.objects.active = active_obj
# Select all mesh objects
for obj in mesh_objs:
obj.select_set(True)
# Join them
bpy.ops.object.join()
# Rename the resulting object and its mesh
active_obj.name = new_name
if active_obj.data:
active_obj.data.name = f"{new_name}_Mesh"
return active_obj
###############################
# Example 1: Hollow Star
h = 4
t_wall = 1.2
gap = 2
# Star
my_obj = create_star_edges(num_points=6, outer_radius=10, inner_radius=7, location=(0, 0, 0), collection_name='star')
extrude_edges(my_obj, height=h)
last_obj = my_obj
for i in range(12):
my_obj_i = duplicate_object(last_obj, collection_name='star')
# extrude and shift last_obj
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
# scale copy
scale_object_xy_by_max_radius(my_obj_i, dr=(t_wall+gap))
last_obj = my_obj_i
# extrude and shift final copy
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
_ = join_objects_in_collection('star')
###############################
# Example 2: Hollow Polygon
h = 4
t_wall = 1.2
gap = 2
# Pentagon
my_obj = create_polygon_edges(name="pentagon", num_points=5, radius=10.0, location=(0, 0, 0), collection_name='pentagon')
extrude_edges(my_obj, height=h)
last_obj = my_obj
for i in range(12):
my_obj_i = duplicate_object(last_obj, collection_name='pentagon')
# extrude and shift last_obj
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
# scale copy
scale_object_xy_by_max_radius(my_obj_i, dr=(t_wall+gap))
last_obj = my_obj_i
# extrude and shift final copy
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
_ = join_objects_in_collection('pentagon')
###############################
# Example 3: Filled Polygon
h = 4
t_wall = 1.3
gap = 2
filled = True
# Hexagon
my_obj = create_polygon_edges(name="filled polygon", num_points=6, radius=10.0, location=(0, 0, 0), collection_name='filled polygon')
extrude_edges(my_obj, height=h)
last_obj = my_obj
for i in range(12):
my_obj_i = duplicate_object(last_obj, collection_name='filled polygon')
if filled and i == 0:
last_obj = create_polygon_edges(name="filled polygon", num_points=6, radius=10.0+t_wall, location=(0, 0, 0), collection_name='filled polygon')
extrude_edges(last_obj, height=h, cap=True)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
boolean_cylinder_on_object(last_obj, cylinder_radius=2.5, cylinder_height=30.0)
delete_object(my_obj)
else:
# extrude and shift last_obj
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
# scale copy
scale_object_xy_by_max_radius(my_obj_i, dr=(t_wall+gap))
last_obj = my_obj_i
# extrude and shift final copy
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
_ = join_objects_in_collection('filled polygon')
###############################
# Example 4: Filled Star
h = 4
t_wall = 1.2
gap = 2
filled = True
# Star
my_obj = create_star_edges(num_points=6, outer_radius=15, inner_radius=10, location=(0, 0, 0), collection_name='filled star')
extrude_edges(my_obj, height=h)
last_obj = my_obj
for i in range(12):
my_obj_i = duplicate_object(last_obj, collection_name='filled star')
if filled and i == 0:
last_obj = create_star_edges(num_points=6, outer_radius=15+t_wall, inner_radius=10+t_wall, location=(0, 0, 0), collection_name='filled star')
extrude_edges(last_obj, height=h, cap=True)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
boolean_cylinder_on_object(last_obj, cylinder_radius=2.5, cylinder_height=30.0)
delete_object(my_obj)
else:
# extrude and shift last_obj
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
# scale copy
scale_object_xy_by_max_radius(my_obj_i, dr=(t_wall+gap))
last_obj = my_obj_i
# extrude and shift final copy
extrude_faces_along_normals(last_obj, distance=t_wall)
scale_object_xy_by_max_radius(last_obj, dr=-h, z_range=[h-0.1, h+0.1])
mod = add_mirror_modifier_z(last_obj)
bpy.ops.object.modifier_apply(modifier=mod.name)
delete_interior_faces(last_obj)
_ = join_objects_in_collection('filled star')