eulumdat-plot 0.0.1__tar.gz → 1.0.0__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.
- eulumdat_plot-1.0.0/PKG-INFO +238 -0
- eulumdat_plot-1.0.0/README.md +198 -0
- eulumdat_plot-1.0.0/pyproject.toml +73 -0
- eulumdat_plot-1.0.0/src/eulumdat_plot/__init__.py +50 -0
- eulumdat_plot-1.0.0/src/eulumdat_plot/export.py +145 -0
- eulumdat_plot-1.0.0/src/eulumdat_plot/plot.py +365 -0
- eulumdat_plot-1.0.0/src/eulumdat_plot/renderer.py +512 -0
- eulumdat_plot-1.0.0/src/eulumdat_plot.egg-info/PKG-INFO +238 -0
- {eulumdat_plot-0.0.1 → eulumdat_plot-1.0.0}/src/eulumdat_plot.egg-info/SOURCES.txt +6 -1
- eulumdat_plot-1.0.0/src/eulumdat_plot.egg-info/requires.txt +20 -0
- eulumdat_plot-1.0.0/tests/test_scaling.py +195 -0
- eulumdat_plot-1.0.0/tests/test_smoke.py +142 -0
- eulumdat_plot-0.0.1/PKG-INFO +0 -31
- eulumdat_plot-0.0.1/README.md +0 -11
- eulumdat_plot-0.0.1/pyproject.toml +0 -32
- eulumdat_plot-0.0.1/src/eulumdat_plot/__init__.py +0 -1
- eulumdat_plot-0.0.1/src/eulumdat_plot.egg-info/PKG-INFO +0 -31
- eulumdat_plot-0.0.1/src/eulumdat_plot.egg-info/requires.txt +0 -5
- {eulumdat_plot-0.0.1 → eulumdat_plot-1.0.0}/setup.cfg +0 -0
- {eulumdat_plot-0.0.1 → eulumdat_plot-1.0.0}/src/eulumdat_plot.egg-info/dependency_links.txt +0 -0
- {eulumdat_plot-0.0.1 → eulumdat_plot-1.0.0}/src/eulumdat_plot.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eulumdat-plot
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Photometric polar diagram generator for EULUMDAT (.ldt) files — extension to eulumdat-py
|
|
5
|
+
Author: 123VincentB
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/123VincentB/eulumdat-plot
|
|
8
|
+
Project-URL: Repository, https://github.com/123VincentB/eulumdat-plot
|
|
9
|
+
Project-URL: Issues, https://github.com/123VincentB/eulumdat-plot/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/123VincentB/eulumdat-plot/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: eulumdat,ldt,photometry,lighting,luminaire,polar,candela,diagram,svg,lumtopic
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Manufacturing
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: eulumdat-py>=1.0.0
|
|
25
|
+
Requires-Dist: numpy>=1.21
|
|
26
|
+
Requires-Dist: svgwrite>=1.4
|
|
27
|
+
Provides-Extra: export
|
|
28
|
+
Requires-Dist: vl-convert-python>=1.6; extra == "export"
|
|
29
|
+
Requires-Dist: Pillow>=9.0; extra == "export"
|
|
30
|
+
Provides-Extra: cubic
|
|
31
|
+
Requires-Dist: scipy>=1.7; extra == "cubic"
|
|
32
|
+
Provides-Extra: full
|
|
33
|
+
Requires-Dist: eulumdat-plot[export]; extra == "full"
|
|
34
|
+
Requires-Dist: eulumdat-plot[cubic]; extra == "full"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: build; extra == "dev"
|
|
37
|
+
Requires-Dist: twine; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
39
|
+
Requires-Dist: eulumdat-plot[full]; extra == "dev"
|
|
40
|
+
|
|
41
|
+
# eulumdat-plot
|
|
42
|
+
|
|
43
|
+
Photometric polar diagram generator for EULUMDAT (`.ldt`) files —
|
|
44
|
+
designed for **product datasheets and publication-ready documents**.
|
|
45
|
+
|
|
46
|
+
Reads a `.ldt` file and produces a **Lumtopic-style SVG**: a square image
|
|
47
|
+
with a top banner and a polar candela distribution diagram showing the
|
|
48
|
+
C0/C180 (solid) and C90/C270 (dotted) curves, scaled to fill the plot area.
|
|
49
|
+
|
|
50
|
+
> **For scientific / interactive plots** (matplotlib, axis labels, legends),
|
|
51
|
+
> see the
|
|
52
|
+
> [eulumdat-py examples](https://github.com/123VincentB/eulumdat-py/blob/main/examples/02_polar_diagram.md).
|
|
53
|
+
|
|
54
|
+
Part of the [`eulumdat-*`](https://github.com/123VincentB) ecosystem, built
|
|
55
|
+
on top of [`eulumdat-py`](https://pypi.org/project/eulumdat-py/).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- Reads any EULUMDAT file — all symmetry types (ISYM 0–4) handled by `eulumdat-py`
|
|
66
|
+
- Generates a **publication-ready SVG** polar diagram (Lumtopic style)
|
|
67
|
+
- Dynamic radial scale (3–6 concentric circles, round values)
|
|
68
|
+
- Dominant-hemisphere detection for automatic scale label placement
|
|
69
|
+
- Proportional scaling via `Layout.for_size(n)` — one parameter controls everything
|
|
70
|
+
- Optional I(γ) interpolation (linear or cubic spline) for smooth curves
|
|
71
|
+
- Optional raster export to **PNG** and **JPEG** (cross-platform, no native DLL)
|
|
72
|
+
- Debug mode for visual validation of C-plane assignment
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Core package (SVG generation only):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install eulumdat-plot
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
With raster export (PNG / JPEG):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install "eulumdat-plot[export]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
With cubic spline interpolation:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install "eulumdat-plot[cubic]"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Everything:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install "eulumdat-plot[full]"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quick start
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from eulumdat_plot import plot_ldt
|
|
104
|
+
|
|
105
|
+
# Generate an SVG next to the source file
|
|
106
|
+
svg = plot_ldt("luminaire.ldt")
|
|
107
|
+
|
|
108
|
+
# With a distribution code in the banner centre
|
|
109
|
+
svg = plot_ldt("luminaire.ldt", code="D53")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Scaling
|
|
113
|
+
|
|
114
|
+
All visual parameters (stroke widths, font sizes, margins) scale
|
|
115
|
+
proportionally from the 1181 px reference with a single call:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from eulumdat_plot import plot_ldt, Layout
|
|
119
|
+
|
|
120
|
+
svg = plot_ldt("luminaire.ldt", layout=Layout.for_size(600))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Raster export
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from eulumdat_plot import plot_ldt, Layout
|
|
127
|
+
from eulumdat_plot.export import svg_to_png, svg_to_jpg
|
|
128
|
+
|
|
129
|
+
svg = plot_ldt("luminaire.ldt", layout=Layout.for_size(1181))
|
|
130
|
+
png = svg_to_png(svg, size_px=600)
|
|
131
|
+
jpg = svg_to_jpg(svg, size_px=600, quality=95)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The export size is independent of the SVG canvas size.
|
|
135
|
+
|
|
136
|
+
## API reference
|
|
137
|
+
|
|
138
|
+
### `plot_ldt()`
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
def plot_ldt(
|
|
142
|
+
ldt_path: str | Path,
|
|
143
|
+
svg_path: str | Path | None = None,
|
|
144
|
+
*,
|
|
145
|
+
code: str = "",
|
|
146
|
+
layout: Layout | None = None,
|
|
147
|
+
interpolate: bool = True,
|
|
148
|
+
interp_step_deg: float = 1.0,
|
|
149
|
+
interp_method: str = "linear",
|
|
150
|
+
debug: bool = False,
|
|
151
|
+
) -> Path
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Parameter | Default | Description |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `ldt_path` | — | Source `.ldt` file |
|
|
157
|
+
| `svg_path` | same name, `.svg` | Output SVG path |
|
|
158
|
+
| `code` | `""` | Distribution code shown in the banner centre |
|
|
159
|
+
| `layout` | `Layout()` | Visual parameters |
|
|
160
|
+
| `interpolate` | `True` | Resample I(γ) before plotting |
|
|
161
|
+
| `interp_step_deg` | `1.0` | Angular step for resampling (degrees) |
|
|
162
|
+
| `interp_method` | `"linear"` | `"linear"` or `"cubic"` (requires scipy) |
|
|
163
|
+
| `debug` | `False` | Colour-code C-planes for visual validation |
|
|
164
|
+
|
|
165
|
+
### `Layout.for_size()`
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
Layout.for_size(size_px: int) -> Layout
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Creates a `Layout` with all dimensions scaled proportionally from the
|
|
172
|
+
1181 px reference. `Layout.for_size(1181)` is identical to `Layout()`.
|
|
173
|
+
|
|
174
|
+
### `svg_to_png()` / `svg_to_jpg()`
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
svg_to_png(svg_path, png_path=None, *, size_px=1181, background="#FFFFFF") -> Path
|
|
178
|
+
svg_to_jpg(svg_path, jpg_path=None, *, size_px=1181, background="#FFFFFF", quality=95) -> Path
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Requires `pip install "eulumdat-plot[export]"`.
|
|
182
|
+
|
|
183
|
+
## Examples
|
|
184
|
+
|
|
185
|
+
| File | Description |
|
|
186
|
+
|---|---|
|
|
187
|
+
| [`examples/01_basic_usage.md`](examples/01_basic_usage.md) | Generate an SVG from a `.ldt` file |
|
|
188
|
+
| [`examples/02_resize_and_export.md`](examples/02_resize_and_export.md) | Scaling, raster export, batch processing |
|
|
189
|
+
|
|
190
|
+
## Project structure
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
eulumdat-plot/
|
|
194
|
+
├── data/
|
|
195
|
+
│ ├── input/ # sample .ldt files (ISYM 0–4)
|
|
196
|
+
│ └── output/ # generated SVG / PNG / JPEG
|
|
197
|
+
├── docs/
|
|
198
|
+
│ └── img/
|
|
199
|
+
│ └── sample_01.svg
|
|
200
|
+
├── examples/
|
|
201
|
+
│ ├── 01_basic_usage.md
|
|
202
|
+
│ └── 02_resize_and_export.md
|
|
203
|
+
├── src/
|
|
204
|
+
│ └── eulumdat_plot/
|
|
205
|
+
│ ├── __init__.py
|
|
206
|
+
│ ├── plot.py # public API — LDT → SVG pipeline
|
|
207
|
+
│ ├── renderer.py # SVG renderer + Layout dataclass
|
|
208
|
+
│ └── export.py # raster export (PNG / JPEG)
|
|
209
|
+
├── tests/
|
|
210
|
+
│ ├── test_smoke.py # 46 real LDT files, all ISYM types
|
|
211
|
+
│ └── test_scaling.py # Layout.for_size() proportionality
|
|
212
|
+
├── pyproject.toml
|
|
213
|
+
├── CHANGELOG.md
|
|
214
|
+
└── README.md
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## EULUMDAT ecosystem
|
|
218
|
+
|
|
219
|
+
| Package | Status | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| [`eulumdat-py`](https://pypi.org/project/eulumdat-py/) | v0.1.4 | Read / write EULUMDAT files |
|
|
222
|
+
| [`eulumdat-symmetry`](https://pypi.org/project/eulumdat-symmetry/) | v1.0.0 | Symmetrise EULUMDAT files |
|
|
223
|
+
| `eulumdat-plot` | v1.0.0 | Photometric polar diagram — **this package** |
|
|
224
|
+
| `eulumdat-luminance` | planned | Luminance table cd/m² (γ 55°–85°) |
|
|
225
|
+
| `eulumdat-ugr` | planned | UGR calculation (CIE 117, CIE 190) |
|
|
226
|
+
|
|
227
|
+
## Requirements
|
|
228
|
+
|
|
229
|
+
- Python ≥ 3.9
|
|
230
|
+
- `eulumdat-py` ≥ 1.0.0
|
|
231
|
+
- `numpy` ≥ 1.21
|
|
232
|
+
- `svgwrite` ≥ 1.4
|
|
233
|
+
- *(optional)* `vl-convert-python` ≥ 1.6 + `Pillow` ≥ 9.0 — raster export
|
|
234
|
+
- *(optional)* `scipy` ≥ 1.7 — cubic spline interpolation
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT — © 2024 [123VincentB](https://github.com/123VincentB)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# eulumdat-plot
|
|
2
|
+
|
|
3
|
+
Photometric polar diagram generator for EULUMDAT (`.ldt`) files —
|
|
4
|
+
designed for **product datasheets and publication-ready documents**.
|
|
5
|
+
|
|
6
|
+
Reads a `.ldt` file and produces a **Lumtopic-style SVG**: a square image
|
|
7
|
+
with a top banner and a polar candela distribution diagram showing the
|
|
8
|
+
C0/C180 (solid) and C90/C270 (dotted) curves, scaled to fill the plot area.
|
|
9
|
+
|
|
10
|
+
> **For scientific / interactive plots** (matplotlib, axis labels, legends),
|
|
11
|
+
> see the
|
|
12
|
+
> [eulumdat-py examples](https://github.com/123VincentB/eulumdat-py/blob/main/examples/02_polar_diagram.md).
|
|
13
|
+
|
|
14
|
+
Part of the [`eulumdat-*`](https://github.com/123VincentB) ecosystem, built
|
|
15
|
+
on top of [`eulumdat-py`](https://pypi.org/project/eulumdat-py/).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Reads any EULUMDAT file — all symmetry types (ISYM 0–4) handled by `eulumdat-py`
|
|
26
|
+
- Generates a **publication-ready SVG** polar diagram (Lumtopic style)
|
|
27
|
+
- Dynamic radial scale (3–6 concentric circles, round values)
|
|
28
|
+
- Dominant-hemisphere detection for automatic scale label placement
|
|
29
|
+
- Proportional scaling via `Layout.for_size(n)` — one parameter controls everything
|
|
30
|
+
- Optional I(γ) interpolation (linear or cubic spline) for smooth curves
|
|
31
|
+
- Optional raster export to **PNG** and **JPEG** (cross-platform, no native DLL)
|
|
32
|
+
- Debug mode for visual validation of C-plane assignment
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Core package (SVG generation only):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install eulumdat-plot
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With raster export (PNG / JPEG):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install "eulumdat-plot[export]"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With cubic spline interpolation:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "eulumdat-plot[cubic]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Everything:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "eulumdat-plot[full]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from eulumdat_plot import plot_ldt
|
|
64
|
+
|
|
65
|
+
# Generate an SVG next to the source file
|
|
66
|
+
svg = plot_ldt("luminaire.ldt")
|
|
67
|
+
|
|
68
|
+
# With a distribution code in the banner centre
|
|
69
|
+
svg = plot_ldt("luminaire.ldt", code="D53")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Scaling
|
|
73
|
+
|
|
74
|
+
All visual parameters (stroke widths, font sizes, margins) scale
|
|
75
|
+
proportionally from the 1181 px reference with a single call:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from eulumdat_plot import plot_ldt, Layout
|
|
79
|
+
|
|
80
|
+
svg = plot_ldt("luminaire.ldt", layout=Layout.for_size(600))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Raster export
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from eulumdat_plot import plot_ldt, Layout
|
|
87
|
+
from eulumdat_plot.export import svg_to_png, svg_to_jpg
|
|
88
|
+
|
|
89
|
+
svg = plot_ldt("luminaire.ldt", layout=Layout.for_size(1181))
|
|
90
|
+
png = svg_to_png(svg, size_px=600)
|
|
91
|
+
jpg = svg_to_jpg(svg, size_px=600, quality=95)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The export size is independent of the SVG canvas size.
|
|
95
|
+
|
|
96
|
+
## API reference
|
|
97
|
+
|
|
98
|
+
### `plot_ldt()`
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
def plot_ldt(
|
|
102
|
+
ldt_path: str | Path,
|
|
103
|
+
svg_path: str | Path | None = None,
|
|
104
|
+
*,
|
|
105
|
+
code: str = "",
|
|
106
|
+
layout: Layout | None = None,
|
|
107
|
+
interpolate: bool = True,
|
|
108
|
+
interp_step_deg: float = 1.0,
|
|
109
|
+
interp_method: str = "linear",
|
|
110
|
+
debug: bool = False,
|
|
111
|
+
) -> Path
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Parameter | Default | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `ldt_path` | — | Source `.ldt` file |
|
|
117
|
+
| `svg_path` | same name, `.svg` | Output SVG path |
|
|
118
|
+
| `code` | `""` | Distribution code shown in the banner centre |
|
|
119
|
+
| `layout` | `Layout()` | Visual parameters |
|
|
120
|
+
| `interpolate` | `True` | Resample I(γ) before plotting |
|
|
121
|
+
| `interp_step_deg` | `1.0` | Angular step for resampling (degrees) |
|
|
122
|
+
| `interp_method` | `"linear"` | `"linear"` or `"cubic"` (requires scipy) |
|
|
123
|
+
| `debug` | `False` | Colour-code C-planes for visual validation |
|
|
124
|
+
|
|
125
|
+
### `Layout.for_size()`
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
Layout.for_size(size_px: int) -> Layout
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Creates a `Layout` with all dimensions scaled proportionally from the
|
|
132
|
+
1181 px reference. `Layout.for_size(1181)` is identical to `Layout()`.
|
|
133
|
+
|
|
134
|
+
### `svg_to_png()` / `svg_to_jpg()`
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
svg_to_png(svg_path, png_path=None, *, size_px=1181, background="#FFFFFF") -> Path
|
|
138
|
+
svg_to_jpg(svg_path, jpg_path=None, *, size_px=1181, background="#FFFFFF", quality=95) -> Path
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Requires `pip install "eulumdat-plot[export]"`.
|
|
142
|
+
|
|
143
|
+
## Examples
|
|
144
|
+
|
|
145
|
+
| File | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| [`examples/01_basic_usage.md`](examples/01_basic_usage.md) | Generate an SVG from a `.ldt` file |
|
|
148
|
+
| [`examples/02_resize_and_export.md`](examples/02_resize_and_export.md) | Scaling, raster export, batch processing |
|
|
149
|
+
|
|
150
|
+
## Project structure
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
eulumdat-plot/
|
|
154
|
+
├── data/
|
|
155
|
+
│ ├── input/ # sample .ldt files (ISYM 0–4)
|
|
156
|
+
│ └── output/ # generated SVG / PNG / JPEG
|
|
157
|
+
├── docs/
|
|
158
|
+
│ └── img/
|
|
159
|
+
│ └── sample_01.svg
|
|
160
|
+
├── examples/
|
|
161
|
+
│ ├── 01_basic_usage.md
|
|
162
|
+
│ └── 02_resize_and_export.md
|
|
163
|
+
├── src/
|
|
164
|
+
│ └── eulumdat_plot/
|
|
165
|
+
│ ├── __init__.py
|
|
166
|
+
│ ├── plot.py # public API — LDT → SVG pipeline
|
|
167
|
+
│ ├── renderer.py # SVG renderer + Layout dataclass
|
|
168
|
+
│ └── export.py # raster export (PNG / JPEG)
|
|
169
|
+
├── tests/
|
|
170
|
+
│ ├── test_smoke.py # 46 real LDT files, all ISYM types
|
|
171
|
+
│ └── test_scaling.py # Layout.for_size() proportionality
|
|
172
|
+
├── pyproject.toml
|
|
173
|
+
├── CHANGELOG.md
|
|
174
|
+
└── README.md
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## EULUMDAT ecosystem
|
|
178
|
+
|
|
179
|
+
| Package | Status | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| [`eulumdat-py`](https://pypi.org/project/eulumdat-py/) | v0.1.4 | Read / write EULUMDAT files |
|
|
182
|
+
| [`eulumdat-symmetry`](https://pypi.org/project/eulumdat-symmetry/) | v1.0.0 | Symmetrise EULUMDAT files |
|
|
183
|
+
| `eulumdat-plot` | v1.0.0 | Photometric polar diagram — **this package** |
|
|
184
|
+
| `eulumdat-luminance` | planned | Luminance table cd/m² (γ 55°–85°) |
|
|
185
|
+
| `eulumdat-ugr` | planned | UGR calculation (CIE 117, CIE 190) |
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- Python ≥ 3.9
|
|
190
|
+
- `eulumdat-py` ≥ 1.0.0
|
|
191
|
+
- `numpy` ≥ 1.21
|
|
192
|
+
- `svgwrite` ≥ 1.4
|
|
193
|
+
- *(optional)* `vl-convert-python` ≥ 1.6 + `Pillow` ≥ 9.0 — raster export
|
|
194
|
+
- *(optional)* `scipy` ≥ 1.7 — cubic spline interpolation
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT — © 2024 [123VincentB](https://github.com/123VincentB)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "eulumdat-plot"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Photometric polar diagram generator for EULUMDAT (.ldt) files — extension to eulumdat-py"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "123VincentB" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
"eulumdat-py >= 1.0.0",
|
|
16
|
+
"numpy >= 1.21",
|
|
17
|
+
"svgwrite >= 1.4",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
keywords = [
|
|
21
|
+
"eulumdat", "ldt", "photometry", "lighting", "luminaire",
|
|
22
|
+
"polar", "candela", "diagram", "svg", "lumtopic",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 5 - Production/Stable",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"Intended Audience :: Manufacturing",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.9",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
# Raster export (SVG → PNG / JPEG)
|
|
40
|
+
export = [
|
|
41
|
+
"vl-convert-python >= 1.6",
|
|
42
|
+
"Pillow >= 9.0",
|
|
43
|
+
]
|
|
44
|
+
# Smooth curve interpolation
|
|
45
|
+
cubic = [
|
|
46
|
+
"scipy >= 1.7",
|
|
47
|
+
]
|
|
48
|
+
# Everything
|
|
49
|
+
full = [
|
|
50
|
+
"eulumdat-plot[export]",
|
|
51
|
+
"eulumdat-plot[cubic]",
|
|
52
|
+
]
|
|
53
|
+
# Development tools
|
|
54
|
+
dev = [
|
|
55
|
+
"build",
|
|
56
|
+
"twine",
|
|
57
|
+
"pytest >= 7.0",
|
|
58
|
+
"eulumdat-plot[full]",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/123VincentB/eulumdat-plot"
|
|
63
|
+
Repository = "https://github.com/123VincentB/eulumdat-plot"
|
|
64
|
+
Issues = "https://github.com/123VincentB/eulumdat-plot/issues"
|
|
65
|
+
Changelog = "https://github.com/123VincentB/eulumdat-plot/blob/main/CHANGELOG.md"
|
|
66
|
+
|
|
67
|
+
[tool.setuptools.packages.find]
|
|
68
|
+
where = ["src"]
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
# Add src/ to sys.path so pytest finds eulumdat_plot without editable install.
|
|
72
|
+
pythonpath = ["src"]
|
|
73
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
eulumdat-plot — Photometric polar diagram generator for EULUMDAT (.ldt) files.
|
|
3
|
+
|
|
4
|
+
This package is an extension of ``eulumdat-py`` (``pyldt``).
|
|
5
|
+
It reads a ``.ldt`` file and generates a photometric polar diagram in the
|
|
6
|
+
style of the Lumtopic software: a square SVG image with a top banner
|
|
7
|
+
("LED" / distribution code / "cd / klm") and a polar plot showing the
|
|
8
|
+
C0/C180 distribution (solid curve) and C90/C270 distribution (dotted curve).
|
|
9
|
+
|
|
10
|
+
Quick start
|
|
11
|
+
-----------
|
|
12
|
+
::
|
|
13
|
+
|
|
14
|
+
from eulumdat_plot import plot_ldt
|
|
15
|
+
|
|
16
|
+
# Minimal — outputs "luminaire.svg" next to the source file.
|
|
17
|
+
svg = plot_ldt("luminaire.ldt")
|
|
18
|
+
|
|
19
|
+
# With distribution code and custom canvas size.
|
|
20
|
+
from eulumdat_plot import Layout
|
|
21
|
+
layout = Layout(width=800, height=800)
|
|
22
|
+
svg = plot_ldt("luminaire.ldt", code="D53", layout=layout)
|
|
23
|
+
|
|
24
|
+
# Raster export (requires the ``[export]`` optional dependency).
|
|
25
|
+
from eulumdat_plot.export import svg_to_png, svg_to_jpg
|
|
26
|
+
png = svg_to_png(svg)
|
|
27
|
+
jpg = svg_to_jpg(svg)
|
|
28
|
+
|
|
29
|
+
Public API
|
|
30
|
+
----------
|
|
31
|
+
:func:`plot_ldt`
|
|
32
|
+
Main entry point: LDT file → SVG diagram.
|
|
33
|
+
:class:`Layout`
|
|
34
|
+
Dataclass holding all visual parameters (sizes, stroke widths, fonts…).
|
|
35
|
+
:func:`make_svg`
|
|
36
|
+
Low-level renderer: pre-computed NAT curves → SVG.
|
|
37
|
+
Useful if you need to build curves yourself and bypass the LDT pipeline.
|
|
38
|
+
:func:`polar_to_nat`
|
|
39
|
+
Convert polar ``(r, θ)`` to NAT ``(x, y)`` Cartesian coordinates.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from .plot import plot_ldt
|
|
43
|
+
from .renderer import Layout, make_svg, polar_to_nat
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"plot_ldt",
|
|
47
|
+
"Layout",
|
|
48
|
+
"make_svg",
|
|
49
|
+
"polar_to_nat",
|
|
50
|
+
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
export.py — Raster export of photometric SVG diagrams.
|
|
3
|
+
|
|
4
|
+
Converts SVG files produced by :func:`plot.plot_ldt` (or any compatible
|
|
5
|
+
SVG) to PNG or JPEG.
|
|
6
|
+
|
|
7
|
+
Dependencies
|
|
8
|
+
------------
|
|
9
|
+
``vl-convert-python``
|
|
10
|
+
Pure-Python package embedding a compiled Rust/resvg SVG renderer.
|
|
11
|
+
No native library (DLL/SO) required — the renderer is bundled in the
|
|
12
|
+
wheel. Cross-platform: Windows, Linux, macOS.
|
|
13
|
+
Install with the optional extra::
|
|
14
|
+
|
|
15
|
+
pip install "eulumdat-plot[export]"
|
|
16
|
+
|
|
17
|
+
``Pillow``
|
|
18
|
+
Used for pixel-exact resizing and JPEG compression.
|
|
19
|
+
Also installed via ``[export]``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import io
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check_deps() -> None:
|
|
29
|
+
"""Raise a clear ImportError if vl-convert-python or Pillow are missing."""
|
|
30
|
+
missing = []
|
|
31
|
+
for pkg, pip_name in [("vl_convert", "vl-convert-python"), ("PIL", "Pillow")]:
|
|
32
|
+
try:
|
|
33
|
+
__import__(pkg)
|
|
34
|
+
except ImportError:
|
|
35
|
+
missing.append(pip_name)
|
|
36
|
+
if missing:
|
|
37
|
+
raise ImportError(
|
|
38
|
+
f"Missing package(s) for raster export: {', '.join(missing)}\n"
|
|
39
|
+
"Install with: pip install 'eulumdat-plot[export]'"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _svg_to_pil(svg_path: Path, size_px: int, background: str):
|
|
44
|
+
"""
|
|
45
|
+
Convert an SVG file to a square Pillow Image of exactly *size_px* pixels.
|
|
46
|
+
|
|
47
|
+
Uses ``vl_convert`` (resvg, compiled Rust, no native DLL) for rendering,
|
|
48
|
+
then ``Pillow`` for pixel-exact resizing and background compositing.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
svg_path : Path to the source SVG file.
|
|
53
|
+
size_px : Target output size in pixels (square).
|
|
54
|
+
background : Background colour as a CSS hex string (e.g. ``"#FFFFFF"``).
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
PIL.Image.Image in RGB mode, size (size_px, size_px).
|
|
59
|
+
"""
|
|
60
|
+
_check_deps()
|
|
61
|
+
|
|
62
|
+
import vl_convert as vlc
|
|
63
|
+
from PIL import Image
|
|
64
|
+
|
|
65
|
+
svg_content = svg_path.read_text(encoding="utf-8")
|
|
66
|
+
|
|
67
|
+
# Read the SVG canvas size from the file to compute the scale factor.
|
|
68
|
+
# Layout always writes width="N" height="N" as integers.
|
|
69
|
+
native_size = size_px # fallback if parsing fails
|
|
70
|
+
import re
|
|
71
|
+
m = re.search(r'<svg[^>]+width="(\d+)"', svg_content)
|
|
72
|
+
if m:
|
|
73
|
+
native_size = int(m.group(1))
|
|
74
|
+
|
|
75
|
+
scale = size_px / native_size
|
|
76
|
+
png_bytes = vlc.svg_to_png(svg_content, scale=scale)
|
|
77
|
+
|
|
78
|
+
img = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
|
79
|
+
|
|
80
|
+
# Resize to exactly size_px × size_px (float scale may be off by 1 px).
|
|
81
|
+
if img.size != (size_px, size_px):
|
|
82
|
+
img = img.resize((size_px, size_px), Image.LANCZOS)
|
|
83
|
+
|
|
84
|
+
# Composite over solid background.
|
|
85
|
+
bg = Image.new("RGBA", (size_px, size_px), background)
|
|
86
|
+
bg.paste(img, mask=img)
|
|
87
|
+
return bg.convert("RGB")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def svg_to_png(
|
|
91
|
+
svg_path: str | Path,
|
|
92
|
+
png_path: str | Path | None = None,
|
|
93
|
+
*,
|
|
94
|
+
size_px: int = 1181,
|
|
95
|
+
background: str = "#FFFFFF",
|
|
96
|
+
) -> Path:
|
|
97
|
+
"""
|
|
98
|
+
Convert an SVG file to PNG.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
svg_path : Path to the source SVG file.
|
|
103
|
+
png_path : Destination path. Defaults to svg_path with .png extension.
|
|
104
|
+
size_px : Output width and height in pixels (square output).
|
|
105
|
+
background : Background fill colour as a CSS hex string. Default: #FFFFFF.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
Absolute path to the generated PNG file.
|
|
110
|
+
"""
|
|
111
|
+
svg_path = Path(svg_path)
|
|
112
|
+
png_path = Path(png_path) if png_path is not None else svg_path.with_suffix(".png")
|
|
113
|
+
img = _svg_to_pil(svg_path, size_px, background)
|
|
114
|
+
img.save(png_path, "PNG", optimize=True)
|
|
115
|
+
return png_path.resolve()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def svg_to_jpg(
|
|
119
|
+
svg_path: str | Path,
|
|
120
|
+
jpg_path: str | Path | None = None,
|
|
121
|
+
*,
|
|
122
|
+
size_px: int = 1181,
|
|
123
|
+
background: str = "#FFFFFF",
|
|
124
|
+
quality: int = 95,
|
|
125
|
+
) -> Path:
|
|
126
|
+
"""
|
|
127
|
+
Convert an SVG file to JPEG.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
svg_path : Path to the source SVG file.
|
|
132
|
+
jpg_path : Destination path. Defaults to svg_path with .jpg extension.
|
|
133
|
+
size_px : Output width and height in pixels (square output).
|
|
134
|
+
background : Background fill colour (CSS hex). Default: #FFFFFF.
|
|
135
|
+
quality : JPEG compression quality (1-100). Default: 95.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
Absolute path to the generated JPEG file.
|
|
140
|
+
"""
|
|
141
|
+
svg_path = Path(svg_path)
|
|
142
|
+
jpg_path = Path(jpg_path) if jpg_path is not None else svg_path.with_suffix(".jpg")
|
|
143
|
+
img = _svg_to_pil(svg_path, size_px, background)
|
|
144
|
+
img.save(jpg_path, "JPEG", quality=quality, optimize=True)
|
|
145
|
+
return jpg_path.resolve()
|