Skip to content

Commit 084db45

Browse files
authored
Merge pull request #243 from ducflair/dev
Dev
2 parents 8f34b25 + 9ee1b45 commit 084db45

12 files changed

Lines changed: 468 additions & 14 deletions

File tree

packages/ducpy/docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
html_theme = "furo"
2828
html_static_path = ['_static']
29+
html_extra_path = ['extra']
2930
html_baseurl = "https://ducflair.github.io/duc/reference/python/"
3031

3132
extensions.append('autoapi.extension')

packages/ducpy/docs/examples.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,25 @@ anything beyond what the high-level builders expose.
4949
.. literalinclude:: ../src/examples/sql_builder_demo.py
5050
:language: python
5151
:linenos:
52+
53+
----
54+
55+
Serialization
56+
-------------
57+
58+
Demonstrates how to serialize builder-created elements directly to a `.duc` file using `duc.serialize_duc`.
59+
60+
.. literalinclude:: ../src/examples/serialization_demo.py
61+
:language: python
62+
:linenos:
63+
64+
----
65+
66+
Parsing
67+
-------
68+
69+
Demonstrates how to parse a `.duc` file or raw binary bytes using `duc.parse_duc`, allowing attribute-style access to the document's content.
70+
71+
.. literalinclude:: ../src/examples/parsing_demo.py
72+
:language: python
73+
:linenos:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"url": "https://context7.com/websites/ducflair_github_io_duc",
3+
"public_key": "pk_rW7pJ4T4uki1BAr4guCs1"
4+
}

packages/ducpy/docs/index.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@ ducpy
1616
</p>
1717
</p>
1818

19+
Overview
20+
--------
21+
22+
**Builders API (High-level)**
23+
The easy way to build, manage ``.duc`` files.
24+
Construct elements, apply styles, manage layers, build blocks,
25+
and handle document state with the
26+
:doc:`builders <autoapi/ducpy/builders/index>` module.
27+
28+
**SQL Builder (Low-level)**
29+
A ``.duc`` file is a zlib-compressed SQLite database. Use the
30+
:doc:`sql_builder <autoapi/ducpy/builders/sql_builder/index>`
31+
for direct schema access, bulk queries, and low-level manipulation.
32+
33+
**Search**
34+
Query/search elements and files programmatically via the
35+
:doc:`search <autoapi/ducpy/search/index>` API.
36+
37+
**File I/O**
38+
Read and write ``.duc`` files using the
39+
:doc:`parse <autoapi/ducpy/parse/index>` and
40+
:doc:`serialize <autoapi/ducpy/serialize/index>` modules.
41+
1942
.. toctree::
2043
:maxdepth: 2
2144
:caption: Contents:

packages/ducpy/src/ducpy/__init__.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
"""Main module for duc_py package.
1+
"""Python library for the DUC 2D CAD file format.
22
3-
Import usage examples:
4-
import ducpy as duc
5-
6-
duc.classes.SomeClass
7-
duc.parse_duc(source)
8-
duc.serialize_duc(name=..., elements=...)
9-
duc.utils.some_utility
3+
Usage::
4+
``import ducpy as duc``
5+
6+
Builders API (High-level):
7+
The easy way to build, manage ``.duc`` files.
8+
Construct elements, apply styles, manage layers, build blocks,
9+
and handle document state with the ``duc.builders`` module.
10+
11+
SQL Builder (Low-level):
12+
A ``.duc`` file is a zlib-compressed SQLite database. Use
13+
``duc.builders.sql_builder`` for direct schema access, bulk
14+
queries, and low-level manipulation.
15+
16+
Search:
17+
Query/search elements and files programmatically via the
18+
``duc.search`` API.
19+
20+
File I/O:
21+
Read and write ``.duc`` files using the ``duc.parse``
22+
and ``duc.serialize`` modules.
1023
"""
1124

1225
from .builders import *

packages/ducpy/src/ducpy/parse.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,29 @@ def _read_bytes(source: Union[bytes, bytearray, BinaryIO, str]) -> bytes:
5757
def parse_duc(source: Union[bytes, bytearray, BinaryIO, str]) -> DucData:
5858
"""Parse a ``.duc`` file into a :class:`DucData` dict.
5959
60+
This function reads a raw `.duc` binary blob or file path and parses it using
61+
the Rust native extension. It returns a specialized dictionary (`DucData`)
62+
that allows attribute-style access to the parsed properties (e.g. `data.elements[0].id`),
63+
using `snake_case` keys instead of the internal `camelCase` format.
64+
6065
Parameters
6166
----------
6267
source : bytes | file | str
63-
Raw bytes, an open binary file, or a path to a ``.duc`` file.
68+
Raw bytes, an open binary file, or a string path to a ``.duc`` file.
6469
6570
Returns
6671
-------
6772
DucData
68-
Attribute-accessible dict matching the ExportedDataState schema with
69-
snake_case keys.
73+
An attribute-accessible dictionary matching the internal `ExportedDataState`
74+
schema with snake_case keys. Common keys include `elements`, `global_state`,
75+
`local_state`, and `version_graph`.
76+
77+
Examples
78+
--------
79+
>>> data = duc.parse_duc("path/to/file.duc")
80+
>>> data = duc.parse_duc(binary_data)
81+
>>> print(f"Found {len(data.elements)} elements")
82+
>>> print(f"First element type: {data.elements[0].type}")
7083
"""
7184
buf = _read_bytes(source)
7285
raw = ducpy_native.parse_duc(buf)

packages/ducpy/src/ducpy/serialize.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,48 @@ def serialize_duc(
216216
layers: Optional[list] = None,
217217
external_files: Optional[list] = None,
218218
) -> bytes:
219-
"""Serialize elements to ``.duc`` format.
220-
Element instances and state dataclasses are automatically converted to the
221-
camelCase dicts expected by the Rust native module.
219+
"""Serialize elements and document state to raw ``.duc`` binary format.
220+
221+
This function accepts lists of elements created via the `ducpy.builders` API
222+
(e.g., `ElementBuilder`) and serializes them into the compressed format
223+
expected by `.duc` files. Element instances and state dataclasses are
224+
automatically converted to the camelCase dicts expected by the Rust native module.
225+
226+
Parameters
227+
----------
228+
name : str
229+
The document name or identifier (used to populate the `source` field).
230+
thumbnail : Optional[bytes], default=None
231+
Raw PNG bytes representing a thumbnail of the document.
232+
dictionary : Optional[list], default=None
233+
List of Key-Value string pairs for dictionary entries.
234+
elements : Optional[list], default=None
235+
A list of elements (e.g., created via `ElementBuilder`) to include.
236+
duc_local_state : Any, default=None
237+
A `DucLocalState` object representing viewport state (pan, zoom, etc).
238+
duc_global_state : Any, default=None
239+
A `DucGlobalState` object representing document-wide settings.
240+
version_graph : Any, default=None
241+
Version history metadata of the document.
242+
blocks : Optional[list], default=None
243+
List of block definitions.
244+
block_instances : Optional[list], default=None
245+
List of block instances.
246+
block_collections : Optional[list], default=None
247+
List of block collections (libraries).
248+
groups : Optional[list], default=None
249+
List of element groups.
250+
regions : Optional[list], default=None
251+
List of boolean regions.
252+
layers : Optional[list], default=None
253+
List of document layers.
254+
external_files : Optional[list], default=None
255+
List of external files (e.g., embedded images or PDFs).
256+
257+
Returns
258+
-------
259+
bytes
260+
The raw `.duc` binary data, ready to be written to a file.
222261
"""
223262
thumb = bytes(thumbnail) if thumbnail is not None else None
224263

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example demonstrating how to parse a .duc file using the parsing API.
4+
5+
This demo shows how to read a `.duc` binary blob or file path and access
6+
the parsed data using attribute-style access via DucData.
7+
"""
8+
9+
import os
10+
import tempfile
11+
import ducpy as duc
12+
13+
def main():
14+
print("Parsing Demo")
15+
print("=" * 30)
16+
17+
# First, let's create a temporary .duc file to parse
18+
from ducpy.builders.style_builders import create_fill_and_stroke_style, create_solid_content
19+
elements = [
20+
duc.ElementBuilder()
21+
.at_position(10, 20)
22+
.with_size(100, 50)
23+
.with_label("Parsed Rectangle")
24+
.with_styles(create_fill_and_stroke_style(
25+
fill_content=create_solid_content("#FF6B6B"),
26+
stroke_content=create_solid_content("#2C3E50"),
27+
stroke_width=2.0
28+
))
29+
.build_rectangle()
30+
.build()
31+
]
32+
duc_bytes = duc.serialize_duc(name="parsing_example", elements=elements)
33+
34+
with tempfile.NamedTemporaryFile(suffix=".duc", delete=False) as tmp:
35+
tmp.write(duc_bytes)
36+
tmp_path = tmp.name
37+
38+
print("1. Parsing a .duc file from a file path...")
39+
40+
# You can pass a string path directly to parse_duc
41+
parsed_data = duc.parse_duc(tmp_path)
42+
43+
print(f" Document Source: {parsed_data.source}")
44+
print(f" Parsed {len(parsed_data.elements)} elements.")
45+
46+
print("\n2. Accessing element attributes (snake_case)...")
47+
48+
# Element properties are accessible via dot-notation with snake_case keys
49+
# because parse_duc returns a DucData object.
50+
first_element = parsed_data.elements[0]
51+
print(f" Element ID: {first_element.id}")
52+
print(f" Element Type: {first_element.type}")
53+
print(f" Element Label: {first_element.label}")
54+
print(f" Element Position: (X: {first_element.x}, Y: {first_element.y})")
55+
56+
print("\n3. Parsing directly from raw bytes...")
57+
58+
# You can also pass raw bytes directly to parse_duc
59+
parsed_from_bytes = duc.parse_duc(duc_bytes)
60+
print(f" Parsed successfully from bytes. Found {len(parsed_from_bytes.elements)} elements.")
61+
62+
# Clean up the temporary file
63+
os.unlink(tmp_path)
64+
65+
print("\n✅ Parsing demo complete!")
66+
67+
if __name__ == "__main__":
68+
main()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example demonstrating how to serialize elements created by the Builder API into a .duc file.
4+
5+
This demo shows the correct pattern for taking in-memory python elements
6+
and writing them to a raw `.duc` binary blob.
7+
"""
8+
9+
import os
10+
import ducpy as duc
11+
from ducpy.builders.style_builders import create_fill_and_stroke_style, create_solid_content
12+
13+
def main():
14+
print("Serialization Demo")
15+
print("=" * 30)
16+
17+
print("1. Creating elements via Builder API...")
18+
elements = []
19+
20+
# Create some basic elements
21+
rect = (duc.ElementBuilder()
22+
.at_position(0, 0)
23+
.with_size(100, 50)
24+
.with_label("Sample Rectangle")
25+
.with_styles(create_fill_and_stroke_style(
26+
fill_content=create_solid_content("#FF6B6B"),
27+
stroke_content=create_solid_content("#2C3E50"),
28+
stroke_width=2.0
29+
))
30+
.build_rectangle()
31+
.build())
32+
elements.append(rect)
33+
34+
ellipse = (duc.ElementBuilder()
35+
.at_position(120, 0)
36+
.with_size(60, 40)
37+
.with_label("Sample Ellipse")
38+
.with_styles(create_fill_and_stroke_style(
39+
fill_content=create_solid_content("#4ECDC4"),
40+
stroke_content=create_solid_content("#34495E"),
41+
stroke_width=1.5
42+
))
43+
.build_ellipse()
44+
.build())
45+
elements.append(ellipse)
46+
47+
print(f" Created {len(elements)} elements.")
48+
49+
print("2. Serializing to .duc format...")
50+
# NOTE: The serialize_duc function takes keyword arguments for elements,
51+
# blocks, global state, etc. and bridges to the Rust native backend.
52+
duc_bytes = duc.serialize_duc(
53+
name="serialization_example",
54+
elements=elements
55+
)
56+
57+
print(f" Successfully serialized {len(duc_bytes)} bytes.")
58+
59+
# You would typically write this to a file
60+
# output_file = "example.duc"
61+
# with open(output_file, "wb") as f:
62+
# f.write(duc_bytes)
63+
# print(f"3. Saved to {output_file}")
64+
65+
print("\n✅ Serialization demo complete!")
66+
67+
if __name__ == "__main__":
68+
main()

0 commit comments

Comments
 (0)