atmobin.lol
@frabarz.cl/in.atmob.paste.bundle/3mrldatnvgm2srawat://
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()