ducpy 3.3.0__tar.gz → 3.3.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.
- {ducpy-3.3.0 → ducpy-3.3.2}/Cargo.lock +1 -1
- {ducpy-3.3.0 → ducpy-3.3.2}/PKG-INFO +2 -1
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducpy/crate/Cargo.toml +1 -1
- {ducpy-3.3.0 → ducpy-3.3.2}/pyproject.toml +2 -1
- ducpy-3.3.2/src/ducpy/__init__.py +32 -0
- ducpy-3.3.2/src/ducpy/builders/__init__.py +9 -0
- ducpy-3.3.2/src/ducpy/builders/block_instance_builder.py +36 -0
- ducpy-3.3.2/src/ducpy/builders/block_utils.py +128 -0
- ducpy-3.3.2/src/ducpy/builders/element_builders.py +757 -0
- ducpy-3.3.2/src/ducpy/builders/mutate_builder.py +50 -0
- ducpy-3.3.2/src/ducpy/builders/sql_builder.py +269 -0
- ducpy-3.3.2/src/ducpy/builders/state_builders.py +664 -0
- ducpy-3.3.2/src/ducpy/builders/style_builders.py +292 -0
- ducpy-3.3.2/src/ducpy/classes/DataStateClass.py +156 -0
- ducpy-3.3.2/src/ducpy/classes/ElementsClass.py +582 -0
- ducpy-3.3.2/src/ducpy/classes/__init__.py +3 -0
- ducpy-3.3.2/src/ducpy/enums.py +119 -0
- ducpy-3.3.2/src/ducpy/parse.py +119 -0
- ducpy-3.3.2/src/ducpy/search/__init__.py +13 -0
- ducpy-3.3.2/src/ducpy/search/search_elements.py +683 -0
- ducpy-3.3.2/src/ducpy/serialize.py +286 -0
- ducpy-3.3.2/src/ducpy/utils/__init__.py +4 -0
- ducpy-3.3.2/src/ducpy/utils/constants.py +18 -0
- ducpy-3.3.2/src/ducpy/utils/convert.py +101 -0
- ducpy-3.3.2/src/ducpy/utils/io.py +53 -0
- ducpy-3.3.2/src/ducpy/utils/mutate_utils.py +24 -0
- ducpy-3.3.2/src/ducpy/utils/rand_utils.py +20 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/Cargo.toml +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/LICENSE +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/README.md +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducpy/crate/src/lib.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/.gitignore +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/Cargo.toml +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/LICENSE +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/README.md +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/build.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/package.json +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/release.config.cjs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/document.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/meta.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/mod.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/version_control.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/bootstrap.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/mod.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/native.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/wasm.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/lib.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/parse.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/serde_utils.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/serialize.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/types.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/sst-env.d.ts +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/.gitignore +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/common/mod.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/parser_assets.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/synthetic_roundtrip.rs +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/duc.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000000_to_3000001.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000001_to_3000002.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000002_to_3000003.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000003_to_3000004.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/search.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/schema/version_control.sql +0 -0
- {ducpy-3.3.0 → ducpy-3.3.2}/src/ducpy_native/__init__.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducpy
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.2
|
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
|
5
5
|
Requires-Dist: nanoid>=2.0.0
|
|
6
|
+
Requires-Dist: numpy>=1.20.0
|
|
6
7
|
License-File: LICENSE
|
|
7
8
|
Summary: The library for the Duc 2D CAD file format
|
|
8
9
|
Author: Jorge Soares
|
|
@@ -6,6 +6,7 @@ readme = "README.md"
|
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"nanoid>=2.0.0",
|
|
9
|
+
"numpy>=1.20.0",
|
|
9
10
|
]
|
|
10
11
|
|
|
11
12
|
license = { text = "MIT" }
|
|
@@ -30,6 +31,7 @@ requires = ["maturin>=1.0,<2.0"]
|
|
|
30
31
|
build-backend = "maturin"
|
|
31
32
|
|
|
32
33
|
[tool.maturin]
|
|
34
|
+
python-packages = ["ducpy", "ducpy_native"]
|
|
33
35
|
module-name = "ducpy_native"
|
|
34
36
|
include = [
|
|
35
37
|
{ path = "LICENSE", format = "sdist" },
|
|
@@ -49,7 +51,6 @@ dev = [
|
|
|
49
51
|
"sphinx>=8.1.3",
|
|
50
52
|
"sphinx-autoapi>=3.6.0",
|
|
51
53
|
"pytest>=8.3.5",
|
|
52
|
-
"numpy>=1.20.0",
|
|
53
54
|
"rich>=13.0.0",
|
|
54
55
|
]
|
|
55
56
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Python library for the DUC 2D CAD file format.
|
|
2
|
+
|
|
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.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .builders import *
|
|
26
|
+
from .classes import *
|
|
27
|
+
from .enums import *
|
|
28
|
+
from .parse import (DucData, get_external_file, list_external_files, parse_duc,
|
|
29
|
+
parse_duc_lazy)
|
|
30
|
+
from .search import *
|
|
31
|
+
from .serialize import DUC_SCHEMA_VERSION, serialize_duc
|
|
32
|
+
from .utils import *
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User-friendly helper functions for creating DUC elements without verbose dataclass construction.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .element_builders import *
|
|
6
|
+
from .mutate_builder import *
|
|
7
|
+
from .sql_builder import *
|
|
8
|
+
from .state_builders import *
|
|
9
|
+
from .style_builders import *
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from ducpy.classes.ElementsClass import (DucBlockDuplicationArray,
|
|
4
|
+
DucBlockInstance, StringValueEntry)
|
|
5
|
+
from ducpy.utils.rand_utils import random_versioning
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BlockInstanceBuilder:
|
|
9
|
+
def __init__(self, id: str, block_id: str, version: int):
|
|
10
|
+
self._id = id
|
|
11
|
+
self._block_id = block_id
|
|
12
|
+
self._version = version
|
|
13
|
+
self._element_overrides: List[StringValueEntry] = []
|
|
14
|
+
self._duplication_array: Optional[DucBlockDuplicationArray] = None
|
|
15
|
+
|
|
16
|
+
def with_element_override(self, key: str, value: str) -> 'BlockInstanceBuilder':
|
|
17
|
+
self._element_overrides.append(StringValueEntry(key=key, value=value))
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def with_duplication_array(self, rows: int, cols: int, row_spacing: float, col_spacing: float) -> 'BlockInstanceBuilder':
|
|
21
|
+
self._duplication_array = DucBlockDuplicationArray(
|
|
22
|
+
rows=rows,
|
|
23
|
+
cols=cols,
|
|
24
|
+
row_spacing=row_spacing,
|
|
25
|
+
col_spacing=col_spacing
|
|
26
|
+
)
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def build(self) -> DucBlockInstance:
|
|
30
|
+
return DucBlockInstance(
|
|
31
|
+
id=self._id,
|
|
32
|
+
block_id=self._block_id,
|
|
33
|
+
version=self._version,
|
|
34
|
+
element_overrides=self._element_overrides if self._element_overrides else None,
|
|
35
|
+
duplication_array=self._duplication_array
|
|
36
|
+
)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
import copy
|
|
3
|
+
from ducpy.classes.DataStateClass import ExportedDataState, DucBlockInstance
|
|
4
|
+
from ducpy.classes.ElementsClass import ElementWrapper, DucBlock
|
|
5
|
+
from ducpy.builders.block_instance_builder import BlockInstanceBuilder
|
|
6
|
+
from ducpy.utils.rand_utils import generate_random_id
|
|
7
|
+
|
|
8
|
+
def _get_base(element):
|
|
9
|
+
if hasattr(element, "linear_base"):
|
|
10
|
+
return element.linear_base.base
|
|
11
|
+
return element.base
|
|
12
|
+
|
|
13
|
+
def instantiate_block(
|
|
14
|
+
state: ExportedDataState,
|
|
15
|
+
block_id: str,
|
|
16
|
+
position_x: float,
|
|
17
|
+
position_y: float,
|
|
18
|
+
instance_id: Optional[str] = None
|
|
19
|
+
) -> DucBlockInstance:
|
|
20
|
+
"""
|
|
21
|
+
Creates an instance of a block at a specific position.
|
|
22
|
+
|
|
23
|
+
This function:
|
|
24
|
+
1. Creates a DucBlockInstance metadata object.
|
|
25
|
+
2. Finds all elements associated with the block_id.
|
|
26
|
+
3. Clones those elements.
|
|
27
|
+
4. Updates their positions relative to the new insertion point.
|
|
28
|
+
5. Sets their instance_id.
|
|
29
|
+
6. Adds the cloned elements and the block instance to the state.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
state: The ExportedDataState object to modify.
|
|
33
|
+
block_id: The ID of the block to instantiate.
|
|
34
|
+
position_x: The X coordinate for the instance insertion point.
|
|
35
|
+
position_y: The Y coordinate for the instance insertion point.
|
|
36
|
+
instance_id: Optional custom ID for the instance. If None, one is generated.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The created DucBlockInstance object.
|
|
40
|
+
"""
|
|
41
|
+
if instance_id is None:
|
|
42
|
+
instance_id = generate_random_id()
|
|
43
|
+
|
|
44
|
+
# Find the block definition
|
|
45
|
+
block: Optional[DucBlock] = next((b for b in state.blocks if b.id == block_id), None)
|
|
46
|
+
if not block:
|
|
47
|
+
raise ValueError(f"Block with ID {block_id} not found in state.")
|
|
48
|
+
|
|
49
|
+
# Create Block Instance Metadata
|
|
50
|
+
instance_builder = BlockInstanceBuilder(
|
|
51
|
+
id=instance_id,
|
|
52
|
+
block_id=block_id,
|
|
53
|
+
version=block.version
|
|
54
|
+
)
|
|
55
|
+
block_instance = instance_builder.build()
|
|
56
|
+
|
|
57
|
+
# Ensure lists exist
|
|
58
|
+
if state.block_instances is None:
|
|
59
|
+
state.block_instances = []
|
|
60
|
+
state.block_instances.append(block_instance)
|
|
61
|
+
|
|
62
|
+
# Find source elements (elements that define this block)
|
|
63
|
+
# In the new model, block definitions don't "own" elements in a separate list,
|
|
64
|
+
# but elements have 'block_ids' pointing to the blocks they belong to.
|
|
65
|
+
source_elements: List[ElementWrapper] = []
|
|
66
|
+
|
|
67
|
+
# We need to find elements that are part of the block definition.
|
|
68
|
+
# These are elements that have block_id in their block_ids list AND instance_id is None.
|
|
69
|
+
for wrapper in state.elements:
|
|
70
|
+
element = wrapper.element
|
|
71
|
+
block_ids = _get_base(element).block_ids
|
|
72
|
+
instance_id_val = _get_base(element).instance_id
|
|
73
|
+
# logical check: element belongs to block_id definition
|
|
74
|
+
if block_ids and block_id in block_ids and not instance_id_val:
|
|
75
|
+
source_elements.append(wrapper)
|
|
76
|
+
|
|
77
|
+
if not source_elements:
|
|
78
|
+
# It's possible a block has no elements yet, just return the empty instance
|
|
79
|
+
return block_instance
|
|
80
|
+
|
|
81
|
+
# Calculate bounding box of source elements to determine relative offsets
|
|
82
|
+
# For simplicity, we assume the "origin" of the block is the top-left of its bounding box
|
|
83
|
+
# or (0,0) if we want to be strict, but usually insertion implies some offset.
|
|
84
|
+
# Let's align with the provided TS logic: calculate common bounds.
|
|
85
|
+
|
|
86
|
+
min_x = float('inf')
|
|
87
|
+
min_y = float('inf')
|
|
88
|
+
|
|
89
|
+
for wrapper in source_elements:
|
|
90
|
+
el = wrapper.element
|
|
91
|
+
base = _get_base(el)
|
|
92
|
+
if base.x < min_x: min_x = base.x
|
|
93
|
+
if base.y < min_y: min_y = base.y
|
|
94
|
+
|
|
95
|
+
# If no elements found (shouldn't happen given check above), handle gracefully
|
|
96
|
+
if min_x == float('inf'):
|
|
97
|
+
min_x = 0
|
|
98
|
+
min_y = 0
|
|
99
|
+
|
|
100
|
+
# Calculate delta
|
|
101
|
+
delta_x = position_x - min_x
|
|
102
|
+
delta_y = position_y - min_y
|
|
103
|
+
|
|
104
|
+
# Clone and transform elements
|
|
105
|
+
for wrapper in source_elements:
|
|
106
|
+
# Deep copy the wrapper to duplicate the element
|
|
107
|
+
cloned_wrapper = copy.deepcopy(wrapper)
|
|
108
|
+
cloned_element = cloned_wrapper.element
|
|
109
|
+
|
|
110
|
+
base = _get_base(cloned_element)
|
|
111
|
+
|
|
112
|
+
# Generate new ID for the cloned element
|
|
113
|
+
base.id = generate_random_id()
|
|
114
|
+
|
|
115
|
+
# Update position
|
|
116
|
+
base.x += delta_x
|
|
117
|
+
base.y += delta_y
|
|
118
|
+
|
|
119
|
+
# Set instance_id
|
|
120
|
+
base.instance_id = instance_id
|
|
121
|
+
|
|
122
|
+
# Clear block_ids for the instance (it belongs to the instance, not the block definition directly)
|
|
123
|
+
base.block_ids = []
|
|
124
|
+
|
|
125
|
+
# Add to state elements
|
|
126
|
+
state.elements.append(cloned_wrapper)
|
|
127
|
+
|
|
128
|
+
return block_instance
|