Former/svg.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")
}
}
}