★ 0
Spiral lamp shade generator script
Decorative lamp shade in the shape of an egg with wavy patterns.
spiral_lamp_shade.py
import math
import FreeCAD
import Part
# Initialize active document or create a new one
document = FreeCAD.activeDocument()
if not document:
document = FreeCAD.newDocument("SpiralLampShade")
# Shade dimensional parameters (in mm)
total_sections = 60 # Number of horizontal cross-section slices along the Z-axis
total_height = 240.0 # Total height of the lamp shade in mm
mid_radius = 100.0 # Maximum radius achieved at the mid-height bulge
outer_radius = 70.0 # Radius at the bottom and top ends
star_points = 64 # Number of sharp peaks/star points around the circumference
total_twist_angle = math.radians(60) # Maximum rotation angle (in radians)
# Base mounting parameters for the light bulb socket (in mm)
base_height = 3.92 # Height of the solid base mounting ring
hole_diameter = 41.0 # Standard diameter for E27 socket hardware
hole_radius = hole_diameter / 2.0
def calculate_twist_factor(normalized_height, damping_factor=-2.5):
"""Calculate a non-linear rotation factor along the height profile (0.0 to 1.0).
Uses a cubic ease curve combined with a polynomial perturbation wave
to create a smooth, organic spiral acceleration instead of a linear twist.
"""
x = float(normalized_height)
# Cubic smoothstep base progression from 0.0 to 1.0
base_twist = 3 * x * x - 2 * x * x * x
# Bell-shaped perturbation bump centered at mid-height (x = 0.5)
perturbation = (4 * x * (1 - x)) ** 3 * (x - 0.5)
return base_twist + damping_factor * perturbation
profile_wires = []
for section_index in range(total_sections):
# Normalized height ratio ranging from 0.0 at the base to 1.0 at the top
height_ratio = section_index / float(total_sections - 1)
z_position = height_ratio * total_height
# Calculate the variable radial bulge profile along the height
# Curve profile: f(x) = sin(pi * x^(1/1.3)) creates an asymmetric vertical bulb shape
shape_factor = math.sin(math.pow(height_ratio, (1 / 1.3)) * math.pi)
current_base_radius = outer_radius + (mid_radius - outer_radius) * shape_factor
# Calculate angular shift for the current Z height slice
current_twist = calculate_twist_factor(height_ratio) * total_twist_angle
external_points = []
contour_resolution = star_points * 2 # 2 vertices per star point (peak and trough)
# Generate the star/accordion wave geometry around the perimeter
for point_index in range(contour_resolution):
# Angular position including the accumulated twist shift
theta = current_twist + (point_index * 2 * math.pi / contour_resolution)
# Cosine wave oscillates between -1.0 (trough) and +1.0 (peak)
star_wave = math.cos(
point_index * 2 * math.pi * star_points / contour_resolution,
)
# Depth of the zig-zag indentations scaled proportionally to current radius
zigzag_depth = 0.05 * current_base_radius
current_radius = current_base_radius + star_wave * zigzag_depth
# Convert polar coordinates (radius, theta) to Cartesian (X, Y)
x = current_radius * math.cos(theta)
y = current_radius * math.sin(theta)
external_points.append(FreeCAD.Vector(x, y, z_position))
# Close the polygon loop by re-adding the start point
external_points.append(external_points[0])
outer_wire = Part.makePolygon(external_points)
profile_wires.append(outer_wire)
# Generate a continuous 3D solid by skinning/lofting through all wire profiles
loft_body = Part.makeLoft(profile_wires, True, False, False)
# Subtract a hole at the base for E27 socket insertion (compatible with 3D printer vase mode)
socket_cutting_tool = Part.makeCylinder(
hole_radius,
base_height + 1.0,
FreeCAD.Vector(0, 0, -1),
FreeCAD.Vector(0, 0, 1),
)
final_lamp_shade = loft_body.cut(socket_cutting_tool)
# Export the solid geometry to the active FreeCAD document tree
visual_object = document.addObject("Part::Feature", "LampShade")
visual_object.Shape = final_lamp_shade
document.recompute()