laylight 0.0.2__tar.gz → 0.2.2__tar.gz

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 (155) hide show
  1. laylight-0.2.2/.gitignore +33 -0
  2. laylight-0.2.2/PKG-INFO +68 -0
  3. {laylight-0.0.2 → laylight-0.2.2}/README.md +13 -12
  4. laylight-0.2.2/laylight/__init__.py +64 -0
  5. laylight-0.2.2/laylight/backend.py +143 -0
  6. laylight-0.2.2/laylight/font.py +157 -0
  7. laylight-0.2.2/laylight/geometry.py +704 -0
  8. laylight-0.2.2/laylight/ir.py +380 -0
  9. laylight-0.2.2/laylight/klayout_bridge.py +121 -0
  10. laylight-0.2.2/laylight/layout.py +1036 -0
  11. laylight-0.2.2/laylight/pcell.py +196 -0
  12. laylight-0.2.2/laylight/placable.py +588 -0
  13. laylight-0.2.2/laylight/primitives.py +6 -0
  14. {laylight-0.0.2 → laylight-0.2.2}/laylight/render.py +130 -155
  15. {laylight-0.0.2 → laylight-0.2.2}/laylight/render_all.py +194 -256
  16. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/__init__.py +22 -20
  17. laylight-0.2.2/laylight/structure/abstract.py +266 -0
  18. laylight-0.2.2/laylight/structure/black_box.py +121 -0
  19. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/connection.py +238 -265
  20. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/coupler.py +469 -641
  21. laylight-0.2.2/laylight/structure/crossing.py +50 -0
  22. laylight-0.2.2/laylight/structure/curve.py +126 -0
  23. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/cutback.py +99 -125
  24. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/grating.py +522 -660
  25. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/metal.py +187 -236
  26. laylight-0.2.2/laylight/structure/mmi.py +251 -0
  27. laylight-0.2.2/laylight/structure/mzi.py +139 -0
  28. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/mzi_rect.py +332 -357
  29. laylight-0.2.2/laylight/structure/packaging.py +81 -0
  30. laylight-0.2.2/laylight/structure/prober.py +85 -0
  31. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/racetrack.py +116 -173
  32. laylight-0.2.2/laylight/structure/ring.py +140 -0
  33. {laylight-0.0.2 → laylight-0.2.2}/laylight/structure/spiral.py +174 -200
  34. laylight-0.2.2/laylight/structure/taper.py +86 -0
  35. laylight-0.2.2/laylight/structure/transition.py +38 -0
  36. laylight-0.2.2/laylight/structure/wg.py +119 -0
  37. laylight-0.2.2/laylight/technology.py +62 -0
  38. laylight-0.2.2/pyproject.toml +34 -0
  39. laylight-0.0.2/.gitignore +0 -15
  40. laylight-0.0.2/PKG-INFO +0 -69
  41. laylight-0.0.2/laylight/__init__.py +0 -32
  42. laylight-0.0.2/laylight/candy.py +0 -42
  43. laylight-0.0.2/laylight/main.py +0 -6
  44. laylight-0.0.2/laylight/pcell.py +0 -183
  45. laylight-0.0.2/laylight/placable.py +0 -177
  46. laylight-0.0.2/laylight/primitives.py +0 -6
  47. laylight-0.0.2/laylight/structure/abstract.py +0 -144
  48. laylight-0.0.2/laylight/structure/black_box.py +0 -131
  49. laylight-0.0.2/laylight/structure/crossing.py +0 -84
  50. laylight-0.0.2/laylight/structure/curve.py +0 -246
  51. laylight-0.0.2/laylight/structure/mmi.py +0 -409
  52. laylight-0.0.2/laylight/structure/mzi.py +0 -244
  53. laylight-0.0.2/laylight/structure/packaging.py +0 -148
  54. laylight-0.0.2/laylight/structure/prober.py +0 -149
  55. laylight-0.0.2/laylight/structure/ring.py +0 -219
  56. laylight-0.0.2/laylight/structure/taper.py +0 -93
  57. laylight-0.0.2/laylight/structure/transition.py +0 -67
  58. laylight-0.0.2/laylight/structure/wg.py +0 -203
  59. laylight-0.0.2/laylight/technology.py +0 -21
  60. laylight-0.0.2/pyproject.toml +0 -21
  61. laylight-0.0.2/tests/.gitignore +0 -1
  62. laylight-0.0.2/tests/baseline/adiabatic_coupler_shape.png +0 -0
  63. laylight-0.0.2/tests/baseline/arc_curve_shape.png +0 -0
  64. laylight-0.0.2/tests/baseline/black_box_grating_shape.png +0 -0
  65. laylight-0.0.2/tests/baseline/black_box_mmi_shape.png +0 -0
  66. laylight-0.0.2/tests/baseline/black_box_shape.png +0 -0
  67. laylight-0.0.2/tests/baseline/contra_directional_coupler_shape.png +0 -0
  68. laylight-0.0.2/tests/baseline/crossing_shape.png +0 -0
  69. laylight-0.0.2/tests/baseline/cutback_shape.png +0 -0
  70. laylight-0.0.2/tests/baseline/die_with_pads_shape.png +0 -0
  71. laylight-0.0.2/tests/baseline/directional_coupler_shape.png +0 -0
  72. laylight-0.0.2/tests/baseline/fiber_array_shape.png +0 -0
  73. laylight-0.0.2/tests/baseline/grating_bragg_shape.png +0 -0
  74. laylight-0.0.2/tests/baseline/grating_elliptical_shape.png +0 -0
  75. laylight-0.0.2/tests/baseline/grating_focusing_shape.png +0 -0
  76. laylight-0.0.2/tests/baseline/grating_polarization_shape.png +0 -0
  77. laylight-0.0.2/tests/baseline/grating_rectangular_shape.png +0 -0
  78. laylight-0.0.2/tests/baseline/gsg_pad_shape.png +0 -0
  79. laylight-0.0.2/tests/baseline/heater_pad_shape.png +0 -0
  80. laylight-0.0.2/tests/baseline/heater_routing_shape.png +0 -0
  81. laylight-0.0.2/tests/baseline/metal_straight_shape.png +0 -0
  82. laylight-0.0.2/tests/baseline/metal_transition_shape.png +0 -0
  83. laylight-0.0.2/tests/baseline/mmi_shape.png +0 -0
  84. laylight-0.0.2/tests/baseline/mzi_rectangular_array_shape.png +0 -0
  85. laylight-0.0.2/tests/baseline/mzi_shape.png +0 -0
  86. laylight-0.0.2/tests/baseline/mzi_tensor_shape.png +0 -0
  87. laylight-0.0.2/tests/baseline/pad_shape.png +0 -0
  88. laylight-0.0.2/tests/baseline/pcell_library_shape.png +0 -0
  89. laylight-0.0.2/tests/baseline/racetrack_coupled_shape.png +0 -0
  90. laylight-0.0.2/tests/baseline/racetrack_shape.png +0 -0
  91. laylight-0.0.2/tests/baseline/ring_coupled_shape.png +0 -0
  92. laylight-0.0.2/tests/baseline/ring_shape.png +0 -0
  93. laylight-0.0.2/tests/baseline/scurve_shape.png +0 -0
  94. laylight-0.0.2/tests/baseline/spiral_shape.png +0 -0
  95. laylight-0.0.2/tests/baseline/strip_to_rib_transition_shape.png +0 -0
  96. laylight-0.0.2/tests/baseline/via_stack_shape.png +0 -0
  97. laylight-0.0.2/tests/baseline/wg_straight_shape.png +0 -0
  98. laylight-0.0.2/tests/baseline/ybranch_shape.png +0 -0
  99. laylight-0.0.2/tests/common.py +0 -115
  100. laylight-0.0.2/tests/output/adiabatic_coupler_shape.png +0 -0
  101. laylight-0.0.2/tests/output/arc_curve_shape.png +0 -0
  102. laylight-0.0.2/tests/output/black_box_grating_shape.png +0 -0
  103. laylight-0.0.2/tests/output/black_box_mmi_shape.png +0 -0
  104. laylight-0.0.2/tests/output/black_box_shape.png +0 -0
  105. laylight-0.0.2/tests/output/contra_directional_coupler_shape.png +0 -0
  106. laylight-0.0.2/tests/output/crossing_shape.png +0 -0
  107. laylight-0.0.2/tests/output/cutback_shape.png +0 -0
  108. laylight-0.0.2/tests/output/die_with_pads_shape.png +0 -0
  109. laylight-0.0.2/tests/output/directional_coupler_shape.png +0 -0
  110. laylight-0.0.2/tests/output/fiber_array_shape.png +0 -0
  111. laylight-0.0.2/tests/output/grating_bragg_shape.png +0 -0
  112. laylight-0.0.2/tests/output/grating_elliptical_shape.png +0 -0
  113. laylight-0.0.2/tests/output/grating_focusing_shape.png +0 -0
  114. laylight-0.0.2/tests/output/grating_polarization_shape.png +0 -0
  115. laylight-0.0.2/tests/output/grating_rectangular_shape.png +0 -0
  116. laylight-0.0.2/tests/output/gsg_pad_shape.png +0 -0
  117. laylight-0.0.2/tests/output/heater_pad_shape.png +0 -0
  118. laylight-0.0.2/tests/output/heater_routing_shape.png +0 -0
  119. laylight-0.0.2/tests/output/metal_straight_shape.png +0 -0
  120. laylight-0.0.2/tests/output/metal_transition_shape.png +0 -0
  121. laylight-0.0.2/tests/output/mmi_shape.png +0 -0
  122. laylight-0.0.2/tests/output/mzi_rectangular_array_shape.png +0 -0
  123. laylight-0.0.2/tests/output/mzi_shape.png +0 -0
  124. laylight-0.0.2/tests/output/mzi_tensor_shape.png +0 -0
  125. laylight-0.0.2/tests/output/pad_shape.png +0 -0
  126. laylight-0.0.2/tests/output/pcell_library_shape.png +0 -0
  127. laylight-0.0.2/tests/output/racetrack_coupled_shape.png +0 -0
  128. laylight-0.0.2/tests/output/racetrack_shape.png +0 -0
  129. laylight-0.0.2/tests/output/ring_coupled_shape.png +0 -0
  130. laylight-0.0.2/tests/output/ring_shape.png +0 -0
  131. laylight-0.0.2/tests/output/scurve_shape.png +0 -0
  132. laylight-0.0.2/tests/output/spiral_shape.png +0 -0
  133. laylight-0.0.2/tests/output/strip_to_rib_transition_shape.png +0 -0
  134. laylight-0.0.2/tests/output/via_stack_shape.png +0 -0
  135. laylight-0.0.2/tests/output/wg_straight_shape.png +0 -0
  136. laylight-0.0.2/tests/output/ybranch_shape.png +0 -0
  137. laylight-0.0.2/tests/test_black_box_shape.py +0 -154
  138. laylight-0.0.2/tests/test_coupler_shape.py +0 -79
  139. laylight-0.0.2/tests/test_crossing_shape.py +0 -29
  140. laylight-0.0.2/tests/test_curve_shape.py +0 -75
  141. laylight-0.0.2/tests/test_cutback_shape.py +0 -29
  142. laylight-0.0.2/tests/test_grating_shape.py +0 -94
  143. laylight-0.0.2/tests/test_metal_shape.py +0 -75
  144. laylight-0.0.2/tests/test_mmi_shape.py +0 -51
  145. laylight-0.0.2/tests/test_mzi_rect_shape.py +0 -128
  146. laylight-0.0.2/tests/test_mzi_shape.py +0 -137
  147. laylight-0.0.2/tests/test_packaging_shape.py +0 -45
  148. laylight-0.0.2/tests/test_pcell.py +0 -64
  149. laylight-0.0.2/tests/test_pcell_library.py +0 -178
  150. laylight-0.0.2/tests/test_prober_shape.py +0 -74
  151. laylight-0.0.2/tests/test_racetrack_shape.py +0 -29
  152. laylight-0.0.2/tests/test_render.py +0 -140
  153. laylight-0.0.2/tests/test_resonator_shape.py +0 -92
  154. laylight-0.0.2/tests/test_spiral_shape.py +0 -29
  155. laylight-0.0.2/tests/test_waveguide_shape.py +0 -76
@@ -0,0 +1,33 @@
1
+ /target
2
+ node_modules/
3
+ dist/
4
+ packages/laylight-viewer-demo/public/
5
+
6
+ # large local test fixture (kept out of git; re-obtain or regenerate locally)
7
+ samples/gds/large-test.gds
8
+ samples/gds/large-test.gds.gz
9
+
10
+ # generated perf-bench fixture (regenerate: cargo run --release -p psdb --example gen_bench_gds)
11
+ samples/gds/bench-gen.gds
12
+ samples/gds/bench-gen.gds.gz
13
+
14
+ # generated CI perf-gate fixture (regenerated by scripts/perf-gate.mjs)
15
+ samples/gds/bench-gate.gds
16
+
17
+ # Python workspace (root pyproject.toml, packages under pypkg/)
18
+ __pycache__/
19
+ *.py[oc]
20
+ *.egg-info
21
+ .venv
22
+ .pytest_cache
23
+ .local
24
+ build/
25
+ wheels/
26
+
27
+ # laylight docs site build output (packages/laylight-docs)
28
+ packages/laylight-docs/.next/
29
+ packages/laylight-docs/out/
30
+ *.tsbuildinfo
31
+ packages/laylight-docs/lib/lyl-samples.generated.ts
32
+ packages/laylight-docs/lib/benchmarks.generated.ts
33
+ packages/laylight-docs/lib/photonics-examples.generated.ts
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.5
2
+ Name: laylight
3
+ Version: 0.2.2
4
+ Summary: Photonic layout in Python, compiled to GDSII by the Laylight compiler
5
+ Keywords: gds,layout,photonics
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: laylight-cli==0.2.2
8
+ Provides-Extra: klayout
9
+ Requires-Dist: klayout>=0.30.8; extra == 'klayout'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # laylight
13
+
14
+ `laylight` is a Python toolkit for photonic layout design. Devices are Python classes; the library builds the [Laylight IR](https://laylight.photonstack.io/concepts/compilation/) from them, and the Laylight compiler, the `laylight` command line, turns the IR into GDSII.
15
+
16
+ laylight is a sub-product of [PhotonStack](https://photonstack.io/), focused on making photonic design more programmable, composable, and easier to explore.
17
+
18
+ ## Why laylight
19
+
20
+ Photonic layout work often mixes device geometry, parameter sweeps, custom scripts, and visualization. laylight provides a Python-first way to:
21
+
22
+ - define photonic structures with reusable, parameterized devices
23
+ - chain devices port to port with `Placer`
24
+ - write layouts that carry their cells' parameters, ports and connections, through the same compiler as the [Laylight DSL](https://laylight.photonstack.io/concepts/dsl/)
25
+
26
+ ## Documentation and tools
27
+
28
+ - Documentation: [laylight.photonstack.io](https://laylight.photonstack.io/)
29
+ - Playground: [laylight.photonstack.io/playground](https://laylight.photonstack.io/playground/)
30
+ - VS Code extension: [PhotonStack/laylight-vscode-extension](https://github.com/PhotonStack/laylight-vscode-extension)
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install laylight
36
+ ```
37
+
38
+ This installs the `laylight` command line too (the `laylight-cli` package). PNG previews and KLayout PCells need KLayout:
39
+
40
+ ```bash
41
+ pip install "laylight[klayout]"
42
+ ```
43
+
44
+ ## Simple example
45
+
46
+ ```python
47
+ import laylight
48
+ from laylight.render import new_layout
49
+ from laylight.structure.wg import WGStraight
50
+
51
+ layout, top, tech = new_layout("DEMO")
52
+
53
+ device = WGStraight(
54
+ length=200,
55
+ width=0.5,
56
+ layout=layout,
57
+ tech=tech,
58
+ )
59
+
60
+ laylight.Placer(device).into(top)
61
+ layout.write("demo.gds")
62
+ ```
63
+
64
+ This creates a straight waveguide and writes the result to `demo.gds`. `layout.to_ir()` returns the Laylight IR, and `layout.write("demo.lyir")` writes it to a file that Laylight DSL code can `import`.
65
+
66
+ ## Learn more
67
+
68
+ For guided examples, API usage, and browser-based experiments, start with the documentation site and the online playground.
@@ -1,6 +1,6 @@
1
1
  # laylight
2
2
 
3
- `laylight` is a Python toolkit for photonic layout design. It helps engineers and researchers describe photonic structures in code, generate layout data with KLayout, and build repeatable design flows around reusable devices.
3
+ `laylight` is a Python toolkit for photonic layout design. Devices are Python classes; the library builds the [Laylight IR](https://laylight.photonstack.io/concepts/compilation/) from them, and the Laylight compiler, the `laylight` command line, turns the IR into GDSII.
4
4
 
5
5
  laylight is a sub-product of [PhotonStack](https://photonstack.io/), focused on making photonic design more programmable, composable, and easier to explore.
6
6
 
@@ -8,9 +8,9 @@ laylight is a sub-product of [PhotonStack](https://photonstack.io/), focused on
8
8
 
9
9
  Photonic layout work often mixes device geometry, parameter sweeps, custom scripts, and visualization. laylight provides a Python-first way to:
10
10
 
11
- - define photonic structures with reusable building blocks
12
- - generate layout data with a programmable workflow
13
- - preview and iterate on designs more quickly than manual editing alone
11
+ - define photonic structures with reusable, parameterized devices
12
+ - chain devices port to port with `Placer`
13
+ - write layouts that carry their cells' parameters, ports and connections, through the same compiler as the [Laylight DSL](https://laylight.photonstack.io/concepts/dsl/)
14
14
 
15
15
  ## Documentation and tools
16
16
 
@@ -24,18 +24,20 @@ Photonic layout work often mixes device geometry, parameter sweeps, custom scrip
24
24
  pip install laylight
25
25
  ```
26
26
 
27
+ This installs the `laylight` command line too (the `laylight-cli` package). PNG previews and KLayout PCells need KLayout:
28
+
29
+ ```bash
30
+ pip install "laylight[klayout]"
31
+ ```
32
+
27
33
  ## Simple example
28
34
 
29
35
  ```python
30
36
  import laylight
31
-
32
- from laylight.technology import Technology
37
+ from laylight.render import new_layout
33
38
  from laylight.structure.wg import WGStraight
34
39
 
35
- layout = laylight.Layout()
36
- layout.dbu = 0.001
37
- tech = Technology()
38
- top = layout.create_cell("DEMO")
40
+ layout, top, tech = new_layout("DEMO")
39
41
 
40
42
  device = WGStraight(
41
43
  length=200,
@@ -48,9 +50,8 @@ laylight.Placer(device).into(top)
48
50
  layout.write("demo.gds")
49
51
  ```
50
52
 
51
- This creates a simple straight waveguide and writes the result to `demo.gds`.
53
+ This creates a straight waveguide and writes the result to `demo.gds`. `layout.to_ir()` returns the Laylight IR, and `layout.write("demo.lyir")` writes it to a file that Laylight DSL code can `import`.
52
54
 
53
55
  ## Learn more
54
56
 
55
57
  For guided examples, API usage, and browser-based experiments, start with the documentation site and the online playground.
56
-
@@ -0,0 +1,64 @@
1
+ """laylight: describe photonic layouts in Python and compile them with the
2
+ Laylight compiler.
3
+
4
+ Devices draw into a `Layout` of cells; `Layout.to_ir()` is the Laylight IR
5
+ of the result and `Layout.write("out.gds")` compiles it to GDSII with the
6
+ `laylight` command line.
7
+ """
8
+
9
+ from . import structure
10
+ from .backend import CompilerError, CompilerWarning
11
+ from .geometry import Box, Path, Point, Polygon, SimplePolygon, Trans, Vector, text
12
+ from .layout import Cell, CellInstArray, Instance, Layout, Marker, PortRef, read
13
+ from .pcell import (
14
+ PCellParameterSpec,
15
+ abstract_device_to_pcell,
16
+ register_abstract_device_pcell,
17
+ )
18
+ from .placable import Chain, Placer
19
+ from .render import (
20
+ LayoutRenderError,
21
+ RenderArtifacts,
22
+ execute_layout_code,
23
+ export_layout_png,
24
+ new_layout,
25
+ render_code_to_files,
26
+ )
27
+ from .structure.abstract import AbstractDevice, AdhocDevice
28
+ from .technology import LayerInfo, Technology
29
+
30
+ __all__ = [
31
+ "AbstractDevice",
32
+ "AdhocDevice",
33
+ "Box",
34
+ "Cell",
35
+ "CellInstArray",
36
+ "Chain",
37
+ "CompilerError",
38
+ "CompilerWarning",
39
+ "Instance",
40
+ "LayerInfo",
41
+ "Layout",
42
+ "LayoutRenderError",
43
+ "Marker",
44
+ "PCellParameterSpec",
45
+ "Path",
46
+ "Placer",
47
+ "Point",
48
+ "Polygon",
49
+ "PortRef",
50
+ "RenderArtifacts",
51
+ "SimplePolygon",
52
+ "Technology",
53
+ "Trans",
54
+ "Vector",
55
+ "abstract_device_to_pcell",
56
+ "execute_layout_code",
57
+ "export_layout_png",
58
+ "new_layout",
59
+ "read",
60
+ "register_abstract_device_pcell",
61
+ "render_code_to_files",
62
+ "structure",
63
+ "text",
64
+ ]
@@ -0,0 +1,143 @@
1
+ """The compiler backend: the `laylight` command line (the `laylight-cli`
2
+ package) turns Laylight IR into GDSII, and reads GDSII back as IR.
3
+
4
+ The command is found through the `LAYLIGHT_CLI` environment variable, then
5
+ where the installed `laylight-cli` package put it, then next to the running
6
+ Python, then on `PATH`.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import functools
12
+ from importlib import metadata
13
+ import os
14
+ from pathlib import Path
15
+ import re
16
+ import shutil
17
+ import subprocess
18
+ import sys
19
+ import sysconfig
20
+ import tempfile
21
+ from typing import Sequence
22
+ import warnings
23
+
24
+ # The first release whose `build` reads IR.
25
+ MIN_VERSION = (0, 2, 2)
26
+
27
+
28
+ class CompilerError(RuntimeError):
29
+ """The `laylight` command line is missing or failed."""
30
+
31
+
32
+ class CompilerWarning(UserWarning):
33
+ """A warning from the `laylight` command line."""
34
+
35
+
36
+ def find_cli() -> str:
37
+ """The path of the `laylight` command."""
38
+ configured = os.environ.get("LAYLIGHT_CLI")
39
+ if configured:
40
+ if not Path(configured).is_file():
41
+ raise CompilerError(f"LAYLIGHT_CLI is set to `{configured}`, which is not a file")
42
+ return configured
43
+ executable = "laylight.exe" if sys.platform == "win32" else "laylight"
44
+ for candidate in _installed_scripts(executable):
45
+ if candidate.is_file():
46
+ return str(candidate)
47
+ for directory in (sysconfig.get_path("scripts"), str(Path(sys.executable).parent)):
48
+ candidate = Path(directory) / executable
49
+ if candidate.is_file():
50
+ return str(candidate)
51
+ found = shutil.which("laylight")
52
+ if found:
53
+ return found
54
+ raise CompilerError(
55
+ "the `laylight` command line was not found: install it with "
56
+ "`pip install laylight-cli`, or point LAYLIGHT_CLI at the executable"
57
+ )
58
+
59
+
60
+ def _installed_scripts(executable: str) -> list[Path]:
61
+ """Where the installed `laylight-cli` distribution put its command: the
62
+ script its record lists, and `bin/` beside it (`pip install --target`)."""
63
+ try:
64
+ dist = metadata.distribution("laylight-cli")
65
+ except metadata.PackageNotFoundError:
66
+ return []
67
+ found = [
68
+ Path(str(dist.locate_file(f)))
69
+ for f in dist.files or []
70
+ if f.name == executable and f.parent.name in ("bin", "Scripts")
71
+ ]
72
+ root = Path(str(dist.locate_file("")))
73
+ found += [root / "bin" / executable, root / "Scripts" / executable]
74
+ return found
75
+
76
+
77
+ @functools.lru_cache(maxsize=None)
78
+ def cli_version(cli: str | None = None) -> tuple[int, ...]:
79
+ """The version of the `laylight` command, as numbers."""
80
+ command = cli or find_cli()
81
+ result = subprocess.run([command, "--version"], capture_output=True, text=True)
82
+ match = re.search(r"(\d+)\.(\d+)\.(\d+)", result.stdout)
83
+ if result.returncode != 0 or match is None:
84
+ raise CompilerError(f"`{command} --version` failed: {result.stderr.strip() or result.stdout.strip()}")
85
+ return tuple(int(part) for part in match.groups())
86
+
87
+
88
+ def run(args: Sequence[str], *, cli: str | None = None) -> subprocess.CompletedProcess[str]:
89
+ """Runs `laylight <args>` and returns the completed process; a failure
90
+ raises `CompilerError` with the command's messages."""
91
+ command = cli or find_cli()
92
+ version = cli_version(command)
93
+ if version < MIN_VERSION:
94
+ needed = ".".join(str(v) for v in MIN_VERSION)
95
+ found = ".".join(str(v) for v in version)
96
+ raise CompilerError(
97
+ f"laylight {found} at `{command}` cannot compile IR; laylight-cli {needed} or later is needed"
98
+ )
99
+ result = subprocess.run([command, *args], capture_output=True, text=True)
100
+ if result.returncode != 0:
101
+ message = result.stderr.strip() or result.stdout.strip()
102
+ raise CompilerError(f"laylight {' '.join(args[:1])} failed:\n{message}")
103
+ for line in result.stderr.splitlines():
104
+ if line.startswith("warning: "):
105
+ warnings.warn(line[len("warning: "):], CompilerWarning, stacklevel=3)
106
+ return result
107
+
108
+
109
+ def build(
110
+ ir_text: str,
111
+ output: "str | os.PathLike[str]",
112
+ *,
113
+ dummy: Sequence[str] = (),
114
+ cli: str | None = None,
115
+ ) -> Path:
116
+ """Compiles IR text to `output` (GDSII, or IR for `.lyir`), with optional
117
+ dummy fill rules (`--dummy`)."""
118
+ target = Path(output).resolve()
119
+ with tempfile.TemporaryDirectory(prefix="laylight-") as tmp:
120
+ source = Path(tmp) / "layout.lyir"
121
+ source.write_text(ir_text, encoding="utf-8")
122
+ args = ["build", str(source), "-o", str(target)]
123
+ for rule in dummy:
124
+ args += ["--dummy", rule]
125
+ run(args, cli=cli)
126
+ return target
127
+
128
+
129
+ def to_ir(path: "str | os.PathLike[str]", *, cli: str | None = None) -> str:
130
+ """The IR text of a layout file (GDSII, IR or Laylight source)."""
131
+ return run(["ir", str(Path(path).resolve())], cli=cli).stdout
132
+
133
+
134
+ __all__ = [
135
+ "CompilerError",
136
+ "CompilerWarning",
137
+ "MIN_VERSION",
138
+ "build",
139
+ "cli_version",
140
+ "find_cli",
141
+ "run",
142
+ "to_ir",
143
+ ]
@@ -0,0 +1,157 @@
1
+ """The 5x7 pixel font of the `text` primitive, shared with the Laylight DSL
2
+ (`crates/laylight-dsl/src/geom/font.rs`): printable ASCII.
3
+
4
+ Each glyph lists its pixel rows from the top, `#` for a filled pixel: seven
5
+ rows above the baseline, then optionally two descender rows.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ # Rows above the baseline (the height of a capital letter).
11
+ CAP_ROWS = 7
12
+ # Horizontal distance between characters, in pixels.
13
+ ADVANCE = 6
14
+ # Vertical distance between lines, in pixels.
15
+ LINE_PITCH = 11
16
+
17
+
18
+ def glyph(c: str) -> str | None:
19
+ index = ord(c) - ord(" ")
20
+ if 0 <= index < len(GLYPHS):
21
+ return GLYPHS[index]
22
+ return None
23
+
24
+
25
+ def boxes(glyph: str) -> list[tuple[int, int, int, int]]:
26
+ """The pixels of a glyph as few rectangles `(column, row, columns, rows)`,
27
+ `row` counted from the top: horizontal runs, merged with identical runs
28
+ in the rows below."""
29
+ open_boxes: list[tuple[int, int, int, int]] = []
30
+ done: list[tuple[int, int, int, int]] = []
31
+ for row, pixels in enumerate(glyph.split("/")):
32
+ row_runs = _runs(pixels)
33
+ following: list[tuple[int, int, int, int]] = []
34
+ for column, top, columns, rows in open_boxes:
35
+ if (column, columns) in row_runs:
36
+ following.append((column, top, columns, rows + 1))
37
+ else:
38
+ done.append((column, top, columns, rows))
39
+ for column, columns in row_runs:
40
+ if not any(b[0] == column and b[2] == columns for b in following):
41
+ following.append((column, row, columns, 1))
42
+ open_boxes = following
43
+ done.extend(open_boxes)
44
+ return done
45
+
46
+
47
+ def _runs(pixels: str) -> list[tuple[int, int]]:
48
+ """`(start, length)` of each run of filled pixels."""
49
+ out: list[tuple[int, int]] = []
50
+ start = None
51
+ for i, p in enumerate(pixels + "."):
52
+ if p == "#" and start is None:
53
+ start = i
54
+ elif p != "#" and start is not None:
55
+ out.append((start, i - start))
56
+ start = None
57
+ return out
58
+
59
+
60
+ # Glyphs for U+0020 to U+007E.
61
+ GLYPHS = (
62
+ "...../...../...../...../...../...../.....", # space
63
+ "..#../..#../..#../..#../..#../...../..#..", # !
64
+ ".#.#./.#.#./.#.#./...../...../...../.....", # "
65
+ ".#.#./.#.#./#####/.#.#./#####/.#.#./.#.#.", # #
66
+ "..#../.####/#.#../.###./..#.#/####./..#..", # $
67
+ "##.../##..#/...#./..#../.#.../#..##/...##", # %
68
+ ".##../#..#./#.#../.#.../#.#.#/#..#./.##.#", # &
69
+ "..#../..#../...../...../...../...../.....", # '
70
+ "...#./..#../.#.../.#.../.#.../..#../...#.", # (
71
+ ".#.../..#../...#./...#./...#./..#../.#...", # )
72
+ "...../..#../#.#.#/.###./#.#.#/..#../.....", # *
73
+ "...../..#../..#../#####/..#../..#../.....", # +
74
+ "...../...../...../...../...../.##../.##../..#../.#...", # ,
75
+ "...../...../...../#####/...../...../.....", # -
76
+ "...../...../...../...../...../.##../.##..", # .
77
+ "...../....#/...#./..#../.#.../#..../.....", # /
78
+ ".###./#...#/#..##/#.#.#/##..#/#...#/.###.", # 0
79
+ "..#../.##../..#../..#../..#../..#../.###.", # 1
80
+ ".###./#...#/....#/...#./..#../.#.../#####", # 2
81
+ "#####/...#./..#../...#./....#/#...#/.###.", # 3
82
+ "...#./..##./.#.#./#..#./#####/...#./...#.", # 4
83
+ "#####/#..../####./....#/....#/#...#/.###.", # 5
84
+ "..##./.#.../#..../####./#...#/#...#/.###.", # 6
85
+ "#####/....#/...#./..#../.#.../.#.../.#...", # 7
86
+ ".###./#...#/#...#/.###./#...#/#...#/.###.", # 8
87
+ ".###./#...#/#...#/.####/....#/...#./.##..", # 9
88
+ "...../.##../.##../...../...../.##../.##..", # :
89
+ "...../.##../.##../...../...../.##../.##../..#../.#...", # ;
90
+ "...#./..#../.#.../#..../.#.../..#../...#.", # <
91
+ "...../...../#####/...../#####/...../.....", # =
92
+ ".#.../..#../...#./....#/...#./..#../.#...", # >
93
+ ".###./#...#/....#/...#./..#../...../..#..", # ?
94
+ ".###./#...#/....#/.##.#/#.#.#/#.#.#/.###.", # @
95
+ ".###./#...#/#...#/#####/#...#/#...#/#...#", # A
96
+ "####./#...#/#...#/####./#...#/#...#/####.", # B
97
+ ".###./#...#/#..../#..../#..../#...#/.###.", # C
98
+ "###../#..#./#...#/#...#/#...#/#..#./###..", # D
99
+ "#####/#..../#..../####./#..../#..../#####", # E
100
+ "#####/#..../#..../####./#..../#..../#....", # F
101
+ ".###./#...#/#..../#.###/#...#/#...#/.####", # G
102
+ "#...#/#...#/#...#/#####/#...#/#...#/#...#", # H
103
+ ".###./..#../..#../..#../..#../..#../.###.", # I
104
+ "..###/...#./...#./...#./...#./#..#./.##..", # J
105
+ "#...#/#..#./#.#../##.../#.#../#..#./#...#", # K
106
+ "#..../#..../#..../#..../#..../#..../#####", # L
107
+ "#...#/##.##/#.#.#/#.#.#/#...#/#...#/#...#", # M
108
+ "#...#/#...#/##..#/#.#.#/#..##/#...#/#...#", # N
109
+ ".###./#...#/#...#/#...#/#...#/#...#/.###.", # O
110
+ "####./#...#/#...#/####./#..../#..../#....", # P
111
+ ".###./#...#/#...#/#...#/#.#.#/#..#./.##.#", # Q
112
+ "####./#...#/#...#/####./#.#../#..#./#...#", # R
113
+ ".####/#..../#..../.###./....#/....#/####.", # S
114
+ "#####/..#../..#../..#../..#../..#../..#..", # T
115
+ "#...#/#...#/#...#/#...#/#...#/#...#/.###.", # U
116
+ "#...#/#...#/#...#/#...#/#...#/.#.#./..#..", # V
117
+ "#...#/#...#/#...#/#.#.#/#.#.#/#.#.#/.#.#.", # W
118
+ "#...#/#...#/.#.#./..#../.#.#./#...#/#...#", # X
119
+ "#...#/#...#/.#.#./..#../..#../..#../..#..", # Y
120
+ "#####/....#/...#./..#../.#.../#..../#####", # Z
121
+ ".###./.#.../.#.../.#.../.#.../.#.../.###.", # [
122
+ "...../#..../.#.../..#../...#./....#/.....", # backslash
123
+ ".###./...#./...#./...#./...#./...#./.###.", # ]
124
+ "..#../.#.#./#...#/...../...../...../.....", # ^
125
+ "...../...../...../...../...../...../#####", # _
126
+ ".#.../..#../...../...../...../...../.....", # `
127
+ "...../...../.###./....#/.####/#...#/.####", # a
128
+ "#..../#..../#.##./##..#/#...#/#...#/####.", # b
129
+ "...../...../.###./#..../#..../#...#/.###.", # c
130
+ "....#/....#/.##.#/#..##/#...#/#...#/.####", # d
131
+ "...../...../.###./#...#/#####/#..../.###.", # e
132
+ "..##./.#..#/.#.../###../.#.../.#.../.#...", # f
133
+ "...../...../.####/#...#/#...#/#...#/.####/....#/.###.", # g
134
+ "#..../#..../#.##./##..#/#...#/#...#/#...#", # h
135
+ "..#../...../.##../..#../..#../..#../.###.", # i
136
+ "...#./...../..##./...#./...#./...#./...#./#..#./.##..", # j
137
+ "#..../#..../#..#./#.#../##.../#.#../#..#.", # k
138
+ ".##../..#../..#../..#../..#../..#../.###.", # l
139
+ "...../...../##.#./#.#.#/#.#.#/#.#.#/#.#.#", # m
140
+ "...../...../#.##./##..#/#...#/#...#/#...#", # n
141
+ "...../...../.###./#...#/#...#/#...#/.###.", # o
142
+ "...../...../####./#...#/#...#/#...#/####./#..../#....", # p
143
+ "...../...../.####/#...#/#...#/#...#/.####/....#/....#", # q
144
+ "...../...../#.##./##..#/#..../#..../#....", # r
145
+ "...../...../.####/#..../.###./....#/####.", # s
146
+ ".#.../.#.../###../.#.../.#.../.#..#/..##.", # t
147
+ "...../...../#...#/#...#/#...#/#..##/.##.#", # u
148
+ "...../...../#...#/#...#/#...#/.#.#./..#..", # v
149
+ "...../...../#...#/#...#/#.#.#/#.#.#/.#.#.", # w
150
+ "...../...../#...#/.#.#./..#../.#.#./#...#", # x
151
+ "...../...../#...#/#...#/#...#/#...#/.####/....#/.###.", # y
152
+ "...../...../#####/...#./..#../.#.../#####", # z
153
+ "...#./..#../..#../.#.../..#../..#../...#.", # {
154
+ "..#../..#../..#../..#../..#../..#../..#..", # |
155
+ ".#.../..#../..#../...#./..#../..#../.#...", # }
156
+ "...../...../.#.../#.#.#/...#./...../.....", # ~
157
+ )