---
name: openscad-mechanical
description: 'Create and edit OpenSCAD (.scad) files for 3D-printable mechanical parts and housings. Use when: modeling enclosures, brackets, mounting plates, standoffs, panels, or any parametric mechanical part. Covers OpenSCAD language syntax, project conventions, boolean operations, module and function design, tolerance/clearance choices, and print-orientation notes.'
argument-hint: 'Describe the part you need (e.g., "mounting bracket 40×20 mm, two M3 holes")'
---

# OpenSCAD Mechanical Part Authoring

## When to Use

Load this skill when asked to:
- Create or update a `.scad` file for any printable mechanical part (housing, frame, bracket, spacer, backplate, faceplate …)
- Add parametric models for PCB mounting, connector cutouts, or panel assemblies
- Adapt tolerances or clearances for a new printer/material
- Explain or review OpenSCAD geometry logic

## Language Reference

Full language reference: [language-reference.md](./references/language-reference.md)

Quick syntax recap:
- Variables are **single-assignment** within a scope; use `let()` for local rebinding.
- Modules define reusable 3-D geometry; functions return values (no geometry).
- Boolean: `union()`, `difference()`, `intersection()`.
- 3-D primitives: `cube([x,y,z])`, `sphere(r)`, `cylinder(h,d)`, `polyhedron()`.
- 2-D primitives + extrusion: `square()`, `circle()`, `polygon()` → `linear_extrude()` / `rotate_extrude()`.
- Transforms: `translate()`, `rotate()`, `mirror()`, `scale()`, `resize()`.
- Rounding via 2-D `offset(r)` inside `linear_extrude()`, or `minkowski()`.
- Special variables: `$fn` (facets), `$fs` (min-segment size), `$fa` (min-angle).
- Flow: `for()`, `if/else`, conditional `? :`.
- Includes: `use <file.scad>` (modules/functions only), `include <file.scad>` (full execution).

## Project Conventions (this repository)

Derived from the existing `doc/boards/hub75/housing/hub75.scad`:

### File Header

```scad
// <Short description of what this part is>
//
// Dimensions: <link or "measured" or "N/A">
// Units: mm
// Print orientation:
// - <part-name>: <orientation advice>
//
// Author: <Name> (<email>)
// License: MIT License (https://opensource.org/licenses/MIT)
```

### Parameter Block Order

1. **Global constants** — `eps`, `fit_tolerance`, `rounded_fn`
2. **Component parameters** — one group per physical object, prefixed (`frame_`, `panel_`, `board_`, …)
3. **Derived dimensions** — calculated from the above, never hard-coded
4. **Preview/export selector** — `part = "assembly";`

```scad
// Global constants
eps           = 0.01;  // Boolean overlap in mm
fit_tolerance = 0.2;   // Assembly clearance in mm
rounded_fn    = 96;    // Facet count for outer curves

// Part selector (assembly | frame | faceplate | …)
part = "assembly";
```

### Naming Conventions

| Item | Convention | Example |
|------|-----------|---------|
| Parameters | `snake_case` prefixed by component | `frame_wall_thickness` |
| Modules | `snake_case` verbs or nouns | `rounded_cube_xz()`, `board_mount_pattern()` |
| Pure functions | `snake_case` | `centered_offset(outer, inner)` |
| Booleans | descriptive flag | `show_panel = false;` |

### Inline Comment Rule

Every parameter must have a `// Description in mm` (or relevant unit) comment on the same line:

```scad
frame_wall_thickness = 3; // Thickness of the frame walls in mm
```

### Boolean Operation Patterns

Always add `eps` overlap to avoid z-fighting / non-manifold edges:

```scad
eps = 0.01;

difference() {
    cube([width, depth, height]);
    // Cutout starts eps below the surface, extends eps beyond the far side
    translate([x, y, -eps])
        cylinder(d=hole_d, h=height + 2*eps, $fn=48);
}
```

### Hole Sizing Guidelines

| Purpose | Diameter formula |
|---------|-----------------|
| Clearance (bolt passes through) | `bolt_nominal + 0.4` |
| Tapped / press-fit | `bolt_nominal - 0.2` (tune per material) |
| General assembly fit | `nominal + fit_tolerance` |

### Rounded Corners

Prefer the 2-D `offset(r)` + `linear_extrude()` pattern (avoids Minkowski overhead):

```scad
module rounded_profile_xz(width, height, radius) {
    $fn = rounded_fn;
    if (radius > 0)
        translate([radius, radius])
            offset(r=radius)
                square([width - 2*radius, height - 2*radius]);
    else
        square([width, height]);
}

module rounded_cube_xz(size_xyz, radius) {
    translate([0, size_xyz[1], 0])
        rotate([90, 0, 0])
            linear_extrude(height=size_xyz[1])
                rounded_profile_xz(size_xyz[0], size_xyz[2], radius);
}
```

### Part Selector Pattern

Use a top-level `if` chain on a `part` string so each piece can be exported independently or shown in assembly:

```scad
if      (part == "assembly") { assembly(); }
else if (part == "frame")    { frame(); }
else if (part == "faceplate"){ faceplate(); }
```

## Procedure

### 1. Gather Requirements

Collect these before writing any geometry:
- Overall envelope (width × height × depth in mm)
- List of sub-parts to model (frame, lid, spacer, …)
- External components that must fit inside (PCB, connectors, panels)
- Mounting interfaces (screw holes, snap fits, press fits)
- Print orientation constraints (overhang limits, layer strength direction)
- Clearances and tolerances (default: `fit_tolerance = 0.2 mm`)

### 2. Read Existing File (if editing)

Always read the full file before modifying. Identify:
- Existing parameter names to reuse
- Module and function signatures to extend, not replace
- The `part` selector — add new values rather than removing old ones

### 3. Structure the File

Use the template in [assets/template.scad](./assets/template.scad):
1. File header comment
2. Global constants block
3. Per-component parameter blocks (ordered by assembly depth: outermost first)
4. Derived dimensions
5. `part` selector variable
6. Pure functions
7. Reusable geometry modules (primitives → composites)
8. Top-level part modules
9. `if/else` part-selector dispatcher

### 4. Implement Geometry

**Work outside-in** (define outer shell first, then subtract holes/cutouts):

1. Build the solid body as a `union()` or single primitive.
2. Subtract connector cutouts, mounting holes, and internal cavities via `difference()`.
3. Add small features last (chamfers, lips, standoffs).

**Parametrize everything** — no magic numbers in geometry calls. Every numeric literal must trace back to a named variable.

### 5. Verify

Check all of the following before finishing:
- [ ] No hard-coded mm values in geometry calls (only in the parameter block)
- [ ] Every parameter has a `// ... in mm` (or unit) inline comment
- [ ] `eps` is applied to all `difference()` cutouts
- [ ] Holes use the correct clearance formula (see table above)
- [ ] `$fn` or `$fn=48` set on every `cylinder()` / `circle()`
- [ ] Print orientation note added to file header
- [ ] Part selector works: each `part` value renders without error
- [ ] Assembly preview (`part = "assembly"`) shows correct relative positions

### 6. Export Notes

- Render to STL per part: `File → Export → Export as STL` (or CLI: `openscad -o part.stl -D 'part="frame"' file.scad`)
- For FFF printing: slice with part-specific orientation noted in the file header
- Recommended `$fn` for final export: ≥ 96 for outer curves, 48 for holes
