pcb-to-stencil/scad.go

257 lines
8.7 KiB
Go

package main
import (
"fmt"
"math"
"os"
)
func WriteSCAD(filename string, triangles [][3]Point) error {
// Fallback/legacy mesh WriteSCAD
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
fmt.Fprintf(f, "// Generated by pcb-to-stencil\npolyhedron(\n points=[\n")
for i, t := range triangles {
fmt.Fprintf(f, " [%f, %f, %f], [%f, %f, %f], [%f, %f, %f]", t[0].X, t[0].Y, t[0].Z, t[1].X, t[1].Y, t[1].Z, t[2].X, t[2].Y, t[2].Z)
if i < len(triangles)-1 {
fmt.Fprintf(f, ",\n")
} else {
fmt.Fprintf(f, "\n")
}
}
fmt.Fprintf(f, " ],\n faces=[\n")
for i := 0; i < len(triangles); i++ {
idx := i * 3
fmt.Fprintf(f, " [%d, %d, %d]", idx, idx+1, idx+2)
if i < len(triangles)-1 {
fmt.Fprintf(f, ",\n")
} else {
fmt.Fprintf(f, "\n")
}
}
fmt.Fprintf(f, " ]\n);\n")
return nil
}
// ExtractPolygonFromGerber traces the Edge.Cuts gerber to form a continuous 2D polygon
func ExtractPolygonFromGerber(gf *GerberFile) [][2]float64 {
var points [][2]float64
curX, curY := 0.0, 0.0
interpolationMode := "G01"
for _, cmd := range gf.Commands {
if cmd.Type == "G01" || cmd.Type == "G02" || cmd.Type == "G03" {
interpolationMode = cmd.Type
continue
}
prevX, prevY := curX, curY
if cmd.X != nil {
curX = *cmd.X
}
if cmd.Y != nil {
curY = *cmd.Y
}
if cmd.Type == "DRAW" {
if len(points) == 0 {
points = append(points, [2]float64{prevX, prevY})
}
if interpolationMode == "G01" {
points = append(points, [2]float64{curX, curY})
} else {
iVal, jVal := 0.0, 0.0
if cmd.I != nil {
iVal = *cmd.I
}
if cmd.J != nil {
jVal = *cmd.J
}
centerX, centerY := prevX+iVal, prevY+jVal
radius := math.Sqrt(iVal*iVal + jVal*jVal)
startAngle := math.Atan2(prevY-centerY, prevX-centerX)
endAngle := math.Atan2(curY-centerY, curX-centerX)
if interpolationMode == "G03" {
if endAngle <= startAngle {
endAngle += 2 * math.Pi
}
} else {
if startAngle <= endAngle {
startAngle += 2 * math.Pi
}
}
arcLen := math.Abs(endAngle-startAngle) * radius
steps := int(arcLen * 10)
if steps < 5 {
steps = 5
}
for s := 1; s <= steps; s++ {
t := float64(s) / float64(steps)
a := startAngle + t*(endAngle-startAngle)
ax, ay := centerX+radius*math.Cos(a), centerY+radius*math.Sin(a)
points = append(points, [2]float64{ax, ay})
}
}
}
}
return points
}
// WriteNativeSCAD generates native parametrically defined CSG OpenSCAD code
func WriteNativeSCAD(filename string, isTray bool, outlineVertices [][2]float64, cfg EnclosureConfig, holes []DrillHole, cutouts []SideCutout, minBX, maxBX, boardCenterY float64) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
fmt.Fprintf(f, "// Generated by pcb-to-stencil (Native SCAD)\n")
fmt.Fprintf(f, "$fn = 60;\n\n")
// 1. Output the Board Polygon Module
fmt.Fprintf(f, "module board_polygon() {\n polygon(points=[\n")
for i, v := range outlineVertices {
fmt.Fprintf(f, " [%f, %f]", v[0], v[1])
if i < len(outlineVertices)-1 {
fmt.Fprintf(f, ",\n")
}
}
fmt.Fprintf(f, "\n ]);\n}\n\n")
// Dimensions
clearance := cfg.Clearance
wt := cfg.WallThickness
lidThick := wt
snapHeight := 2.5
trayFloor := 1.5
pcbT := cfg.PCBThickness
totalH := cfg.WallHeight + pcbT + trayFloor
lipH := pcbT + 1.5
// Create Peg and Socket helper
fmt.Fprintf(f, "module mounting_pegs(isSocket) {\n")
for _, h := range holes {
if h.Type == DrillTypeMounting {
r := (h.Diameter / 2.0) - 0.15
if isTray {
// We subtract sockets from the tray floor
r = (h.Diameter / 2.0) + 0.1
fmt.Fprintf(f, " translate([%f, %f, -1]) cylinder(r=%f, h=%f);\n", h.X, h.Y, r, trayFloor+2)
} else {
fmt.Fprintf(f, " translate([%f, %f, 0]) cylinder(r=%f, h=%f);\n", h.X, h.Y, r, totalH-lidThick)
}
}
}
fmt.Fprintf(f, "}\n\n")
// Print Side Cutouts module
fmt.Fprintf(f, "module side_cutouts() {\n")
for _, c := range cutouts {
// Cutouts are relative to board.
x, y, z := 0.0, 0.0, c.Height/2+trayFloor+pcbT
w, d, h := c.Width, 20.0, c.Height // d is deep enough to cut through walls
if c.Side == 0 { // Top
y = outlineVertices[0][1] + 10 // rough outside pos
x = c.X
fmt.Fprintf(f, " translate([%f, %f, %f]) cube([%f, %f, %f], center=true);\n", x, y, z, w, d, h)
} else if c.Side == 1 { // Right
x = maxBX
y = c.Y
fmt.Fprintf(f, " translate([%f, %f, %f]) cube([%f, %f, %f], center=true);\n", x, y, z, d, w, h)
} else if c.Side == 2 { // Bottom
y = outlineVertices[0][1] - 10
x = c.X
fmt.Fprintf(f, " translate([%f, %f, %f]) cube([%f, %f, %f], center=true);\n", x, y, z, w, d, h)
} else if c.Side == 3 { // Left
x = minBX
y = c.Y
fmt.Fprintf(f, " translate([%f, %f, %f]) cube([%f, %f, %f], center=true);\n", x, y, z, d, w, h)
}
}
fmt.Fprintf(f, "}\n\n")
// Print Pry Slots Module
fmt.Fprintf(f, "module pry_slots() {\n")
pryW := 8.0
pryD := 1.5
fmt.Fprintf(f, " translate([%f, %f, 0]) cube([%f, %f, %f], center=true);\n", minBX-clearance-wt+pryD/2, boardCenterY, pryD*2, pryW, snapHeight*3)
fmt.Fprintf(f, " translate([%f, %f, 0]) cube([%f, %f, %f], center=true);\n", maxBX+clearance+wt-pryD/2, boardCenterY, pryD*2, pryW, snapHeight*3)
fmt.Fprintf(f, "}\n\n")
if isTray {
// --- TRAY ---
fmt.Fprintf(f, "// --- TRAY ---\n")
fmt.Fprintf(f, "difference() {\n")
fmt.Fprintf(f, " union() {\n")
fmt.Fprintf(f, " // Tray Floor (extends to clearance + 2*wt so it is flush with enclosure outside)\n")
fmt.Fprintf(f, " linear_extrude(height=%f) offset(r=%f) board_polygon();\n", trayFloor, clearance+2*wt)
fmt.Fprintf(f, " // Tray Inner Wall (thickness wt)\n")
fmt.Fprintf(f, " translate([0,0,%f]) linear_extrude(height=%f) difference() {\n", trayFloor, snapHeight)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+wt)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance)
fmt.Fprintf(f, " }\n")
fmt.Fprintf(f, " // Snap Bumps (on outside of tray wall)\n")
fmt.Fprintf(f, " translate([0,0,%f]) linear_extrude(height=%f) difference() {\n", trayFloor+snapHeight-0.6, 0.4)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+wt+0.4)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+wt)
fmt.Fprintf(f, " }\n")
fmt.Fprintf(f, " // Mounting Pegs\n")
for _, hole := range holes {
if hole.Type != DrillTypeMounting {
continue
}
pegRadius := (hole.Diameter / 2.0) - 0.15
fmt.Fprintf(f, " translate([%f,%f,0]) cylinder(h=%f, r=%f, $fn=32);\n", hole.X, hole.Y, totalH-lidThick, pegRadius)
}
fmt.Fprintf(f, " }\n")
fmt.Fprintf(f, " // Subtract Lip Recess (for easy opening)\n")
fmt.Fprintf(f, " translate([0,0,-1]) linear_extrude(height=%f) difference() {\n", trayFloor+lipH+0.5)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+2*wt+1.0)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+2*wt-0.5) // Assuming lipCut is 0.5mm
fmt.Fprintf(f, " }\n")
// Remove peg holes from floor
for _, hole := range holes {
if hole.Type != DrillTypeMounting {
continue
}
socketRadius := (hole.Diameter / 2.0) + 0.1
fmt.Fprintf(f, " translate([%f,%f,-1]) cylinder(h=%f, r=%f, $fn=32);\n", hole.X, hole.Y, trayFloor+2, socketRadius)
}
fmt.Fprintf(f, " pry_slots();\n")
fmt.Fprintf(f, " side_cutouts();\n")
fmt.Fprintf(f, "}\n\n")
} else {
// --- ENCLOSURE ---
fmt.Fprintf(f, "// --- ENCLOSURE ---\n")
fmt.Fprintf(f, "difference() {\n")
fmt.Fprintf(f, " union() {\n")
fmt.Fprintf(f, " // Outer Enclosure block (accommodates Tray Wall + Enclosure Wall)\n")
fmt.Fprintf(f, " translate([0,0,%f]) linear_extrude(height=%f) offset(r=%f) board_polygon();\n", trayFloor, totalH-trayFloor, clearance+2*wt)
fmt.Fprintf(f, " }\n")
fmt.Fprintf(f, " // Subtract Inner Cavity (Base clearance around board)\n")
fmt.Fprintf(f, " translate([0,0,-1]) linear_extrude(height=%f) offset(r=%f) board_polygon();\n", totalH-lidThick+1, clearance)
fmt.Fprintf(f, " // Subtract Tray Recess (Accommodates Tray Wall)\n")
fmt.Fprintf(f, " translate([0,0,-1]) linear_extrude(height=%f) offset(r=%f) board_polygon();\n", trayFloor+snapHeight+0.2, clearance+wt+0.15)
fmt.Fprintf(f, " // Subtract Snap Groove\n")
fmt.Fprintf(f, " translate([0,0,%f]) linear_extrude(height=%f) difference() {\n", trayFloor+snapHeight-0.7, 0.6)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+wt+0.5)
fmt.Fprintf(f, " offset(r=%f) board_polygon();\n", clearance+wt)
fmt.Fprintf(f, " }\n")
fmt.Fprintf(f, " pry_slots();\n")
fmt.Fprintf(f, " side_cutouts();\n")
fmt.Fprintf(f, "}\n")
fmt.Fprintf(f, "mounting_pegs(false);\n")
}
return nil
}