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.
Files changed (64) hide show
  1. {ducpy-3.3.0 → ducpy-3.3.2}/Cargo.lock +1 -1
  2. {ducpy-3.3.0 → ducpy-3.3.2}/PKG-INFO +2 -1
  3. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducpy/crate/Cargo.toml +1 -1
  4. {ducpy-3.3.0 → ducpy-3.3.2}/pyproject.toml +2 -1
  5. ducpy-3.3.2/src/ducpy/__init__.py +32 -0
  6. ducpy-3.3.2/src/ducpy/builders/__init__.py +9 -0
  7. ducpy-3.3.2/src/ducpy/builders/block_instance_builder.py +36 -0
  8. ducpy-3.3.2/src/ducpy/builders/block_utils.py +128 -0
  9. ducpy-3.3.2/src/ducpy/builders/element_builders.py +757 -0
  10. ducpy-3.3.2/src/ducpy/builders/mutate_builder.py +50 -0
  11. ducpy-3.3.2/src/ducpy/builders/sql_builder.py +269 -0
  12. ducpy-3.3.2/src/ducpy/builders/state_builders.py +664 -0
  13. ducpy-3.3.2/src/ducpy/builders/style_builders.py +292 -0
  14. ducpy-3.3.2/src/ducpy/classes/DataStateClass.py +156 -0
  15. ducpy-3.3.2/src/ducpy/classes/ElementsClass.py +582 -0
  16. ducpy-3.3.2/src/ducpy/classes/__init__.py +3 -0
  17. ducpy-3.3.2/src/ducpy/enums.py +119 -0
  18. ducpy-3.3.2/src/ducpy/parse.py +119 -0
  19. ducpy-3.3.2/src/ducpy/search/__init__.py +13 -0
  20. ducpy-3.3.2/src/ducpy/search/search_elements.py +683 -0
  21. ducpy-3.3.2/src/ducpy/serialize.py +286 -0
  22. ducpy-3.3.2/src/ducpy/utils/__init__.py +4 -0
  23. ducpy-3.3.2/src/ducpy/utils/constants.py +18 -0
  24. ducpy-3.3.2/src/ducpy/utils/convert.py +101 -0
  25. ducpy-3.3.2/src/ducpy/utils/io.py +53 -0
  26. ducpy-3.3.2/src/ducpy/utils/mutate_utils.py +24 -0
  27. ducpy-3.3.2/src/ducpy/utils/rand_utils.py +20 -0
  28. {ducpy-3.3.0 → ducpy-3.3.2}/Cargo.toml +0 -0
  29. {ducpy-3.3.0 → ducpy-3.3.2}/LICENSE +0 -0
  30. {ducpy-3.3.0 → ducpy-3.3.2}/README.md +0 -0
  31. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducpy/crate/src/lib.rs +0 -0
  32. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/.gitignore +0 -0
  33. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/Cargo.toml +0 -0
  34. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/LICENSE +0 -0
  35. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/README.md +0 -0
  36. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/build.rs +0 -0
  37. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/package.json +0 -0
  38. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/release.config.cjs +0 -0
  39. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/document.rs +0 -0
  40. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/meta.rs +0 -0
  41. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/mod.rs +0 -0
  42. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/api/version_control.rs +0 -0
  43. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/bootstrap.rs +0 -0
  44. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/mod.rs +0 -0
  45. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/native.rs +0 -0
  46. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/db/wasm.rs +0 -0
  47. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/lib.rs +0 -0
  48. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/parse.rs +0 -0
  49. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/serde_utils.rs +0 -0
  50. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/serialize.rs +0 -0
  51. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/src/types.rs +0 -0
  52. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/sst-env.d.ts +0 -0
  53. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/.gitignore +0 -0
  54. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/common/mod.rs +0 -0
  55. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/parser_assets.rs +0 -0
  56. {ducpy-3.3.0 → ducpy-3.3.2}/packages/ducrs/tests/synthetic_roundtrip.rs +0 -0
  57. {ducpy-3.3.0 → ducpy-3.3.2}/schema/duc.sql +0 -0
  58. {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000000_to_3000001.sql +0 -0
  59. {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000001_to_3000002.sql +0 -0
  60. {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000002_to_3000003.sql +0 -0
  61. {ducpy-3.3.0 → ducpy-3.3.2}/schema/migrations/3000003_to_3000004.sql +0 -0
  62. {ducpy-3.3.0 → ducpy-3.3.2}/schema/search.sql +0 -0
  63. {ducpy-3.3.0 → ducpy-3.3.2}/schema/version_control.sql +0 -0
  64. {ducpy-3.3.0 → ducpy-3.3.2}/src/ducpy_native/__init__.py +0 -0
@@ -412,7 +412,7 @@ dependencies = [
412
412
 
413
413
  [[package]]
414
414
  name = "ducpy-native"
415
- version = "3.3.0"
415
+ version = "3.3.2"
416
416
  dependencies = [
417
417
  "duc",
418
418
  "pyo3",
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducpy
3
- Version: 3.3.0
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
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "ducpy-native"
3
- version = "3.3.0"
3
+ version = "3.3.2"
4
4
  edition = "2021"
5
5
  description = "Python native extension for the duc file format (used by ducpy)"
6
6
  publish = false
@@ -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