gerbers-renderer 0.1.4 โ†’ 1.1.5

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 (34) hide show
  1. package/README.md +787 -8
  2. package/dist/gerbers-renderer.es.js +3428 -2403
  3. package/dist/gerbers-renderer.es.js.map +1 -1
  4. package/dist/gerbers-renderer.umd.js +22 -43
  5. package/dist/gerbers-renderer.umd.js.map +1 -1
  6. package/dist/index.d.ts +21 -2
  7. package/dist/render-pipeline/core/renderContract.d.ts +98 -0
  8. package/dist/render-pipeline/core/renderScheduler.d.ts +9 -0
  9. package/dist/render-pipeline/core/testSetup.d.ts +0 -0
  10. package/dist/render-pipeline/core/viewportTransform.d.ts +38 -0
  11. package/dist/render-pipeline/core/viewportTransform.example.d.ts +65 -0
  12. package/dist/render-pipeline/core/viewportTransform.test.d.ts +1 -0
  13. package/dist/render-pipeline/eventSystem.test.d.ts +1 -0
  14. package/dist/render-pipeline/events.d.ts +9 -0
  15. package/dist/render-pipeline/exampleOverlays.d.ts +12 -0
  16. package/dist/render-pipeline/integratedViewer.d.ts +23 -0
  17. package/dist/render-pipeline/markerPass.d.ts +6 -0
  18. package/dist/render-pipeline/markerPicker.d.ts +7 -0
  19. package/dist/render-pipeline/markerRenderer.d.ts +11 -0
  20. package/dist/render-pipeline/markerStore.d.ts +17 -0
  21. package/dist/render-pipeline/markerSystem.test.d.ts +1 -0
  22. package/dist/render-pipeline/overlayPass.d.ts +3 -0
  23. package/dist/render-pipeline/overlayRegistry.d.ts +13 -0
  24. package/dist/render-pipeline/overlayRegistry.test.d.ts +1 -0
  25. package/dist/render-pipeline/renderPasses.d.ts +78 -0
  26. package/dist/render-pipeline/uniformGridIndex.d.ts +10 -0
  27. package/dist/render-pipeline/viewer.d.ts +93 -0
  28. package/dist/render-pipeline/viewer.test.d.ts +1 -0
  29. package/dist/render-pipeline/viewerEvents.d.ts +23 -0
  30. package/dist/render-pipeline/visibilityManager.d.ts +20 -0
  31. package/dist/render-pipeline/visibilityPlumbing.test.d.ts +1 -0
  32. package/dist/viewer/types.d.ts +0 -9
  33. package/package.json +6 -3
  34. 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,745 @@ 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
+ ### Viewer Navigation
492
+
493
+ The viewer provides several methods to navigate to specific locations on the board:
494
+
495
+ **For direct Viewer usage:**
496
+ ```typescript
497
+ // Direct camera control - go to specific board coordinates (in mm)
498
+ viewer.setCamera({
499
+ center_mm: { x: 50, y: 75 }, // Board coordinates in millimeters
500
+ zoom: 10 // Pixels per mm (zoom level)
501
+ });
502
+
503
+ // Get current camera state
504
+ const currentCamera = viewer.getCamera();
505
+ console.log('Current center:', currentCamera.center_mm);
506
+ console.log('Current zoom:', currentCamera.zoom);
507
+
508
+ // Coordinate conversion utilities
509
+ const screenPos = viewer.boardToScreen(50, 75); // mm โ†’ px
510
+ const boardPos = viewer.screenToBoard(250, 300); // px โ†’ mm
511
+ ```
512
+
513
+ **For Integrated Viewer usage:**
514
+ ```typescript
515
+ // When using createIntegratedViewer(), access the underlying viewer
516
+ const integratedViewer = createIntegratedViewer(hostElement);
517
+
518
+ // Navigate using the underlying viewer property
519
+ integratedViewer.viewer.setCamera({
520
+ center_mm: { x: 50, y: 75 },
521
+ zoom: 10
522
+ });
523
+
524
+ // Get current camera state
525
+ const currentCamera = integratedViewer.viewer.getCamera();
526
+
527
+ // Coordinate conversion utilities
528
+ const screenPos = integratedViewer.viewer.boardToScreen(50, 75);
529
+ const boardPos = integratedViewer.viewer.screenToBoard(250, 300);
530
+
531
+ // Navigate to a marker location
532
+ integratedViewer.addMarker({
533
+ id: 'target-location',
534
+ x_mm: 50,
535
+ y_mm: 75,
536
+ radius_mm: 2,
537
+ color: '#ff0000'
538
+ });
539
+
540
+ // Note: selectMarker is on the integrated viewer, not the base viewer
541
+ integratedViewer.viewer.selectMarker('target-location', {
542
+ center: true,
543
+ animate: true
544
+ });
545
+ ```
546
+
547
+ **Camera State Properties:**
548
+ - `center_mm`: Board center point in millimeters
549
+ - `zoom`: Pixels per millimeter (zoom level)
550
+ - `rotation_rad`: Rotation in radians (optional)
551
+ - `mirrorX/mirrorY`: Flip axes (optional)
552
+
553
+ ### Marker Styling
554
+
555
+ Markers are automatically styled by severity:
556
+ - **Error**: Red circles
557
+ - **Warning**: Orange circles
558
+ - **Info**: Blue circles
559
+ - **Default**: Gray circles
560
+
561
+ Selected markers have a blue outline, hovered markers have orange highlighting.
562
+
563
+ ### Performance Features
564
+
565
+ ```typescript
566
+ // The marker system uses:
567
+ // - O(1) lookup via Map storage
568
+ // - Uniform grid spatial index for fast queries
569
+ // - Viewport culling to skip off-screen markers
570
+ // - Constant pixel radius (4px) for visibility at all zooms
571
+ // - Cached list iteration with dirty flag optimization
572
+ ```
573
+
574
+ ### Advanced Usage
575
+
576
+ ```typescript
577
+ // Batch updates for performance
578
+ viewer.updateMarkers([
579
+ { id: "marker1", severity: "warning" },
580
+ { id: "marker2", x_mm: 25.0, y_mm: 30.0 }
581
+ ]);
582
+
583
+ // Query markers near a point (for custom tools)
584
+ const nearby = viewer.markers.queryNear(20, 20, 5); // 5mm radius
585
+
586
+ // Access underlying systems for advanced integration
587
+ const store = viewer.getMarkerStore();
588
+ const picker = viewer.getMarkerPicker();
589
+ ```
590
+
591
+ **Key Features:**
592
+ - **High Performance**: Spatial index with O(1) lookup
593
+ - **Zoom-Aware Picking**: Accurate hit detection at any zoom level
594
+ - **Automatic Styling**: Severity-based color coding
595
+ - **Interactive Selection**: Click to select, hover for feedback
596
+ - **Batch Operations**: Efficient bulk updates
597
+ - **Custom Data**: Attach any metadata via `data` property
598
+
599
+ ## Event System
600
+
601
+ The integrated viewer includes a typed event emitter for responding to user interactions and state changes:
602
+
603
+ ### Event Types
604
+
605
+ ```typescript
606
+ type ViewerEvents = {
607
+ "hover:marker": { markerId: string | null; marker?: Marker };
608
+ "select:marker": { markerId: string | null; marker?: Marker };
609
+ "click:board": { x_mm: number; y_mm: number };
610
+ "view:change": { center_mm: { x: number; y: number }; zoom: number; rotation_rad: number };
611
+ };
612
+ ```
613
+
614
+ ### Setting Up Event Listeners
615
+
616
+ ```typescript
617
+ // Set up mouse event handling (call once after viewer creation)
618
+ viewer.setupEventListeners();
619
+
620
+ // Listen to marker hover events
621
+ viewer.on('hover:marker', ({ markerId, marker }) => {
622
+ if (marker) {
623
+ console.log(`Hovering: ${marker.severity} at ${marker.x_mm}, ${marker.y_mm}`);
624
+ // Update tooltip or UI
625
+ } else {
626
+ // Hide tooltip
627
+ }
628
+ });
629
+
630
+ // Listen to marker selection events
631
+ viewer.on('select:marker', ({ markerId, marker }) => {
632
+ if (marker) {
633
+ console.log(`Selected: ${marker.data?.description}`);
634
+ // Show details panel or highlight related elements
635
+ }
636
+ });
637
+
638
+ // Listen to board clicks (clicking empty space)
639
+ viewer.on('click:board', ({ x_mm, y_mm }) => {
640
+ console.log(`Clicked board at ${x_mm.toFixed(2)}, ${y_mm.toFixed(2)}mm`);
641
+ // Could add a marker at this position or show context menu
642
+ });
643
+
644
+ // Listen to view changes (pan/zoom)
645
+ viewer.on('view:change', ({ center_mm, zoom, rotation_rad }) => {
646
+ console.log(`View changed: center=${center_mm.x},${center_mm.y} zoom=${zoom}`);
647
+ // Update coordinates display or save view state
648
+ });
649
+ ```
650
+
651
+ ### Event Listener Options
652
+
653
+ ```typescript
654
+ // One-time listener (auto-unsubscribes after first event)
655
+ viewer.once('select:marker', ({ marker }) => {
656
+ console.log('First selection:', marker?.id);
657
+ });
658
+
659
+ // Manual unsubscribe
660
+ const unsub = viewer.on('hover:marker', onHover);
661
+ // Later...
662
+ unsub(); // Remove listener
663
+ ```
664
+
665
+ ### Event Characteristics
666
+
667
+ - **No Spam**: Events only emit when state actually changes
668
+ - **Type Safe**: Full TypeScript typing for all events
669
+ - **Performance**: Efficient Set-based handler storage
670
+ - **Robust**: Safe unsubscribe even during event emission
671
+
672
+ ### Integration with Markers
673
+
674
+ The event system integrates seamlessly with the marker system:
675
+
676
+ ```typescript
677
+ // Add markers
678
+ viewer.addMarkers([
679
+ { id: 'error1', x_mm: 10, y_mm: 15, severity: 'error', data: { issue: 'Via too close' } },
680
+ { id: 'warn1', x_mm: 20, y_mm: 25, severity: 'warning', data: { issue: 'Trace width' } }
681
+ ]);
682
+
683
+ // Events will automatically fire for hover/selection
684
+ viewer.setupEventListeners();
685
+
686
+ // Build interactive UI
687
+ viewer.on('hover:marker', ({ marker }) => {
688
+ if (marker?.severity === 'error') {
689
+ showTooltip(marker.data?.issue);
690
+ }
691
+ });
692
+
693
+ viewer.on('select:marker', ({ marker }) => {
694
+ if (marker) {
695
+ showDetailsPanel(marker);
696
+ // Optional: center view on selection
697
+ viewer.selectMarker(marker.id, { center: true, animate: true });
698
+ }
699
+ });
700
+ ```
701
+
702
+ ### Advanced Usage
703
+
704
+ ```typescript
705
+ // Custom event handling for DFM tools
706
+ class DFMTool {
707
+ constructor(viewer) {
708
+ this.viewer = viewer;
709
+ this.setupEvents();
710
+ }
711
+
712
+ setupEvents() {
713
+ this.viewer.on('hover:marker', this.onHover.bind(this));
714
+ this.viewer.on('select:marker', this.onSelect.bind(this));
715
+ this.viewer.on('click:board', this.onBoardClick.bind(this));
716
+ }
717
+
718
+ onHover({ marker }) {
719
+ if (marker?.severity === 'error') {
720
+ this.showCriticalError(marker);
721
+ }
722
+ }
723
+
724
+ onSelect({ marker }) {
725
+ if (marker) {
726
+ this.zoomToIssue(marker);
727
+ this.showRelatedElements(marker);
728
+ }
729
+ }
730
+
731
+ onBoardClick({ x_mm, y_mm }) {
732
+ // Add new marker at click position
733
+ this.viewer.addMarker({
734
+ id: `custom-${Date.now()}`,
735
+ x_mm,
736
+ y_mm,
737
+ severity: 'info',
738
+ data: { source: 'user-click' }
739
+ });
740
+ }
741
+ }
742
+
743
+ // Usage
744
+ const dfmTool = new DFMTool(viewer);
745
+ ```
746
+
747
+ **Key Features:**
748
+ - **Typed Events**: Full TypeScript safety with event payloads
749
+ - **State Change Detection**: No duplicate events spam
750
+ - **Memory Safe**: Automatic cleanup and unsubscribe support
751
+ - **Interactive**: Built-in hover and selection handling
752
+ - **Extensible**: Easy to add custom event handling logic
753
+
754
+ ## Visibility System
755
+
756
+ The integrated viewer includes a centralized visibility management system that controls which layers and features are rendered:
757
+
758
+ ### Visibility State Structure
759
+
760
+ ```typescript
761
+ type VisibilityState = {
762
+ gerber: {
763
+ copper: boolean; // Copper traces
764
+ solderMask: boolean; // Solder mask layers
765
+ silk: boolean; // Silk screen layers
766
+ outline: boolean; // Board outline
767
+ };
768
+ overlays: Record<string, boolean>; // Custom overlay visibility
769
+ markers: boolean; // Marker system visibility
770
+ };
771
+ ```
772
+
773
+ ### Basic Visibility Control
774
+
775
+ ```typescript
776
+ // Get current visibility state
777
+ const state = viewer.getVisibility();
778
+ console.log(state.gerber.copper); // true/false
779
+ console.log(state.markers); // true/false
780
+
781
+ // Set individual layer visibility
782
+ viewer.setGerberVisibility('copper', false);
783
+ viewer.setOverlayVisibility('grid', true);
784
+ viewer.setMarkersVisibility(true);
785
+
786
+ // Toggle layers
787
+ viewer.toggleGerberLayer('silk');
788
+ viewer.toggleOverlay('grid');
789
+ viewer.toggleMarkers();
790
+ ```
791
+
792
+ ### Visibility Presets
793
+
794
+ ```typescript
795
+ // Quick visibility presets for common use cases
796
+ viewer.applyVisibilityPreset('all'); // Show everything
797
+ viewer.applyVisibilityPreset('none'); // Hide everything
798
+ viewer.applyVisibilityPreset('copper-only'); // Only copper + outline
799
+ viewer.applyVisibilityPreset('minimal'); // Copper + outline + markers
800
+ ```
801
+
802
+ ### Reactive Updates
803
+
804
+ The visibility system supports reactive updates through subscriptions:
805
+
806
+ ```typescript
807
+ // Subscribe to visibility changes
808
+ const unsubscribe = viewer.onVisibilityChange((state) => {
809
+ console.log('Visibility changed:', state);
810
+ // Update UI controls, save state, etc.
811
+ });
812
+
813
+ // Later, when done
814
+ unsubscribe();
815
+ ```
816
+
817
+ ### Integration with Render Pipeline
818
+
819
+ All render passes receive the current visibility state and can make rendering decisions based on it:
820
+
821
+ ```typescript
822
+ // Gerber layers check their specific visibility
823
+ enabled: (rc) => rc.visibility.gerber.copper,
824
+
825
+ // Markers check global marker visibility
826
+ enabled: (rc) => rc.visibility.markers,
827
+
828
+ // Overlays filter by their individual visibility
829
+ const visibleOverlays = overlays.filter(overlay =>
830
+ rc.visibility.overlays[overlay.id] ?? overlay.visible
831
+ );
832
+ ```
833
+
834
+ ### Advanced Usage
835
+
836
+ ```typescript
837
+ // Custom visibility management
838
+ class CustomUI {
839
+ constructor(viewer) {
840
+ this.viewer = viewer;
841
+ this.setupControls();
842
+ }
843
+
844
+ setupControls() {
845
+ // Create custom visibility controls
846
+ this.createLayerToggles();
847
+ this.setupPresets();
848
+ this.setupReactiveUI();
849
+ }
850
+
851
+ createLayerToggles() {
852
+ const layers = ['copper', 'solderMask', 'silk', 'outline'];
853
+
854
+ layers.forEach(layer => {
855
+ const toggle = document.createElement('input');
856
+ toggle.type = 'checkbox';
857
+ toggle.checked = this.viewer.getVisibility().gerber[layer];
858
+
859
+ toggle.addEventListener('change', () => {
860
+ this.viewer.setGerberVisibility(layer, toggle.checked);
861
+ });
862
+
863
+ document.body.appendChild(toggle);
864
+ });
865
+ }
866
+
867
+ setupPresets() {
868
+ const presetSelect = document.createElement('select');
869
+
870
+ const presets = [
871
+ { value: 'all', label: 'All Layers' },
872
+ { value: 'none', label: 'None' },
873
+ { value: 'copper-only', label: 'Copper Only' },
874
+ { value: 'minimal', label: 'Minimal' }
875
+ ];
876
+
877
+ presets.forEach(preset => {
878
+ const option = document.createElement('option');
879
+ option.value = preset.value;
880
+ option.textContent = preset.label;
881
+ presetSelect.appendChild(option);
882
+ });
883
+
884
+ presetSelect.addEventListener('change', () => {
885
+ this.viewer.applyVisibilityPreset(presetSelect.value);
886
+ });
887
+
888
+ document.body.appendChild(presetSelect);
889
+ }
890
+
891
+ setupReactiveUI() {
892
+ // Update UI when visibility changes
893
+ this.viewer.onVisibilityChange((state) => {
894
+ // Update checkbox states
895
+ document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
896
+ const layer = checkbox.dataset.layer;
897
+ if (layer && state.gerber[layer]) {
898
+ checkbox.checked = true;
899
+ }
900
+ });
901
+
902
+ // Update status display
903
+ const visibleCount = Object.values(state.gerber).filter(Boolean).length;
904
+ console.log(`Visible layers: ${visibleCount}/4`);
905
+ });
906
+ }
907
+ }
908
+
909
+ // Usage
910
+ const customUI = new CustomUI(viewer);
911
+ ```
912
+
913
+ ### Performance Features
914
+
915
+ - **Centralized State**: Single source of truth prevents inconsistencies
916
+ - **Efficient Updates**: Only re-renders when visibility actually changes
917
+ - **Subscription System**: Reactive updates without polling
918
+ - **Type Safety**: Full TypeScript support prevents runtime errors
919
+ - **Batch Operations**: Presets and bulk updates minimize renders
920
+
921
+ ### Integration Examples
922
+
923
+ ```typescript
924
+ // DFM tool with custom visibility controls
925
+ class DFMTool {
926
+ constructor(viewer) {
927
+ this.viewer = viewer;
928
+ this.setupDFMPresets();
929
+ }
930
+
931
+ setupDFMPresets() {
932
+ // Custom DFM-specific presets
933
+ this.addPreset('dfm-errors', {
934
+ gerber: { copper: true, solderMask: false, silk: false, outline: true },
935
+ markers: true,
936
+ overlays: { 'dfm-highlights': true }
937
+ });
938
+
939
+ this.addPreset('manufacturing', {
940
+ gerber: { copper: true, solderMask: true, silk: true, outline: true },
941
+ markers: false,
942
+ overlays: { 'dimensions': true, 'tolerances': true }
943
+ });
944
+ }
945
+
946
+ addPreset(name, config) {
947
+ // Add custom preset button
948
+ const button = document.createElement('button');
949
+ button.textContent = name;
950
+ button.addEventListener('click', () => {
951
+ this.viewer.setVisibility(config);
952
+ });
953
+ document.body.appendChild(button);
954
+ }
955
+ }
956
+ ```
957
+
958
+ **Key Features:**
959
+ - **Centralized Management**: Single visibility state prevents inconsistencies
960
+ - **Reactive Updates**: Automatic re-renders on state changes
961
+ - **Type Safe**: Full TypeScript support throughout
962
+ - **Performance Optimized**: Efficient subscription-based updates
963
+ - **Extensible**: Easy to add custom controls and presets
964
+
965
+ ### Render Pipeline
966
+
967
+ ```typescript
968
+ import { createIntegratedViewer } from "gerbers-renderer";
969
+
970
+ const viewer = createIntegratedViewer(container);
971
+ viewer.setData({ boardGeom, layers });
972
+
973
+ // Add custom render passes
974
+ viewer.viewer.addPass({
975
+ id: "custom-overlay",
976
+ order: 150,
977
+ enabled: () => true,
978
+ draw: (rc) => {
979
+ // Draw in board coordinates
980
+ const m = rc.xform.getWorldToScreenMatrix();
981
+ rc.ctx.setTransform(m[0], m[3], m[1], m[4], m[2], m[5]);
982
+ // ... your drawing code
983
+ }
984
+ });
985
+ ```
986
+
987
+ **Render Stages:**
988
+ - **Base Gerber** (0-99): Copper traces, masks, silk screen
989
+ - **Overlays** (100-199): Grid, rulers, custom drawings
990
+ - **Markers** (200-299): Test points, components, annotations
991
+ - **Selection** (300-399): Highlighted regions and elements
992
+
993
+ **Key Features:**
994
+ - Deterministic rendering order
995
+ - Efficient render scheduling with requestAnimationFrame
996
+ - Centralized visibility management
997
+ - Extensible render pass system
998
+
999
+ **Visibility Control:**
1000
+ ```typescript
1001
+ // Use presets
1002
+ viewer.visibility.applyPreset('copper-only');
1003
+
1004
+ // Individual control
1005
+ viewer.visibility.setGerberVisibility('copper', false);
1006
+ viewer.visibility.setOverlayVisibility('grid', true);
1007
+ viewer.visibility.setMarkersVisibility(true);
1008
+ ```
1009
+
1010
+ **File Organization:**
1011
+ - `src/render-pipeline/core/`: Core components (transforms, contracts, scheduling)
1012
+ - `src/render-pipeline/`: Complete render pipeline implementation
1013
+ - `src/render-pipeline/overlayRegistry.ts`: Overlay management system
1014
+ - `src/render-pipeline/exampleOverlays.ts`: Built-in overlay examples
1015
+ - `src/viewer/`: Shared types and styles
1016
+ - `src/index.ts`: Unified exports
1017
+
1018
+ See [FILE_STRUCTURE.md](./FILE_STRUCTURE.md) for detailed organization.
1019
+
241
1020
  ## License
242
1021
 
243
1022
  MIT