ezdgn 0.1.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.
ezdgn-0.1.0/Cargo.lock ADDED
@@ -0,0 +1,320 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "bumpalo"
7
+ version = "3.20.3"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
10
+
11
+ [[package]]
12
+ name = "cfb"
13
+ version = "0.14.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
16
+ dependencies = [
17
+ "fnv",
18
+ "uuid",
19
+ "web-time",
20
+ ]
21
+
22
+ [[package]]
23
+ name = "cfg-if"
24
+ version = "1.0.4"
25
+ source = "registry+https://github.com/rust-lang/crates.io-index"
26
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
27
+
28
+ [[package]]
29
+ name = "ezdgn-core"
30
+ version = "0.1.0"
31
+ dependencies = [
32
+ "cfb",
33
+ "thiserror",
34
+ "uuid",
35
+ ]
36
+
37
+ [[package]]
38
+ name = "ezdgn-python"
39
+ version = "0.1.0"
40
+ dependencies = [
41
+ "ezdgn-core",
42
+ "pyo3",
43
+ ]
44
+
45
+ [[package]]
46
+ name = "fnv"
47
+ version = "1.0.7"
48
+ source = "registry+https://github.com/rust-lang/crates.io-index"
49
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
50
+
51
+ [[package]]
52
+ name = "futures-core"
53
+ version = "0.3.33"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
56
+
57
+ [[package]]
58
+ name = "futures-task"
59
+ version = "0.3.33"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
62
+
63
+ [[package]]
64
+ name = "futures-util"
65
+ version = "0.3.33"
66
+ source = "registry+https://github.com/rust-lang/crates.io-index"
67
+ checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
68
+ dependencies = [
69
+ "futures-core",
70
+ "futures-task",
71
+ "pin-project-lite",
72
+ "slab",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "heck"
77
+ version = "0.5.0"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
80
+
81
+ [[package]]
82
+ name = "js-sys"
83
+ version = "0.3.103"
84
+ source = "registry+https://github.com/rust-lang/crates.io-index"
85
+ checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
86
+ dependencies = [
87
+ "cfg-if",
88
+ "futures-util",
89
+ "wasm-bindgen",
90
+ ]
91
+
92
+ [[package]]
93
+ name = "libc"
94
+ version = "0.2.187"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "a7743783ea728ef5c31194c6590797eed286449b4a4e87d626d8a51f0a94e732"
97
+
98
+ [[package]]
99
+ name = "once_cell"
100
+ version = "1.21.4"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
103
+
104
+ [[package]]
105
+ name = "pin-project-lite"
106
+ version = "0.2.17"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
109
+
110
+ [[package]]
111
+ name = "portable-atomic"
112
+ version = "1.14.0"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
115
+
116
+ [[package]]
117
+ name = "proc-macro2"
118
+ version = "1.0.107"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
121
+ dependencies = [
122
+ "unicode-ident",
123
+ ]
124
+
125
+ [[package]]
126
+ name = "pyo3"
127
+ version = "0.29.0"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
130
+ dependencies = [
131
+ "libc",
132
+ "once_cell",
133
+ "portable-atomic",
134
+ "pyo3-build-config",
135
+ "pyo3-ffi",
136
+ "pyo3-macros",
137
+ ]
138
+
139
+ [[package]]
140
+ name = "pyo3-build-config"
141
+ version = "0.29.0"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
144
+ dependencies = [
145
+ "target-lexicon",
146
+ ]
147
+
148
+ [[package]]
149
+ name = "pyo3-ffi"
150
+ version = "0.29.0"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
153
+ dependencies = [
154
+ "libc",
155
+ "pyo3-build-config",
156
+ ]
157
+
158
+ [[package]]
159
+ name = "pyo3-macros"
160
+ version = "0.29.0"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
163
+ dependencies = [
164
+ "proc-macro2",
165
+ "pyo3-macros-backend",
166
+ "quote",
167
+ "syn 2.0.119",
168
+ ]
169
+
170
+ [[package]]
171
+ name = "pyo3-macros-backend"
172
+ version = "0.29.0"
173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
174
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
175
+ dependencies = [
176
+ "heck",
177
+ "proc-macro2",
178
+ "quote",
179
+ "syn 2.0.119",
180
+ ]
181
+
182
+ [[package]]
183
+ name = "quote"
184
+ version = "1.0.47"
185
+ source = "registry+https://github.com/rust-lang/crates.io-index"
186
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
187
+ dependencies = [
188
+ "proc-macro2",
189
+ ]
190
+
191
+ [[package]]
192
+ name = "rustversion"
193
+ version = "1.0.23"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
196
+
197
+ [[package]]
198
+ name = "slab"
199
+ version = "0.4.12"
200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
201
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
202
+
203
+ [[package]]
204
+ name = "syn"
205
+ version = "2.0.119"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
208
+ dependencies = [
209
+ "proc-macro2",
210
+ "quote",
211
+ "unicode-ident",
212
+ ]
213
+
214
+ [[package]]
215
+ name = "syn"
216
+ version = "3.0.2"
217
+ source = "registry+https://github.com/rust-lang/crates.io-index"
218
+ checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
219
+ dependencies = [
220
+ "proc-macro2",
221
+ "quote",
222
+ "unicode-ident",
223
+ ]
224
+
225
+ [[package]]
226
+ name = "target-lexicon"
227
+ version = "0.13.5"
228
+ source = "registry+https://github.com/rust-lang/crates.io-index"
229
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
230
+
231
+ [[package]]
232
+ name = "thiserror"
233
+ version = "2.0.19"
234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
235
+ checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
236
+ dependencies = [
237
+ "thiserror-impl",
238
+ ]
239
+
240
+ [[package]]
241
+ name = "thiserror-impl"
242
+ version = "2.0.19"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
245
+ dependencies = [
246
+ "proc-macro2",
247
+ "quote",
248
+ "syn 3.0.2",
249
+ ]
250
+
251
+ [[package]]
252
+ name = "unicode-ident"
253
+ version = "1.0.24"
254
+ source = "registry+https://github.com/rust-lang/crates.io-index"
255
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
256
+
257
+ [[package]]
258
+ name = "uuid"
259
+ version = "1.20.0"
260
+ source = "registry+https://github.com/rust-lang/crates.io-index"
261
+ checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
262
+ dependencies = [
263
+ "js-sys",
264
+ "wasm-bindgen",
265
+ ]
266
+
267
+ [[package]]
268
+ name = "wasm-bindgen"
269
+ version = "0.2.126"
270
+ source = "registry+https://github.com/rust-lang/crates.io-index"
271
+ checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
272
+ dependencies = [
273
+ "cfg-if",
274
+ "once_cell",
275
+ "rustversion",
276
+ "wasm-bindgen-macro",
277
+ "wasm-bindgen-shared",
278
+ ]
279
+
280
+ [[package]]
281
+ name = "wasm-bindgen-macro"
282
+ version = "0.2.126"
283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
284
+ checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
285
+ dependencies = [
286
+ "quote",
287
+ "wasm-bindgen-macro-support",
288
+ ]
289
+
290
+ [[package]]
291
+ name = "wasm-bindgen-macro-support"
292
+ version = "0.2.126"
293
+ source = "registry+https://github.com/rust-lang/crates.io-index"
294
+ checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
295
+ dependencies = [
296
+ "bumpalo",
297
+ "proc-macro2",
298
+ "quote",
299
+ "syn 2.0.119",
300
+ "wasm-bindgen-shared",
301
+ ]
302
+
303
+ [[package]]
304
+ name = "wasm-bindgen-shared"
305
+ version = "0.2.126"
306
+ source = "registry+https://github.com/rust-lang/crates.io-index"
307
+ checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
308
+ dependencies = [
309
+ "unicode-ident",
310
+ ]
311
+
312
+ [[package]]
313
+ name = "web-time"
314
+ version = "1.1.0"
315
+ source = "registry+https://github.com/rust-lang/crates.io-index"
316
+ checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
317
+ dependencies = [
318
+ "js-sys",
319
+ "wasm-bindgen",
320
+ ]
ezdgn-0.1.0/Cargo.toml ADDED
@@ -0,0 +1,15 @@
1
+ [workspace]
2
+ members = ["crates/ezdgn-core", "crates/ezdgn-python"]
3
+ resolver = "2"
4
+
5
+ [workspace.package]
6
+ version = "0.1.0"
7
+ edition = "2021"
8
+ rust-version = "1.83"
9
+ authors = ["neka-nat <nekanat.stock@gmail.com>"]
10
+ description = "Native V7 DGN reader/writer core and Python bindings"
11
+ repository = "https://github.com/monozukuri-ai/ezdgn"
12
+ license = "MIT"
13
+
14
+ [workspace.dependencies]
15
+ pyo3 = { version = "0.29.0", features = ["abi3-py310"] }
ezdgn-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neka-nat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ezdgn-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: ezdgn
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 2 - Pre-Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Rust
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Dist: matplotlib>=3.8 ; extra == 'dev'
17
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
18
+ Requires-Dist: matplotlib>=3.8 ; extra == 'plot'
19
+ Provides-Extra: dev
20
+ Provides-Extra: plot
21
+ License-File: LICENSE
22
+ Summary: Native V7 DGN reader and seed-based writer for Python powered by Rust
23
+ Keywords: dgn,cad,microstation,parser,rust
24
+ Author-email: neka-nat <nekanat.stock@gmail.com>
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
27
+ Project-URL: Homepage, https://github.com/monozukuri-ai/ezdgn
28
+ Project-URL: Issues, https://github.com/monozukuri-ai/ezdgn/issues
29
+ Project-URL: Repository, https://github.com/monozukuri-ai/ezdgn
30
+
31
+ # ezdgn
32
+
33
+ `ezdgn` is a native V7 DGN reader and seed-based writer for Python,
34
+ implemented with a pure Rust core and PyO3 bindings.
35
+
36
+ ## Installation
37
+
38
+ `ezdgn` requires Python 3.10 or newer.
39
+
40
+ ```bash
41
+ python -m pip install ezdgn
42
+ ```
43
+
44
+ Install the optional Matplotlib renderer when preview images are needed:
45
+
46
+ ```bash
47
+ python -m pip install "ezdgn[plot]"
48
+ ```
49
+
50
+ Building from source requires Rust 1.83 or newer. The extension uses Python's
51
+ stable ABI (`abi3`) with a Python 3.10 minimum.
52
+
53
+ ## Supported scope
54
+
55
+ | Format and operation | Support |
56
+ | --- | --- |
57
+ | V7/ISFF 2D read | Native entities, hierarchy, metadata, linkages, and raw records |
58
+ | V7/ISFF 2D write | Seed-based creation of common primitive entities |
59
+ | V7/ISFF 3D | Signature, raw record, and common-header inspection only |
60
+ | V8 DGN | CFB container identification and directory inspection only |
61
+
62
+ The V7 reader decodes line, line string, shape, curve, ellipse, arc, text,
63
+ cell, text node, complex chain/shape, and B-spline records as native entities.
64
+ It restores parent/child relationships without flattening component records,
65
+ decodes typed attribute linkages while retaining their exact bytes, applies
66
+ high-precision sub-UOR coordinate corrections alongside the stored integers,
67
+ and resolves outline/fill colors through the active color table. Every record,
68
+ including unsupported control and application elements, retains its original
69
+ bytes.
70
+
71
+ The writer creates standalone V7 2D files from a caller-supplied seed while
72
+ preserving its units, origin, and design plane. It writes line, line string,
73
+ shape, curve, ellipse, arc, circle-as-ellipse, and raw-byte text entities with
74
+ basic symbology and shape fill linkage. Coordinates outside the seed's design
75
+ plane are rejected instead of silently clipped.
76
+
77
+ V8 entity semantics and V7 3D geometry are not supported. The writer does not
78
+ yet create cells, complex elements, B-splines, arbitrary linkages, or perform
79
+ in-place editing. The raw V7 record framing is shared by 2D and 3D files, so
80
+ `scan_records()` can inspect a 3D stream safely without implying 3D entity
81
+ support.
82
+
83
+ ## 2D entity API
84
+
85
+ ```python
86
+ import ezdgn
87
+
88
+ drawing = ezdgn.readfile("drawing.dgn")
89
+
90
+ # All records remain ordered and lossless. entities contains only top-level
91
+ # graphics; all_entities also includes drawable component records.
92
+ print(len(drawing.elements), len(drawing.entities), len(drawing.all_entities))
93
+
94
+ for entity in drawing:
95
+ print(entity.dxftype(), entity.record.level, entity.style)
96
+
97
+ for line in drawing.query("LINE"):
98
+ print(line.start_uor, line.end_uor)
99
+ print(line.start_master, line.end_master)
100
+
101
+ for text in drawing.query("TEXT"):
102
+ print(text.text_bytes)
103
+ print(text.decode_text("cp932")) # the caller selects the encoding
104
+
105
+ for cell in drawing.query("CELL"):
106
+ print(cell.name, cell.origin_master, cell.transform)
107
+ for component in drawing.children(cell):
108
+ print(" ", component.dxftype())
109
+
110
+ for element in drawing.elements:
111
+ for linkage in element.linkages:
112
+ print(linkage.kind, linkage.linkage_type_name, linkage.raw_bytes)
113
+ ```
114
+
115
+ Ellipse, arc, curve, and B-spline entities retain their native parameters or
116
+ control records; they are not flattened to polylines. `parent_index` and
117
+ `child_indices` refer to the lossless `drawing.elements` sequence, while
118
+ `drawing.parent()`, `children()`, and `descendants()` resolve the objects.
119
+ Stored integer UOR, sub-UOR-corrected floating UOR, and optional master-unit
120
+ coordinates coexist. `drawing.color_table` is the last type-5, level-1 color
121
+ table in file order; `entity.style.rgb` and `fill_rgb` are resolved from it
122
+ when present.
123
+
124
+ Known DMRS/database, association ID, shape fill, and high-precision linkages
125
+ have typed fields. Unknown user linkages and malformed trailing attribute bytes
126
+ remain accessible through read-only raw views. Shared-cell definition/instance
127
+ types 34/35 remain raw because the public ISFF chapter does not specify their
128
+ layout.
129
+
130
+ The high-level `read()`/`readfile()` API deliberately rejects V7 3D files.
131
+ `scan_records()` and `inspect_headers()` still support bounded inspection of
132
+ their shared record framing and metadata.
133
+
134
+ ## Plotting parsed drawings
135
+
136
+ The optional renderer can display a parsed V7 2D drawing or save it as an
137
+ image without changing the native entity model:
138
+
139
+ ```python
140
+ import ezdgn
141
+
142
+ drawing = ezdgn.readfile("drawing.dgn")
143
+
144
+ figure, axes = ezdgn.plot(
145
+ drawing,
146
+ text_encoding="cp932",
147
+ background="#111111",
148
+ )
149
+ figure.savefig("preview.png", dpi=150, bbox_inches="tight")
150
+
151
+ # Or render and save in one call.
152
+ ezdgn.save_plot(drawing, "preview.png", text_encoding="cp932")
153
+ ```
154
+
155
+ The equivalent CLI command is:
156
+
157
+ ```bash
158
+ ezdgn plot drawing.dgn -o preview.png --encoding cp932
159
+ ```
160
+
161
+ Omit `-o` to open an interactive Matplotlib window. Use `--monochrome` for a
162
+ high-contrast preview, `--hide-text` to suppress text, or
163
+ `--coordinate-space uor` when master-unit coordinates are unavailable. Run
164
+ `ezdgn plot --help` for the complete option list.
165
+
166
+ Lines, line strings, shapes, ellipses, arcs, text, and drawable components of
167
+ cells and complex elements are rendered. Ellipses and arcs are sampled only
168
+ for display. Native type-11 curves and B-spline curves are previewed from
169
+ their parsed control sequences; the source records and entity parameters are
170
+ never flattened or modified. V7 text does not store its code page, so the
171
+ caller must select the correct encoding for non-ASCII text. Geometry and text
172
+ with compatible display styles are batched to keep large previews practical.
173
+
174
+ ## Seed-based V7 writer
175
+
176
+ ```python
177
+ import ezdgn
178
+
179
+ doc = ezdgn.new("seed_2d.dgn")
180
+ msp = doc.modelspace()
181
+
182
+ msp.add_line(
183
+ (0, 0),
184
+ (10, 5),
185
+ dgnattribs={"level": 2, "color": 3, "line_weight": 2},
186
+ )
187
+ msp.add_line_string([(0, 10), (5, 15), (10, 10)])
188
+ msp.add_shape(
189
+ [(20, 0), (30, 0), (30, 10), (20, 10)],
190
+ fill_color=6,
191
+ )
192
+ msp.add_ellipse((25, 25), primary_axis=5, secondary_axis=3, rotation=30)
193
+ msp.add_arc(
194
+ (40, 5),
195
+ primary_axis=5,
196
+ secondary_axis=3,
197
+ start_angle=30,
198
+ sweep_angle=120,
199
+ )
200
+ msp.add_text("日本語", (0, 30), height=2, encoding="cp932")
201
+
202
+ doc.saveas("drawing.dgn")
203
+ roundtrip = doc.readback()
204
+ ```
205
+
206
+ By default `new()` copies the mandatory TCB, digitizer setup, level symbology,
207
+ and the last active color table from the seed. Set `copy_seed_elements=True`
208
+ to retain every existing seed record, including any graphics. Text encoding is
209
+ not recorded by V7 DGN, so `add_text()` accepts bytes directly or requires the
210
+ caller-selected encoding for `str` input.
211
+
212
+ ## Raw record API
213
+
214
+ ```python
215
+ import ezdgn
216
+
217
+ info = ezdgn.detect_format("drawing.dgn")
218
+ print(info.kind, info.dimension)
219
+
220
+ scan = ezdgn.scan_records("drawing.dgn")
221
+ print(len(scan.records), scan.termination)
222
+
223
+ for record in scan.records:
224
+ print(record.offset, record.element_type, record.level, record.raw_bytes)
225
+ ```
226
+
227
+ The `V8_CFB` result means that the input has the generic CFB signature used by
228
+ V8 DGN files. It is intentionally described as a candidate because the outer
229
+ signature alone does not prove that DGN-specific streams are present.
230
+
231
+ The bounded container inspector verifies the known DGN root markers without
232
+ decoding proprietary V8 stream contents:
233
+
234
+ ```python
235
+ container = ezdgn.inspect_v8_container("drawing-v8.dgn")
236
+ print(container.has_dgn_v8_markers)
237
+ print(container.model_storage_paths)
238
+ for entry in container.entries:
239
+ print(entry.path, entry.kind, entry.size_bytes)
240
+ ```
241
+
242
+ This is structural identification, not V8 entity support or a fidelity
243
+ guarantee. `ezdgn.read()`, `readfile()`, and `scan_records()` reject V8 input
244
+ instead of silently flattening or converting it. If a workflow converts V8 to
245
+ V7 outside `ezdgn`, validate the resulting geometry, text, levels, styles, and
246
+ complex/cell relationships before treating it as equivalent to the source.
247
+
248
+ ## Design settings and common headers
249
+
250
+ ```python
251
+ import ezdgn
252
+
253
+ headers = ezdgn.inspect_headers("drawing.dgn")
254
+ settings = headers.design_settings
255
+
256
+ print(settings.master_unit_name, settings.uor_per_master)
257
+ print(settings.global_origin_master)
258
+
259
+ for element in headers.elements:
260
+ common = element.common_header
261
+ if common is not None:
262
+ print(
263
+ element.record.element_type,
264
+ common.range.low_master,
265
+ common.range.high_master,
266
+ common.symbology.color,
267
+ )
268
+ ```
269
+
270
+ `read_design_settings()` decodes only the leading TCB. `inspect_headers()`
271
+ pairs every raw record with its standard common header when that element type
272
+ has one. Attribute bytes remain available as a read-only zero-copy
273
+ `ElementMetadata.attribute_view`.
274
+
275
+ The same inspection is available from the CLI:
276
+
277
+ ```bash
278
+ ezdgn inspect drawing.dgn
279
+ ezdgn inspect drawing.dgn --records --json
280
+ ezdgn inspect drawing.dgn --headers --json
281
+ ezdgn inspect drawing.dgn --entities --json
282
+ ezdgn inspect drawing-v8.dgn --json
283
+ ```
284
+
285
+ ## Development
286
+
287
+ ```bash
288
+ python -m venv .venv
289
+ . .venv/bin/activate
290
+ python -m pip install "maturin>=1.13,<2" "pytest>=8" "matplotlib>=3.8"
291
+ maturin develop
292
+ cargo fmt --check
293
+ cargo clippy --workspace --all-targets --all-features -- -D warnings
294
+ cargo test --workspace
295
+ python -m pytest
296
+ ```
297
+
298
+ Build a distributable wheel with:
299
+
300
+ ```bash
301
+ maturin build --release --out dist
302
+ ```
303
+
304
+ ## License
305
+
306
+ `ezdgn` is released under the [MIT License](LICENSE). Test fixtures retain the
307
+ separate upstream terms documented in
308
+ [`tests/data/dgn/README.md`](tests/data/dgn/README.md).
309
+