Critical bug fixes for the conditions of initial workspace setup and additions to the build scripts which now should fetch all the build dependencies automatically on all platforms.
This commit is contained in:
parent
5d09f56e00
commit
968fd5a6c4
34
README.md
34
README.md
|
|
@ -30,13 +30,39 @@ A desktop application for generating 3D-printable solder paste stencils and snap
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- [Go](https://go.dev/dl/) 1.21+
|
#### macOS
|
||||||
- [Wails CLI](https://wails.io/docs/gettingstarted/installation): `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
|
|
||||||
- [Node.js](https://nodejs.org/) 18+ (for frontend build)
|
1. Install [Homebrew](https://brew.sh/) (if not already installed)
|
||||||
- macOS: Xcode Command Line Tools (`xcode-select --install`)
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
brew install go node
|
||||||
|
xcode-select --install # Xcode Command Line Tools
|
||||||
|
go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
|
||||||
|
1. Install dependencies (using [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), built into Windows 10/11):
|
||||||
|
```powershell
|
||||||
|
winget install GoLang.Go
|
||||||
|
winget install OpenJS.NodeJS.LTS
|
||||||
|
go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||||
|
```
|
||||||
|
Or download manually: [Go](https://go.dev/dl/) 1.21+, [Node.js](https://nodejs.org/) 18+
|
||||||
|
2. **WebView2 Runtime** — required by Wails. Pre-installed on most Windows 10/11 machines with Edge. If missing, download from [Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
|
||||||
|
|
||||||
|
#### Version Summary
|
||||||
|
|
||||||
|
| Tool | Minimum | Install docs |
|
||||||
|
|------|---------|--------------|
|
||||||
|
| Go | 1.21+ | https://go.dev/dl/ |
|
||||||
|
| Node.js | 18+ | https://nodejs.org/ |
|
||||||
|
| Wails CLI | v2 | https://wails.io/docs/gettingstarted/installation |
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
|
The build scripts check for missing dependencies and print install instructions if anything is missing.
|
||||||
|
|
||||||
**macOS:**
|
**macOS:**
|
||||||
```bash
|
```bash
|
||||||
./build.sh
|
./build.sh
|
||||||
|
|
|
||||||
61
app.go
61
app.go
|
|
@ -143,6 +143,7 @@ func (a *App) autosaveCutouts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) startup(ctx context.Context) {
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
debugLog("app.startup() called")
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
|
|
||||||
// Render and cache the logo (PNG for favicon, SVG for background art)
|
// Render and cache the logo (PNG for favicon, SVG for background art)
|
||||||
|
|
@ -153,15 +154,19 @@ func (a *App) startup(ctx context.Context) {
|
||||||
a.imageServer.Store("/api/logo.png", buf.Bytes())
|
a.imageServer.Store("/api/logo.png", buf.Bytes())
|
||||||
}
|
}
|
||||||
a.imageServer.Store("/api/logo.svg", formerLogoSVG)
|
a.imageServer.Store("/api/logo.svg", formerLogoSVG)
|
||||||
|
debugLog("app.startup() done, logo stored (svg=%d bytes)", len(formerLogoSVG))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======== Landing Page ========
|
// ======== Landing Page ========
|
||||||
|
|
||||||
func (a *App) GetRecentProjects() []ProjectInfoJS {
|
func (a *App) GetRecentProjects() []ProjectInfoJS {
|
||||||
|
debugLog("GetRecentProjects() called")
|
||||||
entries, err := ListProjects(20)
|
entries, err := ListProjects(20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
debugLog("GetRecentProjects() error: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
debugLog("GetRecentProjects() found %d projects", len(entries))
|
||||||
var result []ProjectInfoJS
|
var result []ProjectInfoJS
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
name := e.Data.ProjectName
|
name := e.Data.ProjectName
|
||||||
|
|
@ -194,6 +199,13 @@ func (a *App) GetLogoDataURL() string {
|
||||||
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
|
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetLogoSVGDataURL() string {
|
||||||
|
if len(formerLogoSVG) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString(formerLogoSVG)
|
||||||
|
}
|
||||||
|
|
||||||
// ======== File Dialogs ========
|
// ======== File Dialogs ========
|
||||||
|
|
||||||
func (a *App) SelectFile(title string, patterns string) (string, error) {
|
func (a *App) SelectFile(title string, patterns string) (string, error) {
|
||||||
|
|
@ -300,10 +312,15 @@ func (a *App) DiscoverGerberFiles(folderPath string) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) BuildEnclosureSession(gbrjobPath string, gerberPaths []string, drillPath, npthPath string, wallThickness, wallHeight, clearance, dpi float64, exports []string) error {
|
func (a *App) BuildEnclosureSession(gbrjobPath string, gerberPaths []string, drillPath, npthPath string, wallThickness, wallHeight, clearance, dpi float64, exports []string) error {
|
||||||
|
debugLog("BuildEnclosureSession() called: gbrjob=%s gerbers=%d drill=%q npth=%q", gbrjobPath, len(gerberPaths), drillPath, npthPath)
|
||||||
|
debugLog(" params: wallThick=%.2f wallH=%.2f clearance=%.2f dpi=%.0f exports=%v", wallThickness, wallHeight, clearance, dpi, exports)
|
||||||
|
|
||||||
if gbrjobPath == "" {
|
if gbrjobPath == "" {
|
||||||
|
debugLog(" ERROR: no gerber job file selected")
|
||||||
return fmt.Errorf("no gerber job file selected")
|
return fmt.Errorf("no gerber job file selected")
|
||||||
}
|
}
|
||||||
if len(gerberPaths) == 0 {
|
if len(gerberPaths) == 0 {
|
||||||
|
debugLog(" ERROR: no gerber files selected")
|
||||||
return fmt.Errorf("no gerber files selected")
|
return fmt.Errorf("no gerber files selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,12 +346,21 @@ func (a *App) BuildEnclosureSession(gbrjobPath string, gerberPaths []string, dri
|
||||||
exports = []string{"stl", "scad"}
|
exports = []string{"stl", "scad"}
|
||||||
}
|
}
|
||||||
|
|
||||||
os.MkdirAll("temp", 0755)
|
tempDir := formerTempDir()
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
debugLog(" CWD=%s, tempDir=%s", cwd, tempDir)
|
||||||
|
if err := os.MkdirAll(tempDir, 0755); err != nil {
|
||||||
|
debugLog(" ERROR creating temp dir: %v", err)
|
||||||
|
return fmt.Errorf("failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
uuid := randomID()
|
uuid := randomID()
|
||||||
|
debugLog(" session uuid=%s", uuid)
|
||||||
|
|
||||||
// Copy gbrjob
|
// Copy gbrjob
|
||||||
gbrjobDst := filepath.Join("temp", uuid+"_"+filepath.Base(gbrjobPath))
|
gbrjobDst := filepath.Join(tempDir, uuid+"_"+filepath.Base(gbrjobPath))
|
||||||
|
debugLog(" copying gbrjob %s -> %s", gbrjobPath, gbrjobDst)
|
||||||
if err := CopyFile(gbrjobPath, gbrjobDst); err != nil {
|
if err := CopyFile(gbrjobPath, gbrjobDst); err != nil {
|
||||||
|
debugLog(" ERROR copy gbrjob: %v", err)
|
||||||
return fmt.Errorf("failed to copy gbrjob: %v", err)
|
return fmt.Errorf("failed to copy gbrjob: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,8 +369,10 @@ func (a *App) BuildEnclosureSession(gbrjobPath string, gerberPaths []string, dri
|
||||||
var sourceDir string
|
var sourceDir string
|
||||||
for _, src := range gerberPaths {
|
for _, src := range gerberPaths {
|
||||||
baseName := filepath.Base(src)
|
baseName := filepath.Base(src)
|
||||||
dst := filepath.Join("temp", uuid+"_"+baseName)
|
dst := filepath.Join(tempDir, uuid+"_"+baseName)
|
||||||
|
debugLog(" copying gerber %s -> %s", src, dst)
|
||||||
if err := CopyFile(src, dst); err != nil {
|
if err := CopyFile(src, dst); err != nil {
|
||||||
|
debugLog(" ERROR copy gerber %s: %v", baseName, err)
|
||||||
return fmt.Errorf("failed to copy %s: %v", baseName, err)
|
return fmt.Errorf("failed to copy %s: %v", baseName, err)
|
||||||
}
|
}
|
||||||
savedGerbers[baseName] = dst
|
savedGerbers[baseName] = dst
|
||||||
|
|
@ -356,36 +384,46 @@ func (a *App) BuildEnclosureSession(gbrjobPath string, gerberPaths []string, dri
|
||||||
// Copy drill files
|
// Copy drill files
|
||||||
var drillDst, npthDst string
|
var drillDst, npthDst string
|
||||||
if drillPath != "" {
|
if drillPath != "" {
|
||||||
drillDst = filepath.Join("temp", uuid+"_drill"+filepath.Ext(drillPath))
|
drillDst = filepath.Join(tempDir, uuid+"_drill"+filepath.Ext(drillPath))
|
||||||
|
debugLog(" copying drill %s -> %s", drillPath, drillDst)
|
||||||
if err := CopyFile(drillPath, drillDst); err != nil {
|
if err := CopyFile(drillPath, drillDst); err != nil {
|
||||||
|
debugLog(" ERROR copy drill: %v", err)
|
||||||
return fmt.Errorf("failed to copy PTH drill: %v", err)
|
return fmt.Errorf("failed to copy PTH drill: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if npthPath != "" {
|
if npthPath != "" {
|
||||||
npthDst = filepath.Join("temp", uuid+"_npth"+filepath.Ext(npthPath))
|
npthDst = filepath.Join(tempDir, uuid+"_npth"+filepath.Ext(npthPath))
|
||||||
|
debugLog(" copying npth %s -> %s", npthPath, npthDst)
|
||||||
if err := CopyFile(npthPath, npthDst); err != nil {
|
if err := CopyFile(npthPath, npthDst); err != nil {
|
||||||
|
debugLog(" ERROR copy npth: %v", err)
|
||||||
return fmt.Errorf("failed to copy NPTH drill: %v", err)
|
return fmt.Errorf("failed to copy NPTH drill: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog(" calling core BuildEnclosureSession...")
|
||||||
_, session, err := BuildEnclosureSession(gbrjobDst, savedGerbers, drillDst, npthDst, ecfg, exports)
|
_, session, err := BuildEnclosureSession(gbrjobDst, savedGerbers, drillDst, npthDst, ecfg, exports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
debugLog(" ERROR session build: %v", err)
|
||||||
return fmt.Errorf("session build failed: %v", err)
|
return fmt.Errorf("session build failed: %v", err)
|
||||||
}
|
}
|
||||||
|
debugLog(" session built OK: project=%s board=%.1fx%.1fmm", session.ProjectName, session.BoardW, session.BoardH)
|
||||||
session.SourceDir = sourceDir
|
session.SourceDir = sourceDir
|
||||||
|
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
a.enclosureSession = session
|
a.enclosureSession = session
|
||||||
a.cutouts = nil
|
a.cutouts = nil
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
debugLog(" session stored in app state")
|
||||||
|
|
||||||
// Render board preview
|
// Render board preview
|
||||||
if session.OutlineImg != nil {
|
if session.OutlineImg != nil {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
png.Encode(&buf, session.OutlineImg)
|
png.Encode(&buf, session.OutlineImg)
|
||||||
a.imageServer.Store("/api/board-preview.png", buf.Bytes())
|
a.imageServer.Store("/api/board-preview.png", buf.Bytes())
|
||||||
|
debugLog(" board preview rendered (%d bytes)", buf.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugLog("BuildEnclosureSession() returning nil (success)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -605,6 +643,7 @@ func (a *App) ClearLidCutouts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GenerateEnclosureOutputs() (*GenerateResultJS, error) {
|
func (a *App) GenerateEnclosureOutputs() (*GenerateResultJS, error) {
|
||||||
|
debugLog("GenerateEnclosureOutputs() called")
|
||||||
a.mu.RLock()
|
a.mu.RLock()
|
||||||
session := a.enclosureSession
|
session := a.enclosureSession
|
||||||
allCutouts := make([]Cutout, len(a.cutouts))
|
allCutouts := make([]Cutout, len(a.cutouts))
|
||||||
|
|
@ -612,18 +651,22 @@ func (a *App) GenerateEnclosureOutputs() (*GenerateResultJS, error) {
|
||||||
a.mu.RUnlock()
|
a.mu.RUnlock()
|
||||||
|
|
||||||
if session == nil {
|
if session == nil {
|
||||||
|
debugLog(" ERROR: no enclosure session active")
|
||||||
return nil, fmt.Errorf("no enclosure session active")
|
return nil, fmt.Errorf("no enclosure session active")
|
||||||
}
|
}
|
||||||
|
|
||||||
outputDir := session.SourceDir
|
outputDir := session.SourceDir
|
||||||
if outputDir == "" {
|
if outputDir == "" {
|
||||||
outputDir = filepath.Join(".", "temp")
|
outputDir = formerTempDir()
|
||||||
}
|
}
|
||||||
|
debugLog(" outputDir=%s cutouts=%d", outputDir, len(allCutouts))
|
||||||
|
|
||||||
files, err := GenerateEnclosureOutputs(session, allCutouts, outputDir)
|
files, err := GenerateEnclosureOutputs(session, allCutouts, outputDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
debugLog(" ERROR generate: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
debugLog(" generated %d files", len(files))
|
||||||
|
|
||||||
// Auto-save session
|
// Auto-save session
|
||||||
inst := InstanceData{
|
inst := InstanceData{
|
||||||
|
|
@ -643,7 +686,7 @@ func (a *App) GenerateEnclosureOutputs() (*GenerateResultJS, error) {
|
||||||
ProjectName: session.ProjectName,
|
ProjectName: session.ProjectName,
|
||||||
Cutouts: allCutouts,
|
Cutouts: allCutouts,
|
||||||
}
|
}
|
||||||
if savedDir, saveErr := SaveSession(inst, filepath.Join(".", "temp"), session.OutlineImg); saveErr != nil {
|
if savedDir, saveErr := SaveSession(inst, formerTempDir(), session.OutlineImg); saveErr != nil {
|
||||||
log.Printf("Warning: could not save session: %v", saveErr)
|
log.Printf("Warning: could not save session: %v", saveErr)
|
||||||
} else {
|
} else {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
|
|
@ -697,7 +740,7 @@ func (a *App) SaveEnclosureProfile(name string) error {
|
||||||
}
|
}
|
||||||
sourceDir := session.SourceDir
|
sourceDir := session.SourceDir
|
||||||
if sourceDir == "" {
|
if sourceDir == "" {
|
||||||
sourceDir = filepath.Join(".", "temp")
|
sourceDir = formerTempDir()
|
||||||
}
|
}
|
||||||
savedDir, err := SaveProfile(inst, name, sourceDir, session.OutlineImg)
|
savedDir, err := SaveProfile(inst, name, sourceDir, session.OutlineImg)
|
||||||
if err == nil && savedDir != "" {
|
if err == nil && savedDir != "" {
|
||||||
|
|
@ -961,7 +1004,7 @@ func (a *App) GetOutputDir() (string, error) {
|
||||||
|
|
||||||
outputDir := session.SourceDir
|
outputDir := session.SourceDir
|
||||||
if outputDir == "" {
|
if outputDir == "" {
|
||||||
outputDir = filepath.Join(".", "temp")
|
outputDir = formerTempDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
absDir, err := filepath.Abs(outputDir)
|
absDir, err := filepath.Abs(outputDir)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,56 @@
|
||||||
@echo off
|
@echo off
|
||||||
REM Build Former for Windows — run this on a Windows machine with Go and Wails installed
|
REM Build Former for Windows
|
||||||
REM Prerequisites: Go 1.21+, Wails CLI (go install github.com/wailsapp/wails/v2/cmd/wails@latest)
|
setlocal
|
||||||
|
|
||||||
|
REM --- Dependency checks ---
|
||||||
|
set "missing="
|
||||||
|
|
||||||
|
where go >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
set "missing=1"
|
||||||
|
echo ERROR: Go not found.
|
||||||
|
echo Install: winget install GoLang.Go
|
||||||
|
echo Or download from https://go.dev/dl/
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
where node >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
set "missing=1"
|
||||||
|
echo ERROR: Node.js not found.
|
||||||
|
echo Install: winget install OpenJS.NodeJS.LTS
|
||||||
|
echo Or download from https://nodejs.org/
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
where wails >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
set "missing=1"
|
||||||
|
echo ERROR: Wails CLI not found.
|
||||||
|
echo Install: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||||
|
echo Docs: https://wails.io/docs/gettingstarted/installation
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check for WebView2 runtime (required by Wails on Windows)
|
||||||
|
reg query "HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
reg query "HKLM\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
set "missing=1"
|
||||||
|
echo ERROR: WebView2 runtime not found.
|
||||||
|
echo Usually pre-installed on Windows 10/11 with Edge.
|
||||||
|
echo Download: https://developer.microsoft.com/en-us/microsoft-edge/webview2/
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if defined missing (
|
||||||
|
echo Install the tools listed above and re-run this script.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM --- Build ---
|
||||||
echo Generating app icon...
|
echo Generating app icon...
|
||||||
go run ./cmd/genicon 2>nul && echo Icon generated. || echo Icon generation skipped.
|
go run ./cmd/genicon 2>nul && echo Icon generated. || echo Icon generation skipped.
|
||||||
|
|
||||||
|
|
@ -14,3 +63,4 @@ if %ERRORLEVEL% NEQ 0 (
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo Done: build\bin\Former.exe
|
echo Done: build\bin\Former.exe
|
||||||
|
endlocal
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,49 @@
|
||||||
# Cross-compile Former for Windows (amd64) from macOS/Linux
|
# Cross-compile Former for Windows (amd64) from macOS/Linux
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# --- Dependency checks ---
|
||||||
|
missing=()
|
||||||
|
|
||||||
|
if ! command -v go &>/dev/null; then
|
||||||
|
missing+=("go")
|
||||||
|
echo "ERROR: Go not found."
|
||||||
|
if [[ "$OSTYPE" == darwin* ]]; then
|
||||||
|
echo " Install: brew install go"
|
||||||
|
else
|
||||||
|
echo " Install: sudo apt install golang (or your distro's package manager)"
|
||||||
|
fi
|
||||||
|
echo " Or download from https://go.dev/dl/"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v node &>/dev/null; then
|
||||||
|
missing+=("node")
|
||||||
|
echo "ERROR: Node.js not found."
|
||||||
|
if [[ "$OSTYPE" == darwin* ]]; then
|
||||||
|
echo " Install: brew install node"
|
||||||
|
else
|
||||||
|
echo " Install: sudo apt install nodejs npm (or your distro's package manager)"
|
||||||
|
fi
|
||||||
|
echo " Or download from https://nodejs.org/"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v wails &>/dev/null && [ ! -x "$HOME/go/bin/wails" ]; then
|
||||||
|
missing+=("wails")
|
||||||
|
echo "ERROR: Wails CLI not found."
|
||||||
|
echo " Install: go install github.com/wailsapp/wails/v2/cmd/wails@latest"
|
||||||
|
echo " Docs: https://wails.io/docs/gettingstarted/installation"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
|
echo "Missing dependencies: ${missing[*]}"
|
||||||
|
echo "Install the tools listed above and re-run this script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Build ---
|
||||||
|
|
||||||
# Generate app icon (needs CGO on macOS for native SVG rendering)
|
# Generate app icon (needs CGO on macOS for native SVG rendering)
|
||||||
echo "Generating app icon..."
|
echo "Generating app icon..."
|
||||||
if [[ "$OSTYPE" == darwin* ]]; then
|
if [[ "$OSTYPE" == darwin* ]]; then
|
||||||
|
|
|
||||||
42
build.sh
42
build.sh
|
|
@ -1,6 +1,48 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build Former for macOS
|
# Build Former for macOS
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# --- Dependency checks ---
|
||||||
|
missing=()
|
||||||
|
|
||||||
|
if ! command -v go &>/dev/null; then
|
||||||
|
missing+=("go")
|
||||||
|
echo "ERROR: Go not found."
|
||||||
|
echo " Install: brew install go"
|
||||||
|
echo " Or download from https://go.dev/dl/"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v node &>/dev/null; then
|
||||||
|
missing+=("node")
|
||||||
|
echo "ERROR: Node.js not found."
|
||||||
|
echo " Install: brew install node"
|
||||||
|
echo " Or download from https://nodejs.org/"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v wails &>/dev/null && [ ! -x "$HOME/go/bin/wails" ]; then
|
||||||
|
missing+=("wails")
|
||||||
|
echo "ERROR: Wails CLI not found."
|
||||||
|
echo " Install: go install github.com/wailsapp/wails/v2/cmd/wails@latest"
|
||||||
|
echo " Docs: https://wails.io/docs/gettingstarted/installation"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! xcode-select -p &>/dev/null; then
|
||||||
|
missing+=("xcode-cli")
|
||||||
|
echo "ERROR: Xcode Command Line Tools not found."
|
||||||
|
echo " Install: xcode-select --install"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
|
echo "Missing dependencies: ${missing[*]}"
|
||||||
|
echo "Install the tools listed above and re-run this script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Build ---
|
||||||
pkill -f "Former.app" || true
|
pkill -f "Former.app" || true
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build Former for macOS — DEBUG mode (logs to ~/former/debug.log)
|
||||||
|
set -e
|
||||||
|
pkill -f "Former.app" || true
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
export SDKROOT=$(xcrun --show-sdk-path)
|
||||||
|
export CC=/usr/bin/clang
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
|
||||||
|
WAILS=$(command -v wails || echo "$HOME/go/bin/wails")
|
||||||
|
|
||||||
|
# Generate app icon from Former.svg
|
||||||
|
echo "Generating app icon..."
|
||||||
|
go run ./cmd/genicon 2>/dev/null && echo "Icon generated." || echo "Icon generation skipped."
|
||||||
|
|
||||||
|
echo "Building Former (DEBUG)..."
|
||||||
|
"$WAILS" build -skipbindings -tags debug
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Debug build complete. Logs will be written to ~/former/debug.log"
|
||||||
|
echo "Launching..."
|
||||||
|
open build/bin/Former.app
|
||||||
|
|
||||||
|
# Tail the log so you can see it live
|
||||||
|
sleep 1
|
||||||
|
echo ""
|
||||||
|
echo "=== Tailing ~/former/debug.log (Ctrl-C to stop) ==="
|
||||||
|
tail -f ~/former/debug.log 2>/dev/null || echo "Waiting for log file..."
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build !debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func debugLog(format string, args ...interface{}) {}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//go:build debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debugLogger *log.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
logDir := filepath.Join(home, "former")
|
||||||
|
os.MkdirAll(logDir, 0755)
|
||||||
|
logPath := filepath.Join(logDir, "debug.log")
|
||||||
|
|
||||||
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to stderr
|
||||||
|
debugLogger = log.New(os.Stderr, "[DEBUG] ", log.Ltime|log.Lshortfile)
|
||||||
|
debugLogger.Printf("Could not open %s: %v — logging to stderr", logPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogger = log.New(f, "[DEBUG] ", log.Ltime|log.Lshortfile)
|
||||||
|
debugLogger.Printf("=== Former debug session started %s ===", time.Now().Format(time.RFC3339))
|
||||||
|
debugLogger.Printf("Log file: %s", logPath)
|
||||||
|
fmt.Fprintf(os.Stderr, "[Former] Debug logging to %s\n", logPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugLog(format string, args ...interface{}) {
|
||||||
|
if debugLogger != nil {
|
||||||
|
debugLogger.Printf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,8 +53,7 @@ function navigate(page) {
|
||||||
async function initLanding() {
|
async function initLanding() {
|
||||||
const page = $('#page-landing');
|
const page = $('#page-landing');
|
||||||
const projects = await wails()?.GetRecentProjects() || [];
|
const projects = await wails()?.GetRecentProjects() || [];
|
||||||
|
const logoSrc = await wails()?.GetLogoSVGDataURL() || '';
|
||||||
let logoSrc = '/api/logo.png';
|
|
||||||
|
|
||||||
let projectsHTML = '';
|
let projectsHTML = '';
|
||||||
if (projects.length > 0) {
|
if (projects.length > 0) {
|
||||||
|
|
@ -73,13 +72,11 @@ async function initLanding() {
|
||||||
}
|
}
|
||||||
|
|
||||||
page.innerHTML = `
|
page.innerHTML = `
|
||||||
<div class="landing-bg-art">
|
|
||||||
<img src="/api/logo.svg" alt="" class="landing-bg-logo" onerror="this.style.display='none'">
|
|
||||||
</div>
|
|
||||||
<div class="landing-hero">
|
<div class="landing-hero">
|
||||||
<h1>Former</h1>
|
<h1>Former</h1>
|
||||||
<p>PCB Stencil & Enclosure Generator</p>
|
<p>PCB Stencil & Enclosure Generator</p>
|
||||||
</div>
|
</div>
|
||||||
|
${logoSrc ? `<div class="landing-bg-art"><img src="${logoSrc}" alt="" class="landing-bg-logo"></div>` : ''}
|
||||||
<div class="landing-actions">
|
<div class="landing-actions">
|
||||||
<div class="action-card" onclick="navigate('stencil')">
|
<div class="action-card" onclick="navigate('stencil')">
|
||||||
<h3>New Stencil</h3>
|
<h3>New Stencil</h3>
|
||||||
|
|
@ -286,19 +283,19 @@ function initEnclosure() {
|
||||||
<div class="card-title">Parameters</div>
|
<div class="card-title">Parameters</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<span class="form-label">Wall Thickness (mm)</span>
|
<span class="form-label">Wall Thickness (mm)</span>
|
||||||
<input class="form-input" id="enc-wall-thick" type="number" step="0.1" placeholder="1.5">
|
<input class="form-input" id="enc-wall-thick" type="number" step="0.1" value="1.5">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<span class="form-label">Wall Height (mm)</span>
|
<span class="form-label">Wall Height (mm)</span>
|
||||||
<input class="form-input" id="enc-wall-height" type="number" step="0.1" placeholder="10.0">
|
<input class="form-input" id="enc-wall-height" type="number" step="0.1" value="10.0">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<span class="form-label">Clearance (mm)</span>
|
<span class="form-label">Clearance (mm)</span>
|
||||||
<input class="form-input" id="enc-clearance" type="number" step="0.1" placeholder="0.3">
|
<input class="form-input" id="enc-clearance" type="number" step="0.1" value="0.3">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<span class="form-label">DPI</span>
|
<span class="form-label">DPI</span>
|
||||||
<input class="form-input" id="enc-dpi" type="number" step="1" placeholder="600">
|
<input class="form-input" id="enc-dpi" type="number" step="1" value="600">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
@ -415,8 +412,16 @@ async function buildEnclosure() {
|
||||||
if ($('#enc-svg')?.checked) exports.push('svg');
|
if ($('#enc-svg')?.checked) exports.push('svg');
|
||||||
if ($('#enc-png')?.checked) exports.push('png');
|
if ($('#enc-png')?.checked) exports.push('png');
|
||||||
|
|
||||||
|
console.log('[Former] buildEnclosure: gbrjob=' + state.enclosure.gbrjobPath +
|
||||||
|
' gerbers=' + state.enclosure.gerberPaths.length +
|
||||||
|
' drill=' + state.enclosure.drillPath +
|
||||||
|
' npth=' + state.enclosure.npthPath +
|
||||||
|
' exports=' + exports.join(','));
|
||||||
|
console.log('[Former] wails() =', wails());
|
||||||
|
|
||||||
showLoading('Building enclosure session...');
|
showLoading('Building enclosure session...');
|
||||||
try {
|
try {
|
||||||
|
console.log('[Former] calling BuildEnclosureSession...');
|
||||||
await wails().BuildEnclosureSession(
|
await wails().BuildEnclosureSession(
|
||||||
state.enclosure.gbrjobPath,
|
state.enclosure.gbrjobPath,
|
||||||
state.enclosure.gerberPaths,
|
state.enclosure.gerberPaths,
|
||||||
|
|
@ -428,9 +433,11 @@ async function buildEnclosure() {
|
||||||
parseFloat($('#enc-dpi')?.value) || 0,
|
parseFloat($('#enc-dpi')?.value) || 0,
|
||||||
exports,
|
exports,
|
||||||
);
|
);
|
||||||
|
console.log('[Former] BuildEnclosureSession succeeded, navigating to preview');
|
||||||
hideLoading();
|
hideLoading();
|
||||||
navigate('preview');
|
navigate('preview');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('[Former] BuildEnclosureSession FAILED:', e);
|
||||||
hideLoading();
|
hideLoading();
|
||||||
alert('Session build failed: ' + e);
|
alert('Session build failed: ' + e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,6 @@ body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: -8px;
|
|
||||||
margin-bottom: -60px;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
-webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 20%, rgba(0,0,0,0.5) 100%);
|
-webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,1) 20%, rgba(0,0,0,0.5) 100%);
|
||||||
|
|
|
||||||
5
main.go
5
main.go
|
|
@ -45,8 +45,11 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure working directories exist
|
// Ensure working directories exist
|
||||||
os.MkdirAll("temp", 0755)
|
cwd, _ := os.Getwd()
|
||||||
|
debugLog("CWD: %s", cwd)
|
||||||
|
debugLog("Ensuring ~/former dirs (includes temp)...")
|
||||||
ensureFormerDirs()
|
ensureFormerDirs()
|
||||||
|
debugLog("Startup complete, launching GUI")
|
||||||
|
|
||||||
runGUI()
|
runGUI()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
storage.go
19
storage.go
|
|
@ -17,6 +17,10 @@ func formerBaseDir() string {
|
||||||
return filepath.Join(home, "former")
|
return filepath.Join(home, "former")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formerTempDir() string {
|
||||||
|
return filepath.Join(formerBaseDir(), "temp")
|
||||||
|
}
|
||||||
|
|
||||||
func formerSessionsDir() string {
|
func formerSessionsDir() string {
|
||||||
return filepath.Join(formerBaseDir(), "sessions")
|
return filepath.Join(formerBaseDir(), "sessions")
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +30,19 @@ func formerProfilesDir() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureFormerDirs() {
|
func ensureFormerDirs() {
|
||||||
os.MkdirAll(formerSessionsDir(), 0755)
|
td := formerTempDir()
|
||||||
os.MkdirAll(formerProfilesDir(), 0755)
|
sd := formerSessionsDir()
|
||||||
|
pd := formerProfilesDir()
|
||||||
|
debugLog("ensureFormerDirs: temp=%s sessions=%s profiles=%s", td, sd, pd)
|
||||||
|
if err := os.MkdirAll(td, 0755); err != nil {
|
||||||
|
debugLog(" ERROR creating temp dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(sd, 0755); err != nil {
|
||||||
|
debugLog(" ERROR creating sessions dir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(pd, 0755); err != nil {
|
||||||
|
debugLog(" ERROR creating profiles dir: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectEntry represents a saved project on disk
|
// ProjectEntry represents a saved project on disk
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue