gerbers-renderer 0.1.3 โ†’ 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +725 -8
  2. package/dist/core/detect.d.ts +2 -0
  3. package/dist/core/errors.d.ts +5 -0
  4. package/dist/core/list.d.ts +2 -0
  5. package/dist/core/types.d.ts +18 -0
  6. package/dist/gerbers-renderer.es.js +3642 -2240
  7. package/dist/gerbers-renderer.es.js.map +1 -1
  8. package/dist/gerbers-renderer.umd.js +28 -46
  9. package/dist/gerbers-renderer.umd.js.map +1 -1
  10. package/dist/index.d.ts +26 -3
  11. package/dist/io/unpackArchive.d.ts +9 -0
  12. package/dist/libarchive-Bt1VdZR0.js +272 -0
  13. package/dist/libarchive-Bt1VdZR0.js.map +1 -0
  14. package/dist/parse/gerber-parser.d.ts +28 -2
  15. package/dist/render/renderGerbers.d.ts +3 -0
  16. package/dist/render/renderGerbersFiles.d.ts +7 -0
  17. package/dist/render/renderGerbersZip.d.ts +3 -7
  18. package/dist/render-pipeline/core/renderContract.d.ts +98 -0
  19. package/dist/render-pipeline/core/renderScheduler.d.ts +9 -0
  20. package/dist/render-pipeline/core/testSetup.d.ts +0 -0
  21. package/dist/render-pipeline/core/viewportTransform.d.ts +32 -0
  22. package/dist/render-pipeline/core/viewportTransform.example.d.ts +65 -0
  23. package/dist/render-pipeline/core/viewportTransform.test.d.ts +1 -0
  24. package/dist/render-pipeline/eventSystem.test.d.ts +1 -0
  25. package/dist/render-pipeline/events.d.ts +9 -0
  26. package/dist/render-pipeline/exampleOverlays.d.ts +12 -0
  27. package/dist/render-pipeline/integratedViewer.d.ts +23 -0
  28. package/dist/render-pipeline/markerPass.d.ts +6 -0
  29. package/dist/render-pipeline/markerPicker.d.ts +7 -0
  30. package/dist/render-pipeline/markerRenderer.d.ts +11 -0
  31. package/dist/render-pipeline/markerStore.d.ts +17 -0
  32. package/dist/render-pipeline/markerSystem.test.d.ts +1 -0
  33. package/dist/render-pipeline/overlayPass.d.ts +3 -0
  34. package/dist/render-pipeline/overlayRegistry.d.ts +13 -0
  35. package/dist/render-pipeline/overlayRegistry.test.d.ts +1 -0
  36. package/dist/render-pipeline/renderPasses.d.ts +78 -0
  37. package/dist/render-pipeline/uniformGridIndex.d.ts +10 -0
  38. package/dist/render-pipeline/viewer.d.ts +93 -0
  39. package/dist/render-pipeline/viewer.test.d.ts +1 -0
  40. package/dist/render-pipeline/viewerEvents.d.ts +23 -0
  41. package/dist/render-pipeline/visibilityManager.d.ts +20 -0
  42. package/dist/render-pipeline/visibilityPlumbing.test.d.ts +1 -0
  43. package/dist/viewer/types.d.ts +0 -9
  44. package/package.json +6 -3
  45. package/dist/viewer/BoardViewer.d.ts +0 -5
package/README.md CHANGED
@@ -15,11 +15,18 @@ Designed for:
15
15
 
16
16
  - ๐Ÿง  Gerber bundle detection (not just โ€œtry and failโ€)
17
17
  - ๐Ÿ“ฆ Supports `.zip` and `.rar` archives (browser-side)
18
- - ๐ŸŽจ 2D SVG-based board viewer
18
+ - ๐ŸŽจ Canvas-based board viewer with modern render pipeline
19
19
  - ๐Ÿงฉ Drop-in viewer that mounts into any DOM node
20
+ - ๐ŸŽฏ Overlay system for custom visualizations
21
+ - ๐Ÿ“ High-performance marker system with spatial indexing
22
+ - ๐ŸŽฏ Built-in selection and interaction systems
23
+ - โšก Typed event emitter for user interactions
24
+ - ๐Ÿ‘๏ธ Centralized visibility management system
20
25
  - ๐Ÿงช Typed, deterministic render results
21
26
  - ๐Ÿงผ No backend, no workers unless needed
22
27
  - โšก Vite, React, vanilla JS friendly
28
+ - ๐ŸŽฏ Precise viewport transforms with camera controls
29
+ - ๐Ÿ“ Coordinate system: Board (mm) โ†” Screen (px) conversion
23
30
 
24
31
  ## Installation
25
32
 
@@ -27,12 +34,13 @@ Designed for:
27
34
  npm install gerbers-renderer
28
35
  ```
29
36
 
30
- ## Quick start (minimal)
37
+ ## Quick start
31
38
 
39
+ ### Integrated Viewer (Canvas-based)
32
40
  ```typescript
33
- import { renderGerbers, createBoardViewer } from "gerbers-renderer";
41
+ import { createIntegratedViewer } from "gerbers-renderer";
34
42
 
35
- const viewer = createBoardViewer(document.getElementById("pcb")!);
43
+ const viewer = createIntegratedViewer(document.getElementById("pcb")!);
36
44
 
37
45
  const file = input.files[0];
38
46
  const buffer = await file.arrayBuffer();
@@ -46,8 +54,29 @@ viewer.setData({
46
54
  layers: result.layers,
47
55
  });
48
56
  viewer.fit();
57
+
58
+ viewer.setSelection({
59
+ type: "region",
60
+ bounds: { min: { x: 0, y: 0 }, max: { x: 20, y: 10 } }
61
+ });
62
+
63
+ // Add markers for DFM analysis
64
+ viewer.addMarker({
65
+ id: "via-issue",
66
+ x_mm: 12.5,
67
+ y_mm: 8.3,
68
+ severity: "error",
69
+ data: { issue: "Via too close to trace" }
70
+ });
71
+
72
+ viewer.addMarkers([
73
+ { id: "test1", x_mm: 5, y_mm: 5, severity: "info" },
74
+ { id: "test2", x_mm: 15, y_mm: 10, severity: "warning" }
75
+ ]);
49
76
  ```
50
77
 
78
+ **Documentation**: See [MIGRATION.md](./MIGRATION.md) for detailed usage guide.
79
+
51
80
  Always call `result.revoke()` when replacing a render.
52
81
 
53
82
  ## Live demo
@@ -61,6 +90,14 @@ npm run dev
61
90
  Open:
62
91
  ๐Ÿ‘‰ http://localhost:5173/demo/
63
92
 
93
+ **The demo now showcases the new integrated viewer** with:
94
+ - Canvas-based rendering with hardware acceleration
95
+ - Grid overlay with mm/in units
96
+ - Precise viewport transforms and smooth pan/zoom
97
+ - Mouse-centered zoom (zooms where cursor is positioned)
98
+ - Green FR4 board background
99
+ - Clean rendering without placeholder text artifacts
100
+
64
101
  ## Supported input formats
65
102
 
66
103
  | Format | Supported | Notes |
@@ -140,7 +177,7 @@ renderGerbersFiles(
140
177
  Create a drop-in board viewer:
141
178
 
142
179
  ```typescript
143
- const viewer = createBoardViewer(container, {
180
+ const viewer = createIntegratedViewer(container, {
144
181
  onDownload: () => {
145
182
  /* optional */
146
183
  },
@@ -157,10 +194,13 @@ viewer.fit();
157
194
 
158
195
  The viewer supports:
159
196
 
160
- - Pan / zoom
161
- - Layer toggling
162
- - Top / bottom switching
197
+ - Pan / zoom with mouse-centered zoom
198
+ - Layer toggling (top/bottom switching)
199
+ - Grid overlay with mm/in units
200
+ - Custom overlays and markers
201
+ - Selection regions
163
202
  - Download hook (original Gerbers)
203
+ - Hardware-accelerated canvas rendering
164
204
 
165
205
  ## Return value
166
206
 
@@ -238,6 +278,683 @@ This library is intentionally focused on fast, accurate visualization.
238
278
  - WASM-only core split
239
279
  - Headless CI validation mode
240
280
 
281
+ ## Architecture
282
+
283
+ ### Viewport Transform System
284
+
285
+ The renderer uses a precise coordinate transformation system:
286
+
287
+ ```typescript
288
+ import { ViewportTransform, CameraState } from "gerbers-renderer";
289
+
290
+ const transform = new ViewportTransform(
291
+ { center_mm: { x: 50, y: 25 }, zoom: 10 },
292
+ { width_px: 800, height_px: 600 }
293
+ );
294
+
295
+ // Convert between coordinate systems
296
+ const screenPos = transform.boardToScreen({ x: 10, y: 5 }); // mm โ†’ px
297
+ ```
298
+
299
+ This enables:
300
+ - Precise coordinate transformations
301
+ - Smooth pan/zoom operations
302
+ - Mathematical foundation for extensions
303
+
304
+ ## Overlay System
305
+
306
+ The integrated viewer includes a powerful overlay system for custom visualizations:
307
+
308
+ ### Adding Custom Overlays
309
+
310
+ ```typescript
311
+ import { createViolationDotsOverlay, createGridOverlay } from "gerbers-renderer";
312
+
313
+ // Add DFM violation dots (world space)
314
+ viewer.addOverlayLayer({
315
+ id: "dfm:dots",
316
+ zIndex: 50,
317
+ visible: true,
318
+ drawInWorldSpace: true, // Draw in mm coordinates
319
+ draw: (ctx, api) => {
320
+ const violations = [
321
+ { x_mm: 10, y_mm: 12 },
322
+ { x_mm: 40, y_mm: 5 }
323
+ ];
324
+
325
+ ctx.fillStyle = 'red';
326
+ for (const v of violations) {
327
+ ctx.beginPath();
328
+ ctx.arc(v.x_mm, v.y_mm, 0.25, 0, Math.PI * 2); // 0.25mm radius
329
+ ctx.fill();
330
+ }
331
+ }
332
+ });
333
+
334
+ // Add tooltip overlay (screen space)
335
+ viewer.addOverlayLayer({
336
+ id: "ui:tooltip",
337
+ zIndex: 200,
338
+ visible: true,
339
+ drawInWorldSpace: false, // Draw in screen pixels
340
+ draw: (ctx, api) => {
341
+ const hover = getCurrentHover(); // Get hover state
342
+ if (!hover) return;
343
+
344
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
345
+ ctx.fillRect(hover.x_px + 12, hover.y_px - 20, 100, 20);
346
+ ctx.fillStyle = 'white';
347
+ ctx.fillText(hover.text, hover.x_px + 15, hover.y_px - 5);
348
+ }
349
+ });
350
+ ```
351
+
352
+ ### Overlay API
353
+
354
+ Overlays receive a stable API object:
355
+
356
+ ```typescript
357
+ type OverlayApi = {
358
+ // Coordinate conversion
359
+ boardToScreen: (p: { x_mm: number; y_mm: number }) => { x_px: number; y_px: number };
360
+ screenToBoard: (p: { x_px: number; y_px: number }) => { x_mm: number; y_mm: number };
361
+
362
+ // View state
363
+ getViewState: () => { center_mm: { x: number; y: number }; zoom: number; rotation_rad: number };
364
+ getViewport: () => { width_px: number; height_px: number };
365
+ getBoardBounds: () => { minX_mm: number; minY_mm: number; maxX_mm: number; maxY_mm: number };
366
+
367
+ // Render control
368
+ requestRender: (reason: string) => void;
369
+ };
370
+ ```
371
+
372
+ ### Built-in Overlay Examples
373
+
374
+ ```typescript
375
+ // Grid overlay
376
+ viewer.addOverlayLayer(createGridOverlay(1)); // 1mm spacing
377
+
378
+ // Violation dots
379
+ viewer.addOverlayLayer(createViolationDotsOverlay());
380
+
381
+ // Animated marker
382
+ viewer.addOverlayLayer(createPulsingMarkerOverlay({ x_mm: 25, y_mm: 30 }));
383
+
384
+ // Tooltip (provide hover state)
385
+ viewer.addOverlayLayer(createTooltipOverlay(() => getCurrentHover()));
386
+ ```
387
+
388
+ ### Overlay Management
389
+
390
+ ```typescript
391
+ // Control visibility
392
+ viewer.setOverlayVisibility("dfm:dots", false);
393
+
394
+ // Remove overlay
395
+ viewer.removeOverlay("ui:tooltip");
396
+
397
+ // Access registry directly
398
+ const registry = viewer.getOverlayRegistry();
399
+ registry.setZIndex("dfm:dots", 100); // Change render order
400
+ ```
401
+
402
+ **Key Features:**
403
+ - **Stable API**: Same object reference, current state access
404
+ - **Explicit coordinate spaces**: World (mm) vs Screen (px) drawing
405
+ - **Efficient rendering**: Sorted by zIndex, filtered by visibility
406
+ - **Animation support**: Use `api.requestRender()` for smooth animations
407
+ - **Lifecycle hooks**: `onAdd()` and `onRemove()` for setup/cleanup
408
+
409
+ ## Marker System
410
+
411
+ The integrated viewer includes a high-performance marker system for interactive annotations, test points, and DFM indicators:
412
+
413
+ ### Adding Markers
414
+
415
+ ```typescript
416
+ // Add individual markers
417
+ viewer.addMarker({
418
+ id: "test-point-1",
419
+ x_mm: 10.5,
420
+ y_mm: 15.2,
421
+ severity: "error", // "error" | "warning" | "info"
422
+ layer: "top", // "top" | "bottom"
423
+ data: { description: "Via too close to trace" }
424
+ });
425
+
426
+ // Add multiple markers efficiently
427
+ viewer.addMarkers([
428
+ { id: "error1", x_mm: 20, y_mm: 25, severity: "error" },
429
+ { id: "warn1", x_mm: 30, y_mm: 35, severity: "warning" },
430
+ { id: "info1", x_mm: 40, y_mm: 45, severity: "info" }
431
+ ]);
432
+ ```
433
+
434
+ ### Marker Types
435
+
436
+ ```typescript
437
+ type Marker = {
438
+ id: string;
439
+ x_mm: number;
440
+ y_mm: number;
441
+
442
+ // Optional metadata
443
+ layer?: "top" | "bottom";
444
+ severity?: "error" | "warning" | "info";
445
+ radius_mm?: number; // For future world-space rendering
446
+ data?: Record<string, any>; // Custom data
447
+ };
448
+ ```
449
+
450
+ ### Marker Management
451
+
452
+ ```typescript
453
+ // Update marker properties
454
+ viewer.updateMarker("test-point-1", {
455
+ severity: "warning",
456
+ x_mm: 11.0 // Position changes update spatial index automatically
457
+ });
458
+
459
+ // Remove markers
460
+ viewer.removeMarker("test-point-1");
461
+
462
+ // Get marker by ID
463
+ const marker = viewer.getMarker("test-point-1");
464
+
465
+ // List all markers
466
+ const allMarkers = viewer.listMarkers();
467
+
468
+ // Clear all markers
469
+ viewer.clearMarkers();
470
+ ```
471
+
472
+ ### Marker Picking and Selection
473
+
474
+ ```typescript
475
+ // Pick marker at screen coordinates
476
+ const hit = viewer.pickMarker(mouseX, mouseY, 10); // 10px pick radius
477
+
478
+ if (hit) {
479
+ console.log(`Hit marker: ${hit.id} at ${hit.distance_px.toFixed(1)}px`);
480
+ viewer.selectMarker(hit.id);
481
+
482
+ // Access selected marker
483
+ const selected = viewer.getSelectedMarker();
484
+ console.log("Selected:", selected?.data);
485
+ }
486
+
487
+ // Clear selection
488
+ viewer.selectMarker(null);
489
+ ```
490
+
491
+ ### Marker Styling
492
+
493
+ Markers are automatically styled by severity:
494
+ - **Error**: Red circles
495
+ - **Warning**: Orange circles
496
+ - **Info**: Blue circles
497
+ - **Default**: Gray circles
498
+
499
+ Selected markers have a blue outline, hovered markers have orange highlighting.
500
+
501
+ ### Performance Features
502
+
503
+ ```typescript
504
+ // The marker system uses:
505
+ // - O(1) lookup via Map storage
506
+ // - Uniform grid spatial index for fast queries
507
+ // - Viewport culling to skip off-screen markers
508
+ // - Constant pixel radius (4px) for visibility at all zooms
509
+ // - Cached list iteration with dirty flag optimization
510
+ ```
511
+
512
+ ### Advanced Usage
513
+
514
+ ```typescript
515
+ // Batch updates for performance
516
+ viewer.updateMarkers([
517
+ { id: "marker1", severity: "warning" },
518
+ { id: "marker2", x_mm: 25.0, y_mm: 30.0 }
519
+ ]);
520
+
521
+ // Query markers near a point (for custom tools)
522
+ const nearby = viewer.markers.queryNear(20, 20, 5); // 5mm radius
523
+
524
+ // Access underlying systems for advanced integration
525
+ const store = viewer.getMarkerStore();
526
+ const picker = viewer.getMarkerPicker();
527
+ ```
528
+
529
+ **Key Features:**
530
+ - **High Performance**: Spatial index with O(1) lookup
531
+ - **Zoom-Aware Picking**: Accurate hit detection at any zoom level
532
+ - **Automatic Styling**: Severity-based color coding
533
+ - **Interactive Selection**: Click to select, hover for feedback
534
+ - **Batch Operations**: Efficient bulk updates
535
+ - **Custom Data**: Attach any metadata via `data` property
536
+
537
+ ## Event System
538
+
539
+ The integrated viewer includes a typed event emitter for responding to user interactions and state changes:
540
+
541
+ ### Event Types
542
+
543
+ ```typescript
544
+ type ViewerEvents = {
545
+ "hover:marker": { markerId: string | null; marker?: Marker };
546
+ "select:marker": { markerId: string | null; marker?: Marker };
547
+ "click:board": { x_mm: number; y_mm: number };
548
+ "view:change": { center_mm: { x: number; y: number }; zoom: number; rotation_rad: number };
549
+ };
550
+ ```
551
+
552
+ ### Setting Up Event Listeners
553
+
554
+ ```typescript
555
+ // Set up mouse event handling (call once after viewer creation)
556
+ viewer.setupEventListeners();
557
+
558
+ // Listen to marker hover events
559
+ viewer.on('hover:marker', ({ markerId, marker }) => {
560
+ if (marker) {
561
+ console.log(`Hovering: ${marker.severity} at ${marker.x_mm}, ${marker.y_mm}`);
562
+ // Update tooltip or UI
563
+ } else {
564
+ // Hide tooltip
565
+ }
566
+ });
567
+
568
+ // Listen to marker selection events
569
+ viewer.on('select:marker', ({ markerId, marker }) => {
570
+ if (marker) {
571
+ console.log(`Selected: ${marker.data?.description}`);
572
+ // Show details panel or highlight related elements
573
+ }
574
+ });
575
+
576
+ // Listen to board clicks (clicking empty space)
577
+ viewer.on('click:board', ({ x_mm, y_mm }) => {
578
+ console.log(`Clicked board at ${x_mm.toFixed(2)}, ${y_mm.toFixed(2)}mm`);
579
+ // Could add a marker at this position or show context menu
580
+ });
581
+
582
+ // Listen to view changes (pan/zoom)
583
+ viewer.on('view:change', ({ center_mm, zoom, rotation_rad }) => {
584
+ console.log(`View changed: center=${center_mm.x},${center_mm.y} zoom=${zoom}`);
585
+ // Update coordinates display or save view state
586
+ });
587
+ ```
588
+
589
+ ### Event Listener Options
590
+
591
+ ```typescript
592
+ // One-time listener (auto-unsubscribes after first event)
593
+ viewer.once('select:marker', ({ marker }) => {
594
+ console.log('First selection:', marker?.id);
595
+ });
596
+
597
+ // Manual unsubscribe
598
+ const unsub = viewer.on('hover:marker', onHover);
599
+ // Later...
600
+ unsub(); // Remove listener
601
+ ```
602
+
603
+ ### Event Characteristics
604
+
605
+ - **No Spam**: Events only emit when state actually changes
606
+ - **Type Safe**: Full TypeScript typing for all events
607
+ - **Performance**: Efficient Set-based handler storage
608
+ - **Robust**: Safe unsubscribe even during event emission
609
+
610
+ ### Integration with Markers
611
+
612
+ The event system integrates seamlessly with the marker system:
613
+
614
+ ```typescript
615
+ // Add markers
616
+ viewer.addMarkers([
617
+ { id: 'error1', x_mm: 10, y_mm: 15, severity: 'error', data: { issue: 'Via too close' } },
618
+ { id: 'warn1', x_mm: 20, y_mm: 25, severity: 'warning', data: { issue: 'Trace width' } }
619
+ ]);
620
+
621
+ // Events will automatically fire for hover/selection
622
+ viewer.setupEventListeners();
623
+
624
+ // Build interactive UI
625
+ viewer.on('hover:marker', ({ marker }) => {
626
+ if (marker?.severity === 'error') {
627
+ showTooltip(marker.data?.issue);
628
+ }
629
+ });
630
+
631
+ viewer.on('select:marker', ({ marker }) => {
632
+ if (marker) {
633
+ showDetailsPanel(marker);
634
+ // Optional: center view on selection
635
+ viewer.selectMarker(marker.id, { center: true, animate: true });
636
+ }
637
+ });
638
+ ```
639
+
640
+ ### Advanced Usage
641
+
642
+ ```typescript
643
+ // Custom event handling for DFM tools
644
+ class DFMTool {
645
+ constructor(viewer) {
646
+ this.viewer = viewer;
647
+ this.setupEvents();
648
+ }
649
+
650
+ setupEvents() {
651
+ this.viewer.on('hover:marker', this.onHover.bind(this));
652
+ this.viewer.on('select:marker', this.onSelect.bind(this));
653
+ this.viewer.on('click:board', this.onBoardClick.bind(this));
654
+ }
655
+
656
+ onHover({ marker }) {
657
+ if (marker?.severity === 'error') {
658
+ this.showCriticalError(marker);
659
+ }
660
+ }
661
+
662
+ onSelect({ marker }) {
663
+ if (marker) {
664
+ this.zoomToIssue(marker);
665
+ this.showRelatedElements(marker);
666
+ }
667
+ }
668
+
669
+ onBoardClick({ x_mm, y_mm }) {
670
+ // Add new marker at click position
671
+ this.viewer.addMarker({
672
+ id: `custom-${Date.now()}`,
673
+ x_mm,
674
+ y_mm,
675
+ severity: 'info',
676
+ data: { source: 'user-click' }
677
+ });
678
+ }
679
+ }
680
+
681
+ // Usage
682
+ const dfmTool = new DFMTool(viewer);
683
+ ```
684
+
685
+ **Key Features:**
686
+ - **Typed Events**: Full TypeScript safety with event payloads
687
+ - **State Change Detection**: No duplicate events spam
688
+ - **Memory Safe**: Automatic cleanup and unsubscribe support
689
+ - **Interactive**: Built-in hover and selection handling
690
+ - **Extensible**: Easy to add custom event handling logic
691
+
692
+ ## Visibility System
693
+
694
+ The integrated viewer includes a centralized visibility management system that controls which layers and features are rendered:
695
+
696
+ ### Visibility State Structure
697
+
698
+ ```typescript
699
+ type VisibilityState = {
700
+ gerber: {
701
+ copper: boolean; // Copper traces
702
+ solderMask: boolean; // Solder mask layers
703
+ silk: boolean; // Silk screen layers
704
+ outline: boolean; // Board outline
705
+ };
706
+ overlays: Record<string, boolean>; // Custom overlay visibility
707
+ markers: boolean; // Marker system visibility
708
+ };
709
+ ```
710
+
711
+ ### Basic Visibility Control
712
+
713
+ ```typescript
714
+ // Get current visibility state
715
+ const state = viewer.getVisibility();
716
+ console.log(state.gerber.copper); // true/false
717
+ console.log(state.markers); // true/false
718
+
719
+ // Set individual layer visibility
720
+ viewer.setGerberVisibility('copper', false);
721
+ viewer.setOverlayVisibility('grid', true);
722
+ viewer.setMarkersVisibility(true);
723
+
724
+ // Toggle layers
725
+ viewer.toggleGerberLayer('silk');
726
+ viewer.toggleOverlay('grid');
727
+ viewer.toggleMarkers();
728
+ ```
729
+
730
+ ### Visibility Presets
731
+
732
+ ```typescript
733
+ // Quick visibility presets for common use cases
734
+ viewer.applyVisibilityPreset('all'); // Show everything
735
+ viewer.applyVisibilityPreset('none'); // Hide everything
736
+ viewer.applyVisibilityPreset('copper-only'); // Only copper + outline
737
+ viewer.applyVisibilityPreset('minimal'); // Copper + outline + markers
738
+ ```
739
+
740
+ ### Reactive Updates
741
+
742
+ The visibility system supports reactive updates through subscriptions:
743
+
744
+ ```typescript
745
+ // Subscribe to visibility changes
746
+ const unsubscribe = viewer.onVisibilityChange((state) => {
747
+ console.log('Visibility changed:', state);
748
+ // Update UI controls, save state, etc.
749
+ });
750
+
751
+ // Later, when done
752
+ unsubscribe();
753
+ ```
754
+
755
+ ### Integration with Render Pipeline
756
+
757
+ All render passes receive the current visibility state and can make rendering decisions based on it:
758
+
759
+ ```typescript
760
+ // Gerber layers check their specific visibility
761
+ enabled: (rc) => rc.visibility.gerber.copper,
762
+
763
+ // Markers check global marker visibility
764
+ enabled: (rc) => rc.visibility.markers,
765
+
766
+ // Overlays filter by their individual visibility
767
+ const visibleOverlays = overlays.filter(overlay =>
768
+ rc.visibility.overlays[overlay.id] ?? overlay.visible
769
+ );
770
+ ```
771
+
772
+ ### Advanced Usage
773
+
774
+ ```typescript
775
+ // Custom visibility management
776
+ class CustomUI {
777
+ constructor(viewer) {
778
+ this.viewer = viewer;
779
+ this.setupControls();
780
+ }
781
+
782
+ setupControls() {
783
+ // Create custom visibility controls
784
+ this.createLayerToggles();
785
+ this.setupPresets();
786
+ this.setupReactiveUI();
787
+ }
788
+
789
+ createLayerToggles() {
790
+ const layers = ['copper', 'solderMask', 'silk', 'outline'];
791
+
792
+ layers.forEach(layer => {
793
+ const toggle = document.createElement('input');
794
+ toggle.type = 'checkbox';
795
+ toggle.checked = this.viewer.getVisibility().gerber[layer];
796
+
797
+ toggle.addEventListener('change', () => {
798
+ this.viewer.setGerberVisibility(layer, toggle.checked);
799
+ });
800
+
801
+ document.body.appendChild(toggle);
802
+ });
803
+ }
804
+
805
+ setupPresets() {
806
+ const presetSelect = document.createElement('select');
807
+
808
+ const presets = [
809
+ { value: 'all', label: 'All Layers' },
810
+ { value: 'none', label: 'None' },
811
+ { value: 'copper-only', label: 'Copper Only' },
812
+ { value: 'minimal', label: 'Minimal' }
813
+ ];
814
+
815
+ presets.forEach(preset => {
816
+ const option = document.createElement('option');
817
+ option.value = preset.value;
818
+ option.textContent = preset.label;
819
+ presetSelect.appendChild(option);
820
+ });
821
+
822
+ presetSelect.addEventListener('change', () => {
823
+ this.viewer.applyVisibilityPreset(presetSelect.value);
824
+ });
825
+
826
+ document.body.appendChild(presetSelect);
827
+ }
828
+
829
+ setupReactiveUI() {
830
+ // Update UI when visibility changes
831
+ this.viewer.onVisibilityChange((state) => {
832
+ // Update checkbox states
833
+ document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
834
+ const layer = checkbox.dataset.layer;
835
+ if (layer && state.gerber[layer]) {
836
+ checkbox.checked = true;
837
+ }
838
+ });
839
+
840
+ // Update status display
841
+ const visibleCount = Object.values(state.gerber).filter(Boolean).length;
842
+ console.log(`Visible layers: ${visibleCount}/4`);
843
+ });
844
+ }
845
+ }
846
+
847
+ // Usage
848
+ const customUI = new CustomUI(viewer);
849
+ ```
850
+
851
+ ### Performance Features
852
+
853
+ - **Centralized State**: Single source of truth prevents inconsistencies
854
+ - **Efficient Updates**: Only re-renders when visibility actually changes
855
+ - **Subscription System**: Reactive updates without polling
856
+ - **Type Safety**: Full TypeScript support prevents runtime errors
857
+ - **Batch Operations**: Presets and bulk updates minimize renders
858
+
859
+ ### Integration Examples
860
+
861
+ ```typescript
862
+ // DFM tool with custom visibility controls
863
+ class DFMTool {
864
+ constructor(viewer) {
865
+ this.viewer = viewer;
866
+ this.setupDFMPresets();
867
+ }
868
+
869
+ setupDFMPresets() {
870
+ // Custom DFM-specific presets
871
+ this.addPreset('dfm-errors', {
872
+ gerber: { copper: true, solderMask: false, silk: false, outline: true },
873
+ markers: true,
874
+ overlays: { 'dfm-highlights': true }
875
+ });
876
+
877
+ this.addPreset('manufacturing', {
878
+ gerber: { copper: true, solderMask: true, silk: true, outline: true },
879
+ markers: false,
880
+ overlays: { 'dimensions': true, 'tolerances': true }
881
+ });
882
+ }
883
+
884
+ addPreset(name, config) {
885
+ // Add custom preset button
886
+ const button = document.createElement('button');
887
+ button.textContent = name;
888
+ button.addEventListener('click', () => {
889
+ this.viewer.setVisibility(config);
890
+ });
891
+ document.body.appendChild(button);
892
+ }
893
+ }
894
+ ```
895
+
896
+ **Key Features:**
897
+ - **Centralized Management**: Single visibility state prevents inconsistencies
898
+ - **Reactive Updates**: Automatic re-renders on state changes
899
+ - **Type Safe**: Full TypeScript support throughout
900
+ - **Performance Optimized**: Efficient subscription-based updates
901
+ - **Extensible**: Easy to add custom controls and presets
902
+
903
+ ### Render Pipeline
904
+
905
+ ```typescript
906
+ import { createIntegratedViewer } from "gerbers-renderer";
907
+
908
+ const viewer = createIntegratedViewer(container);
909
+ viewer.setData({ boardGeom, layers });
910
+
911
+ // Add custom render passes
912
+ viewer.viewer.addPass({
913
+ id: "custom-overlay",
914
+ order: 150,
915
+ enabled: () => true,
916
+ draw: (rc) => {
917
+ // Draw in board coordinates
918
+ const m = rc.xform.getWorldToScreenMatrix();
919
+ rc.ctx.setTransform(m[0], m[3], m[1], m[4], m[2], m[5]);
920
+ // ... your drawing code
921
+ }
922
+ });
923
+ ```
924
+
925
+ **Render Stages:**
926
+ - **Base Gerber** (0-99): Copper traces, masks, silk screen
927
+ - **Overlays** (100-199): Grid, rulers, custom drawings
928
+ - **Markers** (200-299): Test points, components, annotations
929
+ - **Selection** (300-399): Highlighted regions and elements
930
+
931
+ **Key Features:**
932
+ - Deterministic rendering order
933
+ - Efficient render scheduling with requestAnimationFrame
934
+ - Centralized visibility management
935
+ - Extensible render pass system
936
+
937
+ **Visibility Control:**
938
+ ```typescript
939
+ // Use presets
940
+ viewer.visibility.applyPreset('copper-only');
941
+
942
+ // Individual control
943
+ viewer.visibility.setGerberVisibility('copper', false);
944
+ viewer.visibility.setOverlayVisibility('grid', true);
945
+ viewer.visibility.setMarkersVisibility(true);
946
+ ```
947
+
948
+ **File Organization:**
949
+ - `src/render-pipeline/core/`: Core components (transforms, contracts, scheduling)
950
+ - `src/render-pipeline/`: Complete render pipeline implementation
951
+ - `src/render-pipeline/overlayRegistry.ts`: Overlay management system
952
+ - `src/render-pipeline/exampleOverlays.ts`: Built-in overlay examples
953
+ - `src/viewer/`: Shared types and styles
954
+ - `src/index.ts`: Unified exports
955
+
956
+ See [FILE_STRUCTURE.md](./FILE_STRUCTURE.md) for detailed organization.
957
+
241
958
  ## License
242
959
 
243
960
  MIT