gds-lens 1.1.0 → 1.3.0
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.
- package/CHANGELOG.md +128 -1
- package/README.md +59 -7
- package/dist/esm/gds-lens.js +535 -63
- package/dist/inline-wasm/gds-lens-engine.js +0 -0
- package/dist/inline-wasm/gds-lens-host.js +4 -1
- package/dist/inline-wasm/gds-lens-worker.js +15 -3
- package/dist/inline-wasm/gds-lens.js +529 -60
- package/dist/web/gds-lens-engine.js +1 -1
- package/dist/web/gds-lens-engine.wasm +0 -0
- package/dist/web/gds-lens-host.js +4 -1
- package/dist/web/gds-lens-worker.js +15 -3
- package/dist/web/gds-lens.js +529 -60
- package/docs/embedding.md +69 -0
- package/docs/react.md +19 -6
- package/package.json +1 -1
- package/src/gds-lens.js +91 -2
- package/src/hosts/browser.js +8 -1
- package/src/viewer.css +17 -0
- package/src/viewer.js +647 -66
- package/src/wasm-worker.js +21 -3
- package/types/gds-lens.d.ts +169 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,132 @@ follows [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
From 1.0.0 on, a breaking change to the element's API waits for a major
|
|
8
8
|
version. Before that, `0.x` releases changed it freely.
|
|
9
9
|
|
|
10
|
+
## [1.3.0] - 2026-09-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Two layouts in one viewer.** A `<gds-lens>` can hold two layouts at once
|
|
15
|
+
and draw them through one camera into one canvas, for comparing two
|
|
16
|
+
revisions of a design:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
await element.load(oldBytes);
|
|
20
|
+
await element.load(newBytes, { slot: "b", name: "rev-b.gds" });
|
|
21
|
+
await element.setBlend(0.5);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `load(source, { slot })` picks which layout to replace -- `"a"` (the
|
|
25
|
+
default, and the only one a single-layout viewer ever uses) or `"b"`. A
|
|
26
|
+
second layout arriving does not move the camera off what is already being
|
|
27
|
+
read, and loading either slot leaves the other alone.
|
|
28
|
+
- `unload(slot)` drops one again; `setBlend(t)` / `getBlend()` crossfade
|
|
29
|
+
between them, `0` showing only A, `1` only B, between overlaying them.
|
|
30
|
+
- With two loaded, the panel grows a **Compare** folder: the blend slider,
|
|
31
|
+
an optional per-layout tint, and **Highlight differences**, which marks
|
|
32
|
+
per (layer, datatype) where the two disagree -- one colour where only A
|
|
33
|
+
has geometry, another where only B does. Both rasterize through the same
|
|
34
|
+
camera into the same coverage mask in the same frame, so identical
|
|
35
|
+
geometry cancels exactly. It is a difference of what is drawn at the zoom
|
|
36
|
+
being viewed, not a geometric XOR, and differences under about half a
|
|
37
|
+
pixel are ignored; zoom in to resolve a smaller one.
|
|
38
|
+
- The layer list shows the **union** of both layouts' layers, chipped A or B
|
|
39
|
+
where only one has it, so an added or removed layer is visible as a row
|
|
40
|
+
rather than absent. The hierarchy browser roots both cell trees, and cell
|
|
41
|
+
and label searches run over both with the same chips on the hits.
|
|
42
|
+
- `getLayers()` entries gain `source` (0 for slot A, 1 for B), and
|
|
43
|
+
`gds-load`'s detail gains `slot`.
|
|
44
|
+
|
|
45
|
+
See [docs/embedding.md](docs/embedding.md#two-layouts-in-one-viewer). The
|
|
46
|
+
single-layout case is unchanged in every respect: no attribute, no mode
|
|
47
|
+
flag, and the Compare folder is not built at all until a second layout is
|
|
48
|
+
loaded.
|
|
49
|
+
|
|
50
|
+
- **gdsfactory / kfactory ports.** kfactory records each cell's ports as
|
|
51
|
+
KLayout meta info, which KLayout writes into the layout file itself: a
|
|
52
|
+
`$$$CONTEXT_INFO$$$` cell in GDSII, `KLAYOUT_CONTEXT` properties in OASIS,
|
|
53
|
+
each holding strings like `META('kfactory:ports:0')={'name'=>'o1',...}`. The
|
|
54
|
+
viewer now reads those back (`src/wasm/kfactory_ports.cpp`) -- name, type,
|
|
55
|
+
position, direction, width and layer, through the named cross-section --
|
|
56
|
+
and expands them through every placement, so a component's ports are marked
|
|
57
|
+
wherever it sits in the design: a bar across the port, an arrow the way it
|
|
58
|
+
faces, and its name once few enough are on screen to read. Optical ports
|
|
59
|
+
are orange, electrical green, anything else the highlight blue. A **Ports**
|
|
60
|
+
toggle in Display turns the overlay off; a **Ports** folder lists the top
|
|
61
|
+
cell's ports and centers the view on one when clicked. Each hierarchy cell
|
|
62
|
+
entry carries a `ports` array and the hierarchy a `portCount`, the
|
|
63
|
+
`gds-load` event's detail gains `portCount`, and the element exposes nothing
|
|
64
|
+
new otherwise -- a file without the metadata looks exactly as before. The
|
|
65
|
+
expansion stops at 200,000 port placements and says so in the panel.
|
|
66
|
+
|
|
67
|
+
- **`getCamera()` / `setCamera({zoom, panX, panY})`,
|
|
68
|
+
`getMeasurements()` / `addMeasurement(x0, y0, x1, y1)` /
|
|
69
|
+
`clearMeasurements()`, and `getLayers()` / `setLayerVisible(...)`** on the
|
|
70
|
+
viewer surface and the element, for an app that wants to frame the view,
|
|
71
|
+
place a ruler or drive layer visibility itself. Thin pass-throughs over
|
|
72
|
+
behavior that already existed internally; `getLayers()` is also the only way
|
|
73
|
+
to read a layer's name, group, colors and shape counts.
|
|
74
|
+
|
|
75
|
+
## [1.2.0] - 2026-09-03
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- `gds-load` and `gds-error` events on the element, dispatched however a load
|
|
80
|
+
was started -- the `src` attribute, `load()`, or a host pushing bytes through
|
|
81
|
+
its surface. Until now a load that came from `src` could fail only into the
|
|
82
|
+
viewer's own error panel, with nothing for the embedding page to observe.
|
|
83
|
+
`detail` carries `{ layerCount, cellCount }` and `{ message }` respectively.
|
|
84
|
+
Prefixed rather than `load` / `error`, which every HTML element already has
|
|
85
|
+
with other types attached.
|
|
86
|
+
- `showLoading(label?)` on the element. The surface had it; a page fetching its
|
|
87
|
+
own bytes and calling `load(bytes)` could not reach it without `ready`, so it
|
|
88
|
+
sat on "No layout loaded" for the length of the download.
|
|
89
|
+
- A message when WebGL2 cannot be had: unsupported, disabled, or the page over
|
|
90
|
+
its limit on live contexts. The renderer's `main()` returns quietly when its
|
|
91
|
+
context creation fails -- which is what lets the same module run in the parse
|
|
92
|
+
worker and under Node, where there is no canvas -- and nothing on the other
|
|
93
|
+
side ever asked, so the result was a canvas that never drew and a `load()`
|
|
94
|
+
that reported success. The viewer now asks (`isGlReady`) and says so, and a
|
|
95
|
+
lost context (`webglcontextlost`, after a GPU reset or the browser reclaiming
|
|
96
|
+
it) is reported the same way rather than left as a black canvas.
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- `load()` settles on the outcome. It used to resolve as soon as the file had
|
|
101
|
+
been handed to the parse worker, so it never rejected on a file the parser
|
|
102
|
+
refused -- contrary to what the React docs claimed of it. It now resolves
|
|
103
|
+
once the layout is on screen and rejects with the same message the viewer
|
|
104
|
+
shows. A load superseded by a newer one rejects with an error named
|
|
105
|
+
`AbortError`, the name `fetch()` uses for the same thing.
|
|
106
|
+
|
|
107
|
+
- The demo page's layout is served as gzipped OASIS rather than gzipped GDSII:
|
|
108
|
+
the same design, 4.4 MB down from 8.1 MB. `site/make-demo-assets.py` writes
|
|
109
|
+
OASIS now.
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
|
|
113
|
+
- The parse worker wrote a dozen lines to the console on every load, into the
|
|
114
|
+
embedding page's DevTools, whether or not tracing had been asked for. The
|
|
115
|
+
relay to the on-screen debug panel was always meant to be unconditional; the
|
|
116
|
+
copy to the real console was not. It is now gated on the same flag as the
|
|
117
|
+
main thread's breadcrumbs (the `debug` attribute or `?gdsDebug=1`). The test
|
|
118
|
+
meant to catch this filtered on `[GDS]`, and the worker's prefix is
|
|
119
|
+
`[GDS worker]`.
|
|
120
|
+
- `destroy()` did not release the viewer. Six listeners on `window` (the
|
|
121
|
+
keyboard shortcuts and the coordinate menu's dismissal) were never removed,
|
|
122
|
+
and a listener's closure holds the whole viewer, wasm instance included; the
|
|
123
|
+
renderer's own `mouseup` and `resize` callbacks on `window` did the same from
|
|
124
|
+
the other side, and the WebGL context waited on garbage collection. Every
|
|
125
|
+
listener now carries an abort signal that `dispose()` fires, and the renderer
|
|
126
|
+
exports a `destroyRenderer()` that unregisters its callbacks and destroys the
|
|
127
|
+
context outright -- so a page calling `destroy()` because it hit the per-page
|
|
128
|
+
context cap gets its slot back when it asks, not when the collector gets to
|
|
129
|
+
it.
|
|
130
|
+
- Two quick changes to `src` (or two `load(url)` calls) could show the older
|
|
131
|
+
layout: the viewer superseded an in-flight *parse*, but the fetch in front of
|
|
132
|
+
it was nobody's to cancel, so a slow first file landing after a small second
|
|
133
|
+
one won. Each `load()` now aborts the fetch before it, and a load parked on
|
|
134
|
+
gzip expansion notices it has been superseded when it wakes.
|
|
135
|
+
|
|
10
136
|
## [1.1.0] - 2026-08-31
|
|
11
137
|
|
|
12
138
|
### Added
|
|
@@ -389,7 +515,8 @@ web page rather than for a webview.
|
|
|
389
515
|
worker-loading route and shipped unsubstituted. The `createWorker` host hook
|
|
390
516
|
replaces it.
|
|
391
517
|
|
|
392
|
-
[Unreleased]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.
|
|
518
|
+
[Unreleased]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.2.0...HEAD
|
|
519
|
+
[1.2.0]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.1.0...v1.2.0
|
|
393
520
|
[1.1.0]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.0.3...v1.1.0
|
|
394
521
|
[1.0.3]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.0.2...v1.0.3
|
|
395
522
|
[1.0.2]: https://github.com/EthanLowenthal/GDS-Lens/compare/v1.0.1...v1.0.2
|
package/README.md
CHANGED
|
@@ -15,9 +15,6 @@ a given release does.
|
|
|
15
15
|
|
|
16
16
|

|
|
17
17
|
|
|
18
|
-
> **Status**: 1.0. The element's API is stable, and breaking changes wait
|
|
19
|
-
> for a major version. See [the changelog](CHANGELOG.md).
|
|
20
|
-
|
|
21
18
|
## Quick start
|
|
22
19
|
|
|
23
20
|
```sh
|
|
@@ -68,6 +65,14 @@ a streaming WebAssembly compile — see
|
|
|
68
65
|
and ASCII DRC results.
|
|
69
66
|
- **Navigates hierarchy**. You can search cells and labels, and measure
|
|
70
67
|
distances.
|
|
68
|
+
- **Shows gdsfactory / kfactory ports**. A layout written by gdsfactory 8+
|
|
69
|
+
carries its ports as KLayout metadata inside the file; the viewer reads them
|
|
70
|
+
back, draws every port as a bar across its width with an arrow the way it
|
|
71
|
+
faces (and its name, close in), and lists the top cell's ports in the panel.
|
|
72
|
+
No Python involved.
|
|
73
|
+
- **Compares two layouts**. One viewer can hold two of them, drawn through one
|
|
74
|
+
camera, with a crossfade between them and a per-layer highlight of where they
|
|
75
|
+
differ. See [Two layouts in one viewer](docs/embedding.md#two-layouts-in-one-viewer).
|
|
71
76
|
|
|
72
77
|
## Installation
|
|
73
78
|
|
|
@@ -94,8 +99,6 @@ takes one attribute and exposes a handful of methods.
|
|
|
94
99
|
|
|
95
100
|
#### Attributes
|
|
96
101
|
|
|
97
|
-
The element takes one attribute:
|
|
98
|
-
|
|
99
102
|
| Attribute | Description |
|
|
100
103
|
|---|---|
|
|
101
104
|
| `src` | URL of a layout to fetch and display. Setting it later reloads. |
|
|
@@ -106,13 +109,62 @@ The element exposes the following members:
|
|
|
106
109
|
|
|
107
110
|
| Member | Returns | Description |
|
|
108
111
|
|---|---|---|
|
|
109
|
-
| `ready` | `Promise<
|
|
110
|
-
| `load(source, options?)` | `Promise<void>` | `source` is a URL string, a `Uint8Array`, or an `ArrayBuffer`. `options.reload` keeps the current camera and layer visibility instead of framing the design. |
|
|
112
|
+
| `ready` | `Promise<ViewerSurface>` | Resolves once the engine has mounted. Every method in the following table awaits this, so you rarely need it directly. |
|
|
113
|
+
| `load(source, options?)` | `Promise<void>` | `source` is a URL string, a `Uint8Array`, or an `ArrayBuffer`. `options.reload` keeps the current camera and layer visibility instead of framing the design. `options.slot` is `"a"` (the default) or `"b"`, which loads a second layout alongside the first to compare them — see [Two layouts in one viewer](docs/embedding.md#two-layouts-in-one-viewer). Resolves once the layout is on screen; rejects on a failed fetch, a file the parser refuses, or — with an error named `AbortError` — when a later load into the same slot superseded this one. |
|
|
114
|
+
| `showLoading(label?)` | `Promise<void>` | Shows the loading overlay, for the wait before a `load()` of bytes the page is fetching itself. `load(url)` does this on its own. |
|
|
111
115
|
| `goToPoint(x, y)` | `Promise<boolean>` | Centers on a coordinate in microns and flashes a crosshair. Resolves `true` if the point is inside the layout. |
|
|
112
116
|
| `setLyp(name, text)` | `Promise<void>` | Applies a `.lyp` layer-properties file. Pass `""` to clear. |
|
|
113
117
|
| `setMarkers(name, text)` | `Promise<void>` | Applies a marker database. The viewer detects the format from the content. |
|
|
114
118
|
| `showError(message)` | `Promise<void>` | Replaces the view with an error message. |
|
|
115
119
|
| `destroy()` | `Promise<void>` | Releases the viewer's WebAssembly instance and WebGL context for good. Rarely needed — see [Removal parks the viewer](#removal-parks-the-viewer). |
|
|
120
|
+
| `getCamera()` | `Promise<{zoom, panX, panY}>` | The current pan/zoom. |
|
|
121
|
+
| `setCamera(camera)` | `Promise<void>` | Sets the pan/zoom. Clamped to the loaded design's bounds (the union of both, with two layouts loaded). |
|
|
122
|
+
| `getLayers()` | `Promise<LayerInfo[]>` | Every layer's name, group, colors, visibility and shape counts, plus the `source` slot it came from. |
|
|
123
|
+
| `setLayerVisible(layer, datatype, visible)` | `Promise<void>` | Shows or hides one layer. |
|
|
124
|
+
| `getMeasurements()` | `Promise<{x0,y0,x1,y1}[]>` | Every ruler on screen, in micron endpoints. |
|
|
125
|
+
| `addMeasurement(x0, y0, x1, y1)` | `Promise<void>` | Places a finished ruler without disturbing measure mode. |
|
|
126
|
+
| `clearMeasurements()` | `Promise<void>` | Removes every ruler. Rulers don't survive a reload either way. |
|
|
127
|
+
| `unload(slot?)` | `Promise<void>` | Drops the second layout, leaving a single-layout viewer. |
|
|
128
|
+
| `setBlend(t)` | `Promise<void>` | Crossfades two loaded layouts: `0` shows only A, `1` only B, between overlays them. |
|
|
129
|
+
| `getBlend()` | `Promise<number>` | The current crossfade. |
|
|
130
|
+
|
|
131
|
+
Every `load()` cancels the one before it *for the same slot*, so two quick
|
|
132
|
+
changes to `src` show the second layout even when the first is the slower
|
|
133
|
+
download.
|
|
134
|
+
|
|
135
|
+
#### Events
|
|
136
|
+
|
|
137
|
+
The element dispatches events on itself. None bubbles.
|
|
138
|
+
|
|
139
|
+
| Event | `detail` | When |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `gds-load` | `{ slot, layerCount, cellCount, portCount }` | A layout finished loading and is on screen, whichever way the load was started — the `src` attribute, `load()`, or a host pushing bytes through its surface. `slot` is which of the two layouts it is. `portCount` is how many ports its kfactory metadata declared, 0 for a plain file. |
|
|
142
|
+
| `gds-error` | `{ message }` | A load failed, or `showError()` was called. `message` is the text the viewer shows. |
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
viewer.addEventListener("gds-load", (event) => {
|
|
146
|
+
console.log(`${event.detail.layerCount} layers, ${event.detail.cellCount} cells`);
|
|
147
|
+
});
|
|
148
|
+
viewer.addEventListener("gds-error", (event) => {
|
|
149
|
+
reportProblem(event.detail.message);
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The names carry a prefix because `load` and `error` already have meanings —
|
|
154
|
+
and types — on every HTML element.
|
|
155
|
+
|
|
156
|
+
#### Keyboard shortcuts
|
|
157
|
+
|
|
158
|
+
The keys act on the viewer that was last pointed at, so on a page with several
|
|
159
|
+
they go to the one under the mouse.
|
|
160
|
+
|
|
161
|
+
| Key | Action |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `[` / `]` | Previous / next marker in the selected marker's category. |
|
|
164
|
+
| `m` | Toggle measure mode. Click two points to place a ruler. |
|
|
165
|
+
| `Esc` | Abandon a ruler being placed; with none in progress, clear the finished ones and return to pan mode. Also closes the coordinate menu. |
|
|
166
|
+
| `h` | Show or hide the cell hierarchy. |
|
|
167
|
+
| `/` | Focus the cell filter box. |
|
|
116
168
|
|
|
117
169
|
#### Several viewers on one page
|
|
118
170
|
|