183 lines
4.8 KiB
Go
183 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
)
|
|
|
|
func WriteSVG(filename string, gf *GerberFile, bounds *Bounds) error {
|
|
b := *bounds
|
|
|
|
widthMM := b.MaxX - b.MinX
|
|
heightMM := b.MaxY - b.MinY
|
|
|
|
f, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
// Use mm directly for SVG
|
|
fmt.Fprintf(f, `<svg xmlns="http://www.w3.org/2000/svg" width="%fmm" height="%fmm" viewBox="0 0 %f %f">`,
|
|
widthMM, heightMM, widthMM, heightMM)
|
|
fmt.Fprintf(f, "\n<g fill=\"black\" stroke=\"black\">\n")
|
|
|
|
// Note: SVG Y-axis points down. We need to invert Y: (heightMM - (y - b.MinY))
|
|
toSVGX := func(x float64) float64 { return x - b.MinX }
|
|
toSVGY := func(y float64) float64 { return heightMM - (y - b.MinY) }
|
|
|
|
curX, curY := 0.0, 0.0
|
|
curDCode := 0
|
|
interpolationMode := "G01" // Default linear
|
|
inRegion := false
|
|
var regionVertices [][2]float64
|
|
|
|
for _, cmd := range gf.Commands {
|
|
if cmd.Type == "APERTURE" {
|
|
curDCode = *cmd.D
|
|
continue
|
|
}
|
|
if cmd.Type == "G01" || cmd.Type == "G02" || cmd.Type == "G03" {
|
|
interpolationMode = cmd.Type
|
|
continue
|
|
}
|
|
if cmd.Type == "G36" {
|
|
inRegion = true
|
|
regionVertices = nil
|
|
continue
|
|
}
|
|
if cmd.Type == "G37" {
|
|
if len(regionVertices) >= 3 {
|
|
fmt.Fprintf(f, `<polygon points="`)
|
|
for _, v := range regionVertices {
|
|
fmt.Fprintf(f, "%f,%f ", toSVGX(v[0]), toSVGY(v[1]))
|
|
}
|
|
fmt.Fprintf(f, "\" fill=\"black\" stroke=\"none\"/>\n")
|
|
}
|
|
inRegion = false
|
|
regionVertices = nil
|
|
continue
|
|
}
|
|
|
|
prevX, prevY := curX, curY
|
|
if cmd.X != nil {
|
|
curX = *cmd.X
|
|
}
|
|
if cmd.Y != nil {
|
|
curY = *cmd.Y
|
|
}
|
|
|
|
if inRegion {
|
|
if cmd.Type == "MOVE" || (cmd.Type == "DRAW" && interpolationMode == "G01") {
|
|
regionVertices = append(regionVertices, [2]float64{curX, curY})
|
|
} else if cmd.Type == "DRAW" && (interpolationMode == "G02" || interpolationMode == "G03") {
|
|
iVal, jVal := 0.0, 0.0
|
|
if cmd.I != nil {
|
|
iVal = *cmd.I
|
|
}
|
|
if cmd.J != nil {
|
|
jVal = *cmd.J
|
|
}
|
|
arcPts := approximateArc(prevX, prevY, curX, curY, iVal, jVal, interpolationMode)
|
|
for _, pt := range arcPts {
|
|
regionVertices = append(regionVertices, pt)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
if cmd.Type == "FLASH" {
|
|
ap, ok := gf.State.Apertures[curDCode]
|
|
if ok {
|
|
writeSVGAperture(f, toSVGX(curX), toSVGY(curY), ap, false)
|
|
}
|
|
} else if cmd.Type == "DRAW" {
|
|
ap, ok := gf.State.Apertures[curDCode]
|
|
if ok {
|
|
// Basic stroke representation for lines
|
|
w := 0.1 // default
|
|
if len(ap.Modifiers) > 0 {
|
|
w = ap.Modifiers[0]
|
|
}
|
|
|
|
if interpolationMode == "G01" {
|
|
fmt.Fprintf(f, `<line x1="%f" y1="%f" x2="%f" y2="%f" stroke-width="%f" stroke-linecap="round"/>`+"\n",
|
|
toSVGX(prevX), toSVGY(prevY), toSVGX(curX), toSVGY(curY), w)
|
|
} else {
|
|
iVal, jVal := 0.0, 0.0
|
|
if cmd.I != nil {
|
|
iVal = *cmd.I
|
|
}
|
|
if cmd.J != nil {
|
|
jVal = *cmd.J
|
|
}
|
|
|
|
// SVG path arc (Y-axis inverted: G02 CW -> CCW in SVG, G03 CCW -> CW in SVG)
|
|
r := math.Sqrt(iVal*iVal + jVal*jVal)
|
|
acx, acy := prevX+iVal, prevY+jVal
|
|
sa := math.Atan2(prevY-acy, prevX-acx)
|
|
ea := math.Atan2(curY-acy, curX-acx)
|
|
var arcSpan float64
|
|
if interpolationMode == "G03" {
|
|
if ea <= sa { ea += 2 * math.Pi }
|
|
arcSpan = ea - sa
|
|
} else {
|
|
if sa <= ea { sa += 2 * math.Pi }
|
|
arcSpan = sa - ea
|
|
}
|
|
largeArc := 0
|
|
if arcSpan > math.Pi { largeArc = 1 }
|
|
sweep := 1 // G03 CCW Gerber -> CW SVG
|
|
if interpolationMode == "G02" { sweep = 0 }
|
|
|
|
fmt.Fprintf(f, `<path d="M %f %f A %f %f 0 %d %d %f %f" stroke-width="%f" fill="none" stroke-linecap="round"/>` + "\n",
|
|
toSVGX(prevX), toSVGY(prevY), r, r, largeArc, sweep, toSVGX(curX), toSVGY(curY), w)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fmt.Fprintf(f, "</g>\n</svg>\n")
|
|
return nil
|
|
}
|
|
|
|
func writeSVGAperture(f *os.File, cx, cy float64, ap Aperture, isMacro bool) {
|
|
switch ap.Type {
|
|
case "C":
|
|
if len(ap.Modifiers) > 0 {
|
|
r := ap.Modifiers[0] / 2
|
|
fmt.Fprintf(f, `<circle cx="%f" cy="%f" r="%f" />`+"\n", cx, cy, r)
|
|
}
|
|
case "R":
|
|
if len(ap.Modifiers) >= 2 {
|
|
w, h := ap.Modifiers[0], ap.Modifiers[1]
|
|
fmt.Fprintf(f, `<rect x="%f" y="%f" width="%f" height="%f" />`+"\n", cx-w/2, cy-h/2, w, h)
|
|
}
|
|
case "O":
|
|
if len(ap.Modifiers) >= 2 {
|
|
w, h := ap.Modifiers[0], ap.Modifiers[1]
|
|
r := w / 2
|
|
if h < w {
|
|
r = h / 2
|
|
}
|
|
fmt.Fprintf(f, `<rect x="%f" y="%f" width="%f" height="%f" rx="%f" ry="%f" />`+"\n", cx-w/2, cy-h/2, w, h, r, r)
|
|
}
|
|
case "P":
|
|
if len(ap.Modifiers) >= 2 {
|
|
dia, numV := ap.Modifiers[0], int(ap.Modifiers[1])
|
|
r := dia / 2
|
|
rot := 0.0
|
|
if len(ap.Modifiers) >= 3 {
|
|
rot = ap.Modifiers[2]
|
|
}
|
|
fmt.Fprintf(f, `<polygon points="`)
|
|
for i := 0; i < numV; i++ {
|
|
a := (rot + float64(i)*360.0/float64(numV)) * math.Pi / 180.0
|
|
// SVG inverted Y means we might need minus for Sin
|
|
fmt.Fprintf(f, "%f,%f ", cx+r*math.Cos(a), cy-r*math.Sin(a))
|
|
}
|
|
fmt.Fprintf(f, `" />`+"\n")
|
|
}
|
|
}
|
|
}
|