Skip to content

Python Scripting for Advanced CAD Design

Master advanced CAD design through Python scripting, create mathematically-perfect gears, generate lattice structures, optimize designs iteratively, and automate workflows that would take hours manually. Unlock FreeCAD’s true computational power. #PythonCAD #GenerativeDesign #ParametricOptimization

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  1. Understand when Python scripting is superior to GUI-based CAD modeling
  2. Write Python scripts to generate complex parametric geometry algorithmically
  3. Create lattice structures and generative designs impossible to model manually
  4. Implement design optimization loops that explore parameter spaces automatically
  5. Automate batch operations and data-driven design workflows
  6. Apply Python CAD to real research and engineering problems

🔧 Why Python Scripting in CAD?

While GUI-based CAD is perfect for manually designing individual parts, Python scripting unlocks an entirely different capability: algorithmic design. Instead of drawing geometry, you define mathematical rules that generate geometry. This enables design exploration, optimization, and complexity that would be impractical or impossible with mouse and keyboard alone.

When Python Beats GUI

Problem: Design a mathematically-perfect involute gear

GUI Approach:

  • Manually calculate dozens of tooth profile points
  • Sketch each curve segment individually
  • Repeat for every tooth
  • Any parameter change = start over
  • Time: Hours, error-prone

Python Approach:

  • Define involute equation
  • Loop to generate all teeth
  • Parameters control everything
  • Regenerate in seconds
  • Time: Minutes, mathematically exact

The Power Shift

💡 Paradigm Change

GUI CAD: You are the designer, you create every feature manually

Python CAD: You are the programmer, you teach the computer how to design

Result: The computer can now explore thousands of design variants, handle mathematical complexity you couldn’t calculate by hand, and automate workflows that would take weeks manually.

🐍 Part 1: FreeCAD Python Basics

Accessing Python in FreeCAD

  1. Open FreeCAD Python Console

    • View → Panels → Python console
    • Bottom panel appears with >>> prompt
  2. Try a simple command:

    >>> print("Hello from FreeCAD Python!")
  3. Create a box:

    >>> import Part
    >>> box = Part.makeBox(10, 20, 30)
    >>> Part.show(box)

    A 10×20×30mm box appears in 3D view!

Python Console vs. Macro Files

Use for:

  • Quick experiments
  • Testing commands
  • One-off operations
  • Learning the API

Limitations:

  • Commands lost when FreeCAD closes
  • Can’t easily rerun complex sequences
  • No loops/functions for complex logic

Essential FreeCAD Python Modules

📦 Core Modules You'll Use

Part - Basic solid geometry (boxes, cylinders, spheres, boolean operations)

import Part

FreeCAD - Document management, objects, parameters

import FreeCAD as App

Sketcher - Programmatic sketching (advanced)

import Sketcher

Draft - 2D/3D drafting operations

import Draft

Mesh - Mesh generation, STL operations

import Mesh

numpy - Numerical arrays, mathematics (if installed)

import numpy as np

⚙️ Part 2: Algorithmic Design - Involute Gear Generator



Involute gears are the industry standard for power transmission, used in automotive transmissions, industrial gearboxes, and precision machinery. Creating mathematically-perfect involute profiles manually is tedious and error-prone. Python generates them flawlessly from equations.

Why Involute Gears?

Engineering advantages:

  • Constant velocity ratio (smooth power transmission)
  • Insensitive to center distance variations
  • Easy to manufacture
  • Standard tooth profiles (interchangeability)

Manual modeling challenges:

  • Involute curve is defined by integral equation
  • Each tooth requires dozens of precisely calculated points
  • Changing module or tooth count = complete redesign

Python solution: Define mathematics once, generate any gear instantly.

The Involute Equation

The involute of a circle is the curve traced by a point on a taut string unwinding from that circle.

Parametric equations:

Where:

  • is the base circle radius
  • is the unwinding angle parameter

Python Gear Generator

import Part
import FreeCAD as App
import math
def create_involute_gear(module, num_teeth, pressure_angle=20, thickness=10):
"""
Generate a parametric involute spur gear.
Parameters:
- module: Gear module (pitch diameter / number of teeth) in mm
- num_teeth: Number of teeth
- pressure_angle: Pressure angle in degrees (typically 20°)
- thickness: Gear thickness in mm
Returns: FreeCAD Part object
"""
# Calculate gear geometry
pitch_diameter = module * num_teeth
pitch_radius = pitch_diameter / 2.0
pressure_angle_rad = math.radians(pressure_angle)
base_radius = pitch_radius * math.cos(pressure_angle_rad)
addendum = module # Height above pitch circle
dedendum = 1.25 * module # Depth below pitch circle
outer_radius = pitch_radius + addendum
root_radius = pitch_radius - dedendum
# Angular tooth spacing
tooth_angle = 2 * math.pi / num_teeth
# Generate one tooth profile
def involute_point(theta):
"""Generate point on involute curve"""
x = base_radius * (math.cos(theta) + theta * math.sin(theta))
y = base_radius * (math.sin(theta) - theta * math.cos(theta))
return (x, y)
# Build complete gear
teeth_wires = []
for tooth_num in range(num_teeth):
angle_offset = tooth_num * tooth_angle
# Generate involute curve points (from base circle to outer)
involute_points = []
theta_start = 0
theta_end = math.sqrt((outer_radius / base_radius)**2 - 1)
for i in range(20): # 20 points per involute curve
theta = theta_start + (theta_end - theta_start) * i / 19.0
x, y = involute_point(theta)
# Rotate to tooth position
x_rot = x * math.cos(angle_offset) - y * math.sin(angle_offset)
y_rot = x * math.sin(angle_offset) + y * math.cos(angle_offset)
involute_points.append(App.Vector(x_rot, y_rot, 0))
# Create tooth profile wire
# (Simplified - real implementation would include root fillet, mirror, etc.)
tooth_curve = Part.BSplineCurve()
tooth_curve.interpolate(involute_points)
teeth_wires.append(tooth_curve.toShape())
# Create gear body (simplified - circular approximation)
# In production code, would build precise tooth profiles
gear_circle = Part.makeCircle(pitch_radius)
gear_face = Part.Face(Part.Wire(gear_circle))
gear_solid = gear_face.extrude(App.Vector(0, 0, thickness))
return gear_solid
# Generate gear
gear = create_involute_gear(module=2.5, num_teeth=24, thickness=10)
Part.show(gear, "InvoluteGear")
print("✓ Involute gear generated!")
print(f" Pitch diameter: {2.5 * 24} mm")
print(f" Number of teeth: 24")

🏗️ Part 3: Lattice Structure Generation

Lattice structures are revolutionizing lightweight design in aerospace, biomedical implants, and 3D printing. These periodic cellular structures provide high strength-to-weight ratios and controlled porosity—but creating them manually in CAD is nearly impossible. Python makes them trivial.

Why Lattice Structures?

Engineering benefits:

Types of Lattice Structures

Simple orthogonal beams

Advantages:

  • Easy to analyze structurally
  • Predictable properties
  • Simple to generate

Disadvantages:

  • Stress concentrations at joints
  • Anisotropic (different strength in different directions)

Applications:

  • General lightweight structures
  • Architectural models
  • Educational demonstrations

Python Cubic Lattice Generator

import Part
import FreeCAD as App
def create_cubic_lattice(x_size, y_size, z_size, cell_size, strut_diameter):
"""
Generate a cubic lattice structure.
Parameters:
- x_size, y_size, z_size: Overall dimensions in mm
- cell_size: Size of one cubic cell in mm
- strut_diameter: Diameter of lattice struts in mm
Returns: FreeCAD Part object
"""
struts = []
radius = strut_diameter / 2.0
# Calculate number of cells in each direction
nx = int(x_size / cell_size) + 1
ny = int(y_size / cell_size) + 1
nz = int(z_size / cell_size) + 1
# Generate X-direction struts
for j in range(ny):
for k in range(nz):
y_pos = j * cell_size
z_pos = k * cell_size
# Create cylinder along X-axis
start = App.Vector(0, y_pos, z_pos)
end = App.Vector(x_size, y_pos, z_pos)
strut = Part.makeCylinder(radius, x_size, start, end - start)
struts.append(strut)
# Generate Y-direction struts
for i in range(nx):
for k in range(nz):
x_pos = i * cell_size
z_pos = k * cell_size
start = App.Vector(x_pos, 0, z_pos)
end = App.Vector(x_pos, y_size, z_pos)
strut = Part.makeCylinder(radius, y_size, start, end - start)
struts.append(strut)
# Generate Z-direction struts
for i in range(nx):
for j in range(ny):
x_pos = i * cell_size
y_pos = j * cell_size
start = App.Vector(x_pos, y_pos, 0)
end = App.Vector(x_pos, y_pos, z_size)
strut = Part.makeCylinder(radius, z_size, start, end - start)
struts.append(strut)
# Fuse all struts into one solid
print(f"Fusing {len(struts)} struts...")
lattice = struts[0]
for strut in struts[1:]:
lattice = lattice.fuse(strut)
print(f"✓ Cubic lattice generated: {nx}×{ny}×{nz} cells")
return lattice
# Generate 50×50×50mm lattice with 5mm cells
lattice = create_cubic_lattice(
x_size=50,
y_size=50,
z_size=50,
cell_size=5,
strut_diameter=0.8
)
Part.show(lattice, "CubicLattice")

Variable Density Lattice

Real-world need: Bone implants need higher density at load-bearing surfaces, lower density in core.

def create_variable_density_lattice(x_size, y_size, z_size, cell_size_func):
"""
Generate lattice with variable cell size (density).
cell_size_func: Function that returns cell size given (x, y, z) position
"""
struts = []
# Adaptive cell generation
x, y, z = 0, 0, 0
while x < x_size:
y = 0
while y < y_size:
z = 0
while z < z_size:
# Get local cell size based on position
local_cell_size = cell_size_func(x, y, z)
# Generate strut at this location
# ... (strut generation code)
z += local_cell_size
y += local_cell_size
x += local_cell_size
# Fuse and return
# Example: Denser at edges, sparser in center
def density_function(x, y, z):
center_x, center_y, center_z = 25, 25, 25
distance_from_center = ((x - center_x)**2 + (y - center_y)**2 + (z - center_z)**2)**0.5
# Cell size increases toward center (lower density)
return 3 + distance_from_center / 10.0
lattice_var = create_variable_density_lattice(50, 50, 50, density_function)

🔬 Part 4: Design Optimization Loop

Design optimization is the holy grail of engineering: automatically finding the best design from thousands of possibilities. Python enables iterative exploration of design spaces—testing variants, measuring performance, and converging on optimal solutions. This is advanced CAD used in aerospace, automotive, and cutting-edge research.

Optimization Problem Example

Engineering challenge: Design a lightweight bracket that can support 500N load with minimum weight.

Design variables:

  • Wall thickness (1-5mm)
  • Rib spacing (10-30mm)
  • Fillet radius (2-6mm)

Objective: Minimize mass

Constraints: Max stress < 150 MPa (material yield strength)

Python Optimization Framework

import Part
import FreeCAD as App
def create_bracket(wall_thickness, rib_spacing, fillet_radius):
"""
Generate parametric bracket geometry.
Returns: FreeCAD Part object and calculated mass
"""
# Base plate
base = Part.makeBox(100, 80, wall_thickness)
# Mounting wall
wall = Part.makeBox(wall_thickness, 80, 50)
wall.translate(App.Vector(0, 0, wall_thickness))
# Ribs for reinforcement
ribs = []
num_ribs = int(80 / rib_spacing)
for i in range(1, num_ribs):
y_pos = i * rib_spacing
# Triangular rib
rib_points = [
App.Vector(0, y_pos, wall_thickness),
App.Vector(50, y_pos, wall_thickness),
App.Vector(0, y_pos, 30)
]
# Create rib profile and extrude
rib_wire = Part.makePolygon(rib_points + [rib_points[0]])
rib_face = Part.Face(rib_wire)
rib = rib_face.extrude(App.Vector(0, wall_thickness, 0))
ribs.append(rib)
# Fuse all components
bracket = base.fuse(wall)
for rib in ribs:
bracket = bracket.fuse(rib)
# Apply fillets (if radius > 0)
if fillet_radius > 0:
edges_to_fillet = []
# ... (identify edges to fillet)
# bracket = bracket.makeFillet(fillet_radius, edges_to_fillet)
# Calculate mass (assuming aluminum: 2700 kg/m³ = 2.7e-6 kg/mm³)
volume = bracket.Volume # mm³
density = 2.7e-6 # kg/mm³
mass = volume * density # kg
return bracket, mass

Real Research Applications

🔬 Research Case Studies

1. Topology Optimization for Aerospace Bracket

  • Challenge: Design lightest bracket for satellite structure
  • Method: Python + FEA in loop, remove material iteratively where stress is low
  • Result: 40% weight reduction while maintaining strength
  • Publication: Multiple papers in Journal of Mechanical Design

2. Bone Implant Porosity Optimization

  • Challenge: Match implant stiffness to natural bone to prevent stress shielding
  • Method: Python-generated lattice with variable density, FEA validation
  • Result: Custom implants with patient-specific porosity gradients
  • Impact: Clinical trials showing improved bone ingrowth

3. Heat Sink Fin Optimization

  • Challenge: Maximize heat dissipation with constrained airflow and size
  • Method: Python + CFD simulation loop, varying fin spacing/height
  • Result: 25% better cooling performance
  • Application: High-power LED thermal management

📊 Part 5: Data-Driven Design



Importing Measurement Data

Real-world engineering often starts with measurements—3D scans of parts, sensor data, experimental results, or patient-specific anatomy. Python bridges the gap between raw data and parametric CAD models, enabling reverse engineering, custom fitting, and data-informed design.

Use Case: Custom Enclosure from 3D Scan

Scenario: Design protective housing for electronics assembly measured via 3D scanning

import FreeCAD as App
import Points
import numpy as np
def import_point_cloud(csv_file):
"""
Import point cloud from CSV file.
CSV format: x, y, z (coordinates in mm)
"""
# Read CSV data
data = np.loadtxt(csv_file, delimiter=',', skiprows=1)
x_coords = data[:, 0]
y_coords = data[:, 1]
z_coords = data[:, 2]
# Create FreeCAD Points object
points = []
for i in range(len(x_coords)):
points.append(App.Vector(x_coords[i], y_coords[i], z_coords[i]))
# Create Points object in FreeCAD
point_cloud = App.ActiveDocument.addObject("Points::Feature", "PointCloud")
point_cloud.Points = points
print(f"✓ Imported {len(points)} points from {csv_file}")
return points
# Import scanned electronics assembly
scanned_points = import_point_cloud('electronics_scan.csv')

Research Application: Patient-Specific Medical Devices

Workflow:

  1. Acquire CT/MRI scan of patient anatomy

  2. Segment medical images to extract bone/tissue geometry (external software)

  3. Export point cloud or mesh (STL format)

  4. Import to FreeCAD via Python

    import Mesh
    mesh = Mesh.Mesh('femur_scan.stl')
  5. Generate custom implant fitting exact anatomy

    # Offset surface to create implant shell
    # Add porous regions for bone ingrowth
    # Generate mounting holes for surgical screws
  6. Export for 3D printing (titanium additive manufacturing)

Impact: Personalized implants improve surgical outcomes, reduce operation time, and enhance patient recovery.

🚀 Part 6: Batch Operations and Automation

Mass Export for 3D Printing

Problem: You’ve designed a modular robot with 50 unique parts. Each needs to be exported as STL for 3D printing.

Manual approach: Open each part, File → Export, choose format, set options, save. Repeat 50 times. Time: Hours.

Python approach: One script, runs unattended. Time: Minutes.

import FreeCAD as App
import Mesh
import os
def batch_export_stl(parts_directory, output_directory):
"""
Export all FreeCAD parts in a directory to STL format.
"""
# Create output directory if it doesn't exist
os.makedirs(output_directory, exist_ok=True)
# Find all .FCStd files
part_files = [f for f in os.listdir(parts_directory) if f.endswith('.FCStd')]
print(f"Found {len(part_files)} parts to export")
for part_file in part_files:
# Open document
file_path = os.path.join(parts_directory, part_file)
doc = App.open(file_path)
# Get all Part objects
objects = [obj for obj in doc.Objects if hasattr(obj, 'Shape')]
for obj in objects:
# Export to STL
output_name = f"{part_file[:-6]}_{obj.Name}.stl"
output_path = os.path.join(output_directory, output_name)
Mesh.export([obj], output_path)
print(f" ✓ Exported: {output_name}")
App.closeDocument(doc.Name)
print(f"\n✓ Batch export complete: {len(part_files)} parts processed")
# Export all parts
batch_export_stl(
parts_directory='/path/to/robot_parts',
output_directory='/path/to/stl_output'
)

Automated Drawing Generation

import TechDraw
import TechDrawGui
def create_standard_drawing(part_name, part_object):
"""
Generate technical drawing with standard views and dimensions.
"""
# Create drawing page
page = App.ActiveDocument.addObject('TechDraw::DrawPage', f'Drawing_{part_name}')
template = App.ActiveDocument.addObject('TechDraw::DrawSVGTemplate', 'Template')
template.Template = '/path/to/template_A4.svg'
page.Template = template
# Add front view
front_view = App.ActiveDocument.addObject('TechDraw::DrawViewPart', 'FrontView')
page.addView(front_view)
front_view.Source = [part_object]
front_view.Direction = App.Vector(0, -1, 0) # Looking from front
front_view.X = 50
front_view.Y = 150
front_view.Scale = 1.0
# Add top view
top_view = App.ActiveDocument.addObject('TechDraw::DrawViewPart', 'TopView')
page.addView(top_view)
top_view.Source = [part_object]
top_view.Direction = App.Vector(0, 0, -1) # Looking from top
top_view.X = 50
top_view.Y = 50
top_view.Scale = 1.0
# Add isometric view
iso_view = App.ActiveDocument.addObject('TechDraw::DrawViewPart', 'IsoView')
page.addView(iso_view)
iso_view.Source = [part_object]
iso_view.Direction = App.Vector(1, 1, 1) # Isometric
iso_view.X = 150
iso_view.Y = 150
iso_view.Scale = 0.8
# Update title block
# ... (fill in part name, date, etc.)
# Export to PDF
output_pdf = f'/path/to/drawings/{part_name}.pdf'
page.ViewObject.exportPdf(output_pdf)
print(f"✓ Drawing created: {part_name}.pdf")
# Process all parts
for part in App.ActiveDocument.Objects:
if hasattr(part, 'Shape'):
create_standard_drawing(part.Name, part)

🎓 Learning Outcomes

Congratulations! By completing this lesson, you have:

✅ Understood Python's Power

When scripting beats GUI, what’s possible with code

✅ Generated Algorithmic Geometry

Involute gears, lattice structures, mathematical shapes

✅ Implemented Optimization

Design space exploration, iterative improvement

✅ Processed Data

Point clouds, measurements, data-driven design

✅ Automated Workflows

Batch exports, drawing generation, mass operations

✅ Applied to Research

Real applications in aerospace, biomedical, manufacturing

Most importantly: You’ve unlocked FreeCAD’s full computational power—you can now solve problems impossible with GUI alone!

🚀 Advanced Challenges



Ready to push further? Try these advanced projects:

  1. Helical gear generator

    • Extend involute gear to 3D helical teeth
    • Parametric helix angle
  2. Voronoi lattice

    • Random Voronoi cell generation
    • Organic-looking structures
  3. Parametric spring

    • Compression spring with variable pitch
    • Export coil specifications

📚 Python Resources

Learning Python for CAD

📖 Recommended Resources

Python Basics:

  • Python.org official tutorial
  • “Automate the Boring Stuff with Python” (book)

FreeCAD Python API:

  • FreeCAD Wiki: Python scripting tutorial
  • FreeCAD API documentation
  • FreeCAD forum Python subforum

Scientific Computing:

  • NumPy documentation (numerical arrays)
  • SciPy documentation (scientific algorithms)
  • Matplotlib documentation (plotting)

Optimization:

  • SciPy optimization tutorial
  • DEAP (genetic algorithms in Python)
  • OpenMDAO (NASA’s optimization framework)

FreeCAD Macro Libraries

🔍 Debugging Python in FreeCAD

# Use print() liberally to track execution
print("Starting gear generation...")
for tooth_num in range(num_teeth):
print(f" Generating tooth {tooth_num+1}/{num_teeth}")
# ... code ...
print("✓ Gear complete!")

📊 Case Study: Research Publication



🔬 Real Research Example

Title: “Optimization of Lattice Structure Density Gradients for Additive Manufacturing of Load-Bearing Implants”

Problem: Design hip implant with porosity matching bone stiffness to prevent stress shielding

Python Implementation:

  1. CT scan import - Patient femur geometry
  2. FEA simulation - Stress distribution analysis
  3. Density optimization - Python loop varying lattice density
  4. Lattice generation - Cubic lattice with variable cell size
  5. Validation - FEA on final design

Results:

  • 60% weight reduction vs. solid titanium
  • Modulus matched to bone (±10%)
  • Predicted bone ingrowth improvement: 40%

Code: 500 lines of Python

Design variants tested: 1,000+

Computation time: 48 hours (automated)

Manual equivalent: Months of work

Publication: Journal of Biomechanical Engineering, 2024

❓ Common Issues

“Module not found: numpy”

🎉 Conclusion

Python scripting transforms FreeCAD from a manual design tool into a computational design platform. You can now generate complexity that would be impossible to model by hand, explore design spaces systematically, and automate workflows that would take days manually. This is advanced CAD at the cutting edge of engineering and research.

You’ve learned:

  • Algorithmic geometry generation (gears, lattices)
  • Design optimization and exploration
  • Data-driven design from measurements
  • Batch automation and workflow scripting
  • Real research applications

Next steps:

  • Apply Python to your own design problems
  • Contribute scripts to FreeCAD community
  • Explore advanced optimization algorithms
  • Integrate with research tools (FEA, CFD, AI)


Comments

© 2021-2025 SiliconWit. All rights reserved.