pre-bemis
This commit is contained in:
232
rpi-case/.claude/skills/openscad/SKILL.md
Normal file
232
rpi-case/.claude/skills/openscad/SKILL.md
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
name: openscad
|
||||
description: "Create and render OpenSCAD 3D models. Generate preview images from multiple angles, extract customizable parameters, validate syntax, and export STL files for 3D printing platforms like MakerWorld."
|
||||
---
|
||||
|
||||
# OpenSCAD Skill
|
||||
|
||||
Create, validate, and export OpenSCAD 3D models. Supports parameter customization, visual preview from multiple angles, and STL export for 3D printing platforms like MakerWorld.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
OpenSCAD must be installed. Install via Homebrew:
|
||||
```bash
|
||||
brew install openscad
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
This skill provides several tools in the `tools/` directory:
|
||||
|
||||
### Preview Generation
|
||||
```bash
|
||||
# Generate a single preview image
|
||||
./tools/preview.sh model.scad output.png [--camera=x,y,z,tx,ty,tz,dist] [--size=800x600]
|
||||
|
||||
# Generate multi-angle preview (front, back, left, right, top, iso)
|
||||
./tools/multi-preview.sh model.scad output_dir/
|
||||
```
|
||||
|
||||
### STL Export
|
||||
```bash
|
||||
# Export to STL for 3D printing
|
||||
./tools/export-stl.sh model.scad output.stl [-D 'param=value']
|
||||
```
|
||||
|
||||
### Parameter Extraction
|
||||
```bash
|
||||
# Extract customizable parameters from an OpenSCAD file
|
||||
./tools/extract-params.sh model.scad
|
||||
```
|
||||
|
||||
### Validation
|
||||
```bash
|
||||
# Check for syntax errors and warnings
|
||||
./tools/validate.sh model.scad
|
||||
```
|
||||
|
||||
## Visual Validation (Required)
|
||||
|
||||
**Always validate your OpenSCAD models visually after creating or modifying them.**
|
||||
|
||||
After writing or editing any OpenSCAD file:
|
||||
|
||||
1. **Generate multi-angle previews** using `multi-preview.sh`
|
||||
2. **View each generated image** using the `read` tool
|
||||
3. **Check for issues** from multiple perspectives:
|
||||
- Front/back: Verify symmetry, features, and proportions
|
||||
- Left/right: Check depth and side profiles
|
||||
- Top: Ensure top features are correct
|
||||
- Isometric: Overall shape validation
|
||||
4. **Iterate if needed**: If something looks wrong, fix the code and re-validate
|
||||
|
||||
This catches issues that syntax validation alone cannot detect:
|
||||
- Inverted normals or inside-out geometry
|
||||
- Misaligned features or incorrect boolean operations
|
||||
- Proportions that don't match the intended design
|
||||
- Missing or floating geometry
|
||||
- Z-fighting or overlapping surfaces
|
||||
|
||||
**Never deliver an OpenSCAD model without visually confirming it looks correct from multiple angles.**
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Creating an OpenSCAD Model
|
||||
|
||||
Write OpenSCAD code with customizable parameters at the top:
|
||||
|
||||
```openscad
|
||||
// Customizable parameters
|
||||
wall_thickness = 2; // [1:0.5:5] Wall thickness in mm
|
||||
width = 50; // [20:100] Width in mm
|
||||
height = 30; // [10:80] Height in mm
|
||||
rounded = true; // Add rounded corners
|
||||
|
||||
// Model code below
|
||||
module main_shape() {
|
||||
if (rounded) {
|
||||
minkowski() {
|
||||
cube([width - 4, width - 4, height - 2]);
|
||||
sphere(r = 2);
|
||||
}
|
||||
} else {
|
||||
cube([width, width, height]);
|
||||
}
|
||||
}
|
||||
|
||||
difference() {
|
||||
main_shape();
|
||||
translate([wall_thickness, wall_thickness, wall_thickness])
|
||||
scale([1 - 2*wall_thickness/width, 1 - 2*wall_thickness/width, 1])
|
||||
main_shape();
|
||||
}
|
||||
```
|
||||
|
||||
Parameter comment format:
|
||||
- `// [min:max]` - numeric range
|
||||
- `// [min:step:max]` - numeric range with step
|
||||
- `// [opt1, opt2, opt3]` - dropdown options
|
||||
- `// Description text` - plain description
|
||||
|
||||
### 2. Validate the Model
|
||||
```bash
|
||||
./tools/validate.sh model.scad
|
||||
```
|
||||
|
||||
### 3. Generate Previews
|
||||
|
||||
Generate preview images to visually validate the model:
|
||||
```bash
|
||||
./tools/multi-preview.sh model.scad ./previews/
|
||||
```
|
||||
|
||||
This creates PNG images from multiple angles. Use the `read` tool to view them.
|
||||
|
||||
### 4. Export to STL
|
||||
```bash
|
||||
./tools/export-stl.sh model.scad output.stl
|
||||
# With custom parameters:
|
||||
./tools/export-stl.sh model.scad output.stl -D 'width=60' -D 'height=40'
|
||||
```
|
||||
|
||||
## Camera Positions
|
||||
|
||||
Common camera angles for previews:
|
||||
- **Isometric**: `--camera=0,0,0,45,0,45,200`
|
||||
- **Front**: `--camera=0,0,0,90,0,0,200`
|
||||
- **Top**: `--camera=0,0,0,0,0,0,200`
|
||||
- **Right**: `--camera=0,0,0,90,0,90,200`
|
||||
|
||||
Format: `x,y,z,rotx,roty,rotz,distance`
|
||||
|
||||
## MakerWorld Publishing
|
||||
|
||||
For MakerWorld, you typically need:
|
||||
1. STL file(s) exported via `export-stl.sh`
|
||||
2. Preview images (at least one good isometric view)
|
||||
3. A description of customizable parameters
|
||||
|
||||
Consider creating a `model.json` with metadata:
|
||||
```json
|
||||
{
|
||||
"name": "Model Name",
|
||||
"description": "Description for MakerWorld",
|
||||
"parameters": [...],
|
||||
"tags": ["functional", "container", "organizer"]
|
||||
}
|
||||
```
|
||||
|
||||
## Example: Full Workflow
|
||||
|
||||
```bash
|
||||
# 1. Create the model (write .scad file)
|
||||
|
||||
# 2. Validate syntax
|
||||
./tools/validate.sh box.scad
|
||||
|
||||
# 3. Generate multi-angle previews
|
||||
./tools/multi-preview.sh box.scad ./previews/
|
||||
|
||||
# 4. IMPORTANT: View and validate ALL preview images
|
||||
# Use the read tool on each PNG file to visually inspect:
|
||||
# - previews/box_front.png
|
||||
# - previews/box_back.png
|
||||
# - previews/box_left.png
|
||||
# - previews/box_right.png
|
||||
# - previews/box_top.png
|
||||
# - previews/box_iso.png
|
||||
# Look for geometry issues, misalignments, or unexpected results.
|
||||
# If anything looks wrong, go back to step 1 and fix it!
|
||||
|
||||
# 5. Extract and review parameters
|
||||
./tools/extract-params.sh box.scad
|
||||
|
||||
# 6. Export STL with default parameters
|
||||
./tools/export-stl.sh box.scad box.stl
|
||||
|
||||
# 7. Export STL with custom parameters
|
||||
./tools/export-stl.sh box.scad box_large.stl -D 'width=80' -D 'height=60'
|
||||
```
|
||||
|
||||
**Remember**: Never skip the visual validation step. Many issues (wrong dimensions, boolean operation errors, inverted geometry) are only visible when you actually look at the rendered model.
|
||||
|
||||
## OpenSCAD Quick Reference
|
||||
|
||||
### Basic Shapes
|
||||
```openscad
|
||||
cube([x, y, z]);
|
||||
sphere(r = radius);
|
||||
cylinder(h = height, r = radius);
|
||||
cylinder(h = height, r1 = bottom_r, r2 = top_r); // cone
|
||||
```
|
||||
|
||||
### Transformations
|
||||
```openscad
|
||||
translate([x, y, z]) object();
|
||||
rotate([rx, ry, rz]) object();
|
||||
scale([sx, sy, sz]) object();
|
||||
mirror([x, y, z]) object();
|
||||
```
|
||||
|
||||
### Boolean Operations
|
||||
```openscad
|
||||
union() { a(); b(); } // combine
|
||||
difference() { a(); b(); } // subtract b from a
|
||||
intersection() { a(); b(); } // overlap only
|
||||
```
|
||||
|
||||
### Advanced
|
||||
```openscad
|
||||
linear_extrude(height) 2d_shape();
|
||||
rotate_extrude() 2d_shape();
|
||||
hull() { objects(); } // convex hull
|
||||
minkowski() { a(); b(); } // minkowski sum (rounding)
|
||||
```
|
||||
|
||||
### 2D Shapes
|
||||
```openscad
|
||||
circle(r = radius);
|
||||
square([x, y]);
|
||||
polygon(points = [[x1,y1], [x2,y2], ...]);
|
||||
text("string", size = 10);
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
// Parametric Box with Lid
|
||||
// A customizable storage box for 3D printing
|
||||
|
||||
// === Box Parameters ===
|
||||
width = 60; // [20:200] Width in mm
|
||||
depth = 40; // [20:200] Depth in mm
|
||||
height = 30; // [10:150] Height in mm
|
||||
wall_thickness = 2; // [1:0.5:5] Wall thickness in mm
|
||||
|
||||
// === Lid Parameters ===
|
||||
include_lid = true; // Include a separate lid
|
||||
lid_height = 8; // [5:30] Lid height in mm
|
||||
lid_tolerance = 0.3; // [0.1:0.1:0.8] Gap for lid fit
|
||||
|
||||
// === Style Options ===
|
||||
corner_radius = 3; // [0:10] Corner rounding radius
|
||||
add_grip = true; // Add grip indents to lid
|
||||
|
||||
// === Internal ===
|
||||
$fn = 32; // Smoothness
|
||||
|
||||
// Rounded box module
|
||||
module rounded_box(w, d, h, r) {
|
||||
if (r > 0) {
|
||||
hull() {
|
||||
for (x = [r, w-r]) {
|
||||
for (y = [r, d-r]) {
|
||||
translate([x, y, 0])
|
||||
cylinder(h = h, r = r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cube([w, d, h]);
|
||||
}
|
||||
}
|
||||
|
||||
// Main box body
|
||||
module box_body() {
|
||||
difference() {
|
||||
rounded_box(width, depth, height, corner_radius);
|
||||
|
||||
// Hollow inside
|
||||
translate([wall_thickness, wall_thickness, wall_thickness])
|
||||
rounded_box(
|
||||
width - 2*wall_thickness,
|
||||
depth - 2*wall_thickness,
|
||||
height, // Open top
|
||||
max(0, corner_radius - wall_thickness)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Lid
|
||||
module lid() {
|
||||
inner_w = width - 2*wall_thickness - 2*lid_tolerance;
|
||||
inner_d = depth - 2*wall_thickness - 2*lid_tolerance;
|
||||
lip_height = lid_height * 0.6;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// Top cap
|
||||
rounded_box(width, depth, wall_thickness, corner_radius);
|
||||
|
||||
// Inner lip
|
||||
translate([wall_thickness + lid_tolerance, wall_thickness + lid_tolerance, -lip_height + wall_thickness])
|
||||
rounded_box(inner_w, inner_d, lip_height, max(0, corner_radius - wall_thickness));
|
||||
}
|
||||
|
||||
// Grip indents
|
||||
if (add_grip) {
|
||||
for (x = [width * 0.3, width * 0.7]) {
|
||||
translate([x, -1, wall_thickness/2])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(h = 5, r = 3, $fn = 16);
|
||||
translate([x, depth - 4, wall_thickness/2])
|
||||
rotate([-90, 0, 0])
|
||||
cylinder(h = 5, r = 3, $fn = 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
box_body();
|
||||
|
||||
if (include_lid) {
|
||||
// Position lid next to box for printing
|
||||
translate([width + 10, 0, lid_height - wall_thickness])
|
||||
rotate([180, 0, 0])
|
||||
lid();
|
||||
}
|
||||
95
rpi-case/.claude/skills/openscad/examples/phone_stand.scad
Normal file
95
rpi-case/.claude/skills/openscad/examples/phone_stand.scad
Normal file
@@ -0,0 +1,95 @@
|
||||
// Adjustable Phone/Tablet Stand
|
||||
// Parametric stand with customizable angle and size
|
||||
|
||||
// === Device Parameters ===
|
||||
device_width = 80; // [50:200] Device width in mm
|
||||
device_thickness = 12; // [6:20] Device thickness (with case)
|
||||
|
||||
// === Stand Parameters ===
|
||||
stand_angle = 65; // [45:85] Viewing angle in degrees
|
||||
stand_depth = 80; // [50:150] Base depth in mm
|
||||
stand_height = 100; // [60:200] Back support height in mm
|
||||
|
||||
// === Construction ===
|
||||
material_thickness = 4; // [2:0.5:8] Material thickness
|
||||
slot_depth = 15; // [10:30] How deep device sits in slot
|
||||
|
||||
// === Features ===
|
||||
cable_hole = true; // Add cable pass-through hole
|
||||
cable_diameter = 15; // [8:25] Cable hole diameter
|
||||
add_feet = true; // Add anti-slip feet
|
||||
|
||||
// === Quality ===
|
||||
$fn = 48;
|
||||
|
||||
module stand_profile() {
|
||||
// 2D profile of the stand side
|
||||
polygon([
|
||||
[0, 0], // Front bottom
|
||||
[stand_depth, 0], // Back bottom
|
||||
[stand_depth, material_thickness], // Back bottom inner
|
||||
[stand_depth - material_thickness, material_thickness], // Base top back
|
||||
[slot_depth + material_thickness, material_thickness], // Base top front (behind slot)
|
||||
[slot_depth + material_thickness, slot_depth * tan(90 - stand_angle) + material_thickness], // Slot back
|
||||
[material_thickness, slot_depth * tan(90 - stand_angle) + material_thickness + device_thickness / sin(stand_angle)], // Slot front top
|
||||
[0, slot_depth * tan(90 - stand_angle) + material_thickness], // Front face bottom of slot
|
||||
[0, 0] // Close
|
||||
]);
|
||||
}
|
||||
|
||||
module back_support() {
|
||||
// Back angled support
|
||||
translate([stand_depth - material_thickness, 0, material_thickness]) {
|
||||
rotate([0, -90 + stand_angle, 0]) {
|
||||
cube([stand_height, device_width, material_thickness]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module cable_cutout() {
|
||||
if (cable_hole) {
|
||||
translate([stand_depth/2, device_width/2, -1])
|
||||
cylinder(h = material_thickness + 2, d = cable_diameter);
|
||||
}
|
||||
}
|
||||
|
||||
module foot() {
|
||||
cylinder(h = 2, d1 = 10, d2 = 8);
|
||||
}
|
||||
|
||||
module stand() {
|
||||
difference() {
|
||||
union() {
|
||||
// Left side
|
||||
linear_extrude(material_thickness)
|
||||
stand_profile();
|
||||
|
||||
// Right side
|
||||
translate([0, device_width - material_thickness, 0])
|
||||
linear_extrude(material_thickness)
|
||||
stand_profile();
|
||||
|
||||
// Base plate
|
||||
cube([stand_depth, device_width, material_thickness]);
|
||||
|
||||
// Front lip
|
||||
cube([material_thickness, device_width, slot_depth * tan(90 - stand_angle) + material_thickness]);
|
||||
|
||||
// Back support
|
||||
back_support();
|
||||
}
|
||||
|
||||
// Cable hole
|
||||
cable_cutout();
|
||||
}
|
||||
|
||||
// Feet
|
||||
if (add_feet) {
|
||||
translate([10, 10, 0]) foot();
|
||||
translate([10, device_width - 10, 0]) foot();
|
||||
translate([stand_depth - 10, 10, 0]) foot();
|
||||
translate([stand_depth - 10, device_width - 10, 0]) foot();
|
||||
}
|
||||
}
|
||||
|
||||
stand();
|
||||
50
rpi-case/.claude/skills/openscad/tools/common.sh
Executable file
50
rpi-case/.claude/skills/openscad/tools/common.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Common utilities for OpenSCAD tools
|
||||
|
||||
# Find OpenSCAD executable
|
||||
find_openscad() {
|
||||
# Check common locations
|
||||
if command -v openscad &> /dev/null; then
|
||||
echo "openscad"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# macOS Application bundle
|
||||
if [ -d "/Applications/OpenSCAD.app" ]; then
|
||||
echo "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Homebrew on Apple Silicon
|
||||
if [ -x "/opt/homebrew/bin/openscad" ]; then
|
||||
echo "/opt/homebrew/bin/openscad"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Homebrew on Intel
|
||||
if [ -x "/usr/local/bin/openscad" ]; then
|
||||
echo "/usr/local/bin/openscad"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if OpenSCAD is available
|
||||
check_openscad() {
|
||||
OPENSCAD=$(find_openscad) || {
|
||||
echo "Error: OpenSCAD not found!"
|
||||
echo ""
|
||||
echo "Install OpenSCAD using one of:"
|
||||
echo " brew install openscad"
|
||||
echo " Download from https://openscad.org/downloads.html"
|
||||
exit 1
|
||||
}
|
||||
export OPENSCAD
|
||||
}
|
||||
|
||||
# Get version info
|
||||
openscad_version() {
|
||||
check_openscad
|
||||
$OPENSCAD --version 2>&1
|
||||
}
|
||||
56
rpi-case/.claude/skills/openscad/tools/export-stl.sh
Executable file
56
rpi-case/.claude/skills/openscad/tools/export-stl.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
# Export OpenSCAD file to STL
|
||||
# Usage: export-stl.sh input.scad output.stl [-D 'var=value' ...]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
check_openscad
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 input.scad output.stl [-D 'var=value' ...]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 box.scad box.stl"
|
||||
echo " $0 box.scad box_large.stl -D 'width=80' -D 'height=60'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
OUTPUT="$2"
|
||||
shift 2
|
||||
|
||||
# Collect -D parameters
|
||||
DEFINES=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-D)
|
||||
shift
|
||||
DEFINES+=("-D" "$1")
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Ensure output directory exists
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
|
||||
echo "Exporting STL: $INPUT -> $OUTPUT"
|
||||
if [ ${#DEFINES[@]} -gt 0 ]; then
|
||||
echo "Parameters: ${DEFINES[*]}"
|
||||
fi
|
||||
|
||||
$OPENSCAD \
|
||||
"${DEFINES[@]}" \
|
||||
-o "$OUTPUT" \
|
||||
"$INPUT"
|
||||
|
||||
# Show file info
|
||||
SIZE=$(ls -lh "$OUTPUT" | awk '{print $5}')
|
||||
echo "STL exported: $OUTPUT ($SIZE)"
|
||||
147
rpi-case/.claude/skills/openscad/tools/extract-params.sh
Executable file
147
rpi-case/.claude/skills/openscad/tools/extract-params.sh
Executable file
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
# Extract customizable parameters from an OpenSCAD file
|
||||
# Usage: extract-params.sh input.scad [--json]
|
||||
#
|
||||
# Parses parameter declarations with special comments:
|
||||
# param = value; // [min:max] Description
|
||||
# param = value; // [min:step:max] Description
|
||||
# param = value; // [opt1, opt2] Description
|
||||
# param = value; // Description only
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 input.scad [--json]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
JSON_OUTPUT=false
|
||||
|
||||
if [ "$2" = "--json" ]; then
|
||||
JSON_OUTPUT=true
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT" ]; then
|
||||
echo "Error: File not found: $INPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract parameters using Python for better parsing
|
||||
extract_params() {
|
||||
python3 -c '
|
||||
import sys
|
||||
import re
|
||||
|
||||
filename = sys.argv[1]
|
||||
in_block = 0
|
||||
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
# Track block depth (skip params inside modules/functions)
|
||||
in_block += line.count("{") - line.count("}")
|
||||
if in_block > 0:
|
||||
continue
|
||||
|
||||
# Match: varname = value; // comment
|
||||
match = re.match(r"^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*([^;]+);\s*(?://\s*(.*))?", line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
var_name = match.group(1)
|
||||
value = match.group(2).strip()
|
||||
comment = match.group(3) or ""
|
||||
|
||||
# Determine type
|
||||
if value in ("true", "false"):
|
||||
var_type = "boolean"
|
||||
elif re.match(r"^-?\d+$", value):
|
||||
var_type = "integer"
|
||||
elif re.match(r"^-?\d*\.?\d+$", value):
|
||||
var_type = "number"
|
||||
elif value.startswith("\"") and value.endswith("\""):
|
||||
var_type = "string"
|
||||
value = value[1:-1] # Remove quotes
|
||||
elif value.startswith("["):
|
||||
var_type = "array"
|
||||
else:
|
||||
var_type = "expression"
|
||||
|
||||
# Parse comment for range/options
|
||||
range_val = ""
|
||||
options_val = ""
|
||||
description = comment
|
||||
|
||||
range_match = re.match(r"\[([^\]]+)\]\s*(.*)", comment)
|
||||
if range_match:
|
||||
bracket_content = range_match.group(1)
|
||||
description = range_match.group(2)
|
||||
|
||||
# Check if numeric range (contains :) or options (contains ,)
|
||||
if ":" in bracket_content and not "," in bracket_content:
|
||||
range_val = bracket_content
|
||||
else:
|
||||
options_val = bracket_content
|
||||
|
||||
# Output pipe-delimited
|
||||
print(f"{var_name}|{value}|{var_type}|{range_val}|{options_val}|{description}")
|
||||
' "$INPUT"
|
||||
}
|
||||
|
||||
if [ "$JSON_OUTPUT" = true ]; then
|
||||
echo "["
|
||||
first=true
|
||||
while IFS='|' read -r name value type range options description; do
|
||||
if [ "$first" = true ]; then
|
||||
first=false
|
||||
else
|
||||
echo ","
|
||||
fi
|
||||
|
||||
# Escape quotes in values
|
||||
value=$(echo "$value" | sed 's/"/\\"/g')
|
||||
description=$(echo "$description" | sed 's/"/\\"/g')
|
||||
|
||||
# Build JSON object
|
||||
printf ' {\n'
|
||||
printf ' "name": "%s",\n' "$name"
|
||||
printf ' "value": "%s",\n' "$value"
|
||||
printf ' "type": "%s"' "$type"
|
||||
|
||||
if [ -n "$range" ]; then
|
||||
printf ',\n "range": "%s"' "$range"
|
||||
fi
|
||||
if [ -n "$options" ]; then
|
||||
printf ',\n "options": "%s"' "$options"
|
||||
fi
|
||||
if [ -n "$description" ]; then
|
||||
printf ',\n "description": "%s"' "$description"
|
||||
fi
|
||||
printf '\n }'
|
||||
done < <(extract_params)
|
||||
echo ""
|
||||
echo "]"
|
||||
else
|
||||
echo "Parameters in: $INPUT"
|
||||
echo "==============================================="
|
||||
printf "%-20s %-15s %-10s %s\n" "NAME" "VALUE" "TYPE" "CONSTRAINT/DESC"
|
||||
echo "-----------------------------------------------"
|
||||
|
||||
while IFS='|' read -r name value type range options description; do
|
||||
constraint=""
|
||||
if [ -n "$range" ]; then
|
||||
constraint="[$range]"
|
||||
elif [ -n "$options" ]; then
|
||||
constraint="[$options]"
|
||||
fi
|
||||
if [ -n "$description" ]; then
|
||||
if [ -n "$constraint" ]; then
|
||||
constraint="$constraint $description"
|
||||
else
|
||||
constraint="$description"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "%-20s %-15s %-10s %s\n" "$name" "$value" "$type" "$constraint"
|
||||
done < <(extract_params)
|
||||
fi
|
||||
68
rpi-case/.claude/skills/openscad/tools/multi-preview.sh
Executable file
68
rpi-case/.claude/skills/openscad/tools/multi-preview.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
# Generate preview images from multiple angles
|
||||
# Usage: multi-preview.sh input.scad output_dir/ [-D 'var=value']
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
check_openscad
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 input.scad output_dir/ [-D 'var=value' ...]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
OUTPUT_DIR="$2"
|
||||
shift 2
|
||||
|
||||
# Collect -D parameters
|
||||
DEFINES=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-D)
|
||||
shift
|
||||
DEFINES+=("-D" "$1")
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Get base name without extension
|
||||
BASENAME=$(basename "$INPUT" .scad)
|
||||
|
||||
echo "Generating multi-angle previews for: $INPUT"
|
||||
echo "Output directory: $OUTPUT_DIR"
|
||||
echo ""
|
||||
|
||||
# Define angles as name:camera pairs
|
||||
# Camera format: translate_x,translate_y,translate_z,rot_x,rot_y,rot_z,distance
|
||||
ANGLES="iso:0,0,0,55,0,25,0
|
||||
front:0,0,0,90,0,0,0
|
||||
back:0,0,0,90,0,180,0
|
||||
left:0,0,0,90,0,90,0
|
||||
right:0,0,0,90,0,-90,0
|
||||
top:0,0,0,0,0,0,0"
|
||||
|
||||
echo "$ANGLES" | while IFS=: read -r angle camera; do
|
||||
output="$OUTPUT_DIR/${BASENAME}_${angle}.png"
|
||||
|
||||
echo " Rendering $angle view..."
|
||||
$OPENSCAD \
|
||||
--camera="$camera" \
|
||||
--imgsize="800,600" \
|
||||
--colorscheme="Tomorrow Night" \
|
||||
--autocenter \
|
||||
--viewall \
|
||||
"${DEFINES[@]}" \
|
||||
-o "$output" \
|
||||
"$INPUT" 2>/dev/null
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Generated previews:"
|
||||
ls -la "$OUTPUT_DIR"/${BASENAME}_*.png
|
||||
74
rpi-case/.claude/skills/openscad/tools/preview.sh
Executable file
74
rpi-case/.claude/skills/openscad/tools/preview.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# Generate a preview PNG from an OpenSCAD file
|
||||
# Usage: preview.sh input.scad output.png [options]
|
||||
#
|
||||
# Options:
|
||||
# --camera=x,y,z,rx,ry,rz,dist Camera position
|
||||
# --size=WxH Image size (default: 800x600)
|
||||
# -D 'var=value' Set parameter value
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
check_openscad
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 input.scad output.png [--camera=...] [--size=WxH] [-D 'var=val']"
|
||||
echo ""
|
||||
echo "Camera format: x,y,z,rotx,roty,rotz,distance"
|
||||
echo "Common cameras:"
|
||||
echo " Isometric: --camera=0,0,0,55,0,25,200"
|
||||
echo " Front: --camera=0,0,0,90,0,0,200"
|
||||
echo " Top: --camera=0,0,0,0,0,0,200"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
OUTPUT="$2"
|
||||
shift 2
|
||||
|
||||
# Defaults
|
||||
CAMERA="0,0,0,55,0,25,0"
|
||||
SIZE="800,600"
|
||||
DEFINES=()
|
||||
|
||||
# Parse options
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--camera=*)
|
||||
CAMERA="${1#--camera=}"
|
||||
;;
|
||||
--size=*)
|
||||
SIZE="${1#--size=}"
|
||||
SIZE="${SIZE/x/,}"
|
||||
;;
|
||||
-D)
|
||||
shift
|
||||
DEFINES+=("-D" "$1")
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Ensure output directory exists
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
|
||||
# Run OpenSCAD
|
||||
echo "Rendering preview: $INPUT -> $OUTPUT"
|
||||
$OPENSCAD \
|
||||
--camera="$CAMERA" \
|
||||
--imgsize="${SIZE}" \
|
||||
--colorscheme="Tomorrow Night" \
|
||||
--autocenter \
|
||||
--viewall \
|
||||
"${DEFINES[@]}" \
|
||||
-o "$OUTPUT" \
|
||||
"$INPUT"
|
||||
|
||||
echo "Preview saved to: $OUTPUT"
|
||||
91
rpi-case/.claude/skills/openscad/tools/render-with-params.sh
Executable file
91
rpi-case/.claude/skills/openscad/tools/render-with-params.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
# Render OpenSCAD with parameters from a JSON file
|
||||
# Usage: render-with-params.sh input.scad params.json output.stl|output.png
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
check_openscad
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: $0 input.scad params.json output.[stl|png]"
|
||||
echo ""
|
||||
echo "params.json format:"
|
||||
echo ' {"width": 60, "height": 40, "include_lid": true}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
PARAMS_FILE="$2"
|
||||
OUTPUT="$3"
|
||||
|
||||
if [ ! -f "$INPUT" ]; then
|
||||
echo "Error: Input file not found: $INPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$PARAMS_FILE" ]; then
|
||||
echo "Error: Params file not found: $PARAMS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build -D arguments from JSON
|
||||
DEFINES=()
|
||||
while IFS= read -r line; do
|
||||
# Parse each key-value pair
|
||||
key=$(echo "$line" | cut -d'=' -f1)
|
||||
value=$(echo "$line" | cut -d'=' -f2-)
|
||||
|
||||
if [ -n "$key" ]; then
|
||||
DEFINES+=("-D" "$key=$value")
|
||||
fi
|
||||
done < <(
|
||||
# Use python or jq to parse JSON to key=value lines
|
||||
if command -v python3 &> /dev/null; then
|
||||
python3 -c "
|
||||
import json
|
||||
with open('$PARAMS_FILE') as f:
|
||||
params = json.load(f)
|
||||
for k, v in params.items():
|
||||
if isinstance(v, bool):
|
||||
print(f'{k}={str(v).lower()}')
|
||||
elif isinstance(v, str):
|
||||
print(f'{k}=\"{v}\"')
|
||||
else:
|
||||
print(f'{k}={v}')
|
||||
"
|
||||
elif command -v jq &> /dev/null; then
|
||||
jq -r 'to_entries | .[] | "\(.key)=\(.value)"' "$PARAMS_FILE"
|
||||
else
|
||||
echo "Error: Requires python3 or jq to parse JSON"
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
|
||||
echo "Rendering with parameters from: $PARAMS_FILE"
|
||||
echo "Parameters: ${DEFINES[*]}"
|
||||
|
||||
# Determine output type and set appropriate options
|
||||
EXT="${OUTPUT##*.}"
|
||||
case "$EXT" in
|
||||
stl|STL)
|
||||
$OPENSCAD "${DEFINES[@]}" -o "$OUTPUT" "$INPUT"
|
||||
;;
|
||||
png|PNG)
|
||||
$OPENSCAD "${DEFINES[@]}" \
|
||||
--camera="0,0,0,55,0,25,0" \
|
||||
--imgsize="800,600" \
|
||||
--colorscheme="Tomorrow Night" \
|
||||
--autocenter --viewall \
|
||||
-o "$OUTPUT" "$INPUT"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported output format: $EXT"
|
||||
echo "Supported: stl, png"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Output saved: $OUTPUT"
|
||||
46
rpi-case/.claude/skills/openscad/tools/validate.sh
Executable file
46
rpi-case/.claude/skills/openscad/tools/validate.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Validate an OpenSCAD file for syntax errors
|
||||
# Usage: validate.sh input.scad
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
check_openscad
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 input.scad"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
|
||||
if [ ! -f "$INPUT" ]; then
|
||||
echo "Error: File not found: $INPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Validating: $INPUT"
|
||||
|
||||
# Create temp file for output
|
||||
TEMP_OUTPUT=$(mktemp /tmp/openscad_validate.XXXXXX.echo)
|
||||
trap "rm -f $TEMP_OUTPUT" EXIT
|
||||
|
||||
# Run OpenSCAD with echo output (fastest way to check syntax)
|
||||
# Using --export-format=echo just parses and evaluates without rendering
|
||||
if $OPENSCAD -o "$TEMP_OUTPUT" --export-format=echo "$INPUT" 2>&1; then
|
||||
echo "✓ Syntax OK"
|
||||
|
||||
# Check for warnings in stderr
|
||||
if [ -s "$TEMP_OUTPUT" ]; then
|
||||
echo ""
|
||||
echo "Echo output:"
|
||||
cat "$TEMP_OUTPUT"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
else
|
||||
echo "✗ Validation failed"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user