ducpy 3.3.5__tar.gz → 3.3.7__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 (66) hide show
  1. {ducpy-3.3.5 → ducpy-3.3.7}/Cargo.lock +1 -1
  2. {ducpy-3.3.5 → ducpy-3.3.7}/PKG-INFO +1 -1
  3. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducpy/crate/Cargo.toml +1 -1
  4. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/Cargo.toml +11 -0
  5. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/parse.rs +34 -5
  6. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/serialize.rs +22 -8
  7. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/types.rs +2 -0
  8. {ducpy-3.3.5 → ducpy-3.3.7}/pyproject.toml +4 -0
  9. {ducpy-3.3.5 → ducpy-3.3.7}/schema/duc.sql +12 -2
  10. ducpy-3.3.7/schema/migrations/3000003_to_3000004.sql +41 -0
  11. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/__init__.py +1 -1
  12. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/element_builders.py +54 -44
  13. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/state_builders.py +3 -3
  14. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/classes/ElementsClass.py +8 -0
  15. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/serialize.py +244 -19
  16. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/__init__.py +2 -1
  17. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/convert.py +58 -6
  18. ducpy-3.3.5/packages/ducrs/.gitignore +0 -21
  19. ducpy-3.3.5/packages/ducrs/package.json +0 -13
  20. ducpy-3.3.5/packages/ducrs/release.config.cjs +0 -31
  21. ducpy-3.3.5/packages/ducrs/sst-env.d.ts +0 -10
  22. ducpy-3.3.5/packages/ducrs/tests/.gitignore +0 -2
  23. ducpy-3.3.5/packages/ducrs/tests/common/mod.rs +0 -1008
  24. ducpy-3.3.5/packages/ducrs/tests/parser_assets.rs +0 -227
  25. ducpy-3.3.5/packages/ducrs/tests/synthetic_roundtrip.rs +0 -25
  26. ducpy-3.3.5/schema/migrations/3000003_to_3000004.sql +0 -20
  27. {ducpy-3.3.5 → ducpy-3.3.7}/Cargo.toml +0 -0
  28. {ducpy-3.3.5 → ducpy-3.3.7}/LICENSE +0 -0
  29. {ducpy-3.3.5 → ducpy-3.3.7}/README.md +0 -0
  30. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducpy/crate/src/lib.rs +0 -0
  31. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/LICENSE +0 -0
  32. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/README.md +0 -0
  33. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/build.rs +0 -0
  34. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/api/document.rs +0 -0
  35. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/api/meta.rs +0 -0
  36. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/api/mod.rs +0 -0
  37. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/api/version_control.rs +0 -0
  38. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/db/bootstrap.rs +0 -0
  39. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/db/mod.rs +0 -0
  40. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/db/native.rs +0 -0
  41. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/db/wasm.rs +0 -0
  42. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/lib.rs +0 -0
  43. {ducpy-3.3.5 → ducpy-3.3.7}/packages/ducrs/src/serde_utils.rs +0 -0
  44. {ducpy-3.3.5 → ducpy-3.3.7}/schema/migrations/3000000_to_3000001.sql +0 -0
  45. {ducpy-3.3.5 → ducpy-3.3.7}/schema/migrations/3000001_to_3000002.sql +0 -0
  46. {ducpy-3.3.5 → ducpy-3.3.7}/schema/migrations/3000002_to_3000003.sql +0 -0
  47. {ducpy-3.3.5 → ducpy-3.3.7}/schema/search.sql +0 -0
  48. {ducpy-3.3.5 → ducpy-3.3.7}/schema/version_control.sql +0 -0
  49. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/_version.py +0 -0
  50. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/__init__.py +0 -0
  51. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/block_instance_builder.py +0 -0
  52. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/block_utils.py +0 -0
  53. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/mutate_builder.py +0 -0
  54. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/sql_builder.py +0 -0
  55. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/builders/style_builders.py +0 -0
  56. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/classes/DataStateClass.py +0 -0
  57. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/classes/__init__.py +0 -0
  58. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/enums.py +0 -0
  59. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/parse.py +0 -0
  60. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/search/__init__.py +0 -0
  61. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/search/search_elements.py +0 -0
  62. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/constants.py +0 -0
  63. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/io.py +0 -0
  64. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/mutate_utils.py +0 -0
  65. {ducpy-3.3.5 → ducpy-3.3.7}/src/ducpy/utils/rand_utils.py +0 -0
  66. {ducpy-3.3.5 → ducpy-3.3.7}/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.5"
415
+ version = "3.3.7"
416
416
  dependencies = [
417
417
  "duc",
418
418
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducpy
3
- Version: 3.3.5
3
+ Version: 3.3.7
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Requires-Dist: nanoid>=2.0.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "ducpy-native"
3
- version = "3.3.5"
3
+ version = "3.3.7"
4
4
  edition = "2021"
5
5
  description = "Python native extension for the duc file format (used by ducpy)"
6
6
  publish = false
@@ -7,6 +7,17 @@ license = "MIT"
7
7
  authors = ["Ducflair support@ducflair.com"]
8
8
  repository = "https://github.com/ducflair/duc/tree/main/packages/ducrs"
9
9
  readme = "README.md"
10
+ include = [
11
+ "src/**",
12
+ "build.rs",
13
+ "Cargo.toml",
14
+ "README.md",
15
+ "LICENSE",
16
+ "schema/duc.sql",
17
+ "schema/version_control.sql",
18
+ "schema/search.sql",
19
+ "schema/migrations/*.sql",
20
+ ]
10
21
 
11
22
  [dependencies]
12
23
  base64 = "0.21.5"
@@ -1460,17 +1460,20 @@ fn read_pdf_element(conn: &Connection, base: DucElementBase) -> ParseResult<DucE
1460
1460
  }
1461
1461
 
1462
1462
  fn read_doc_element(conn: &Connection, base: DucElementBase) -> ParseResult<DucElementEnum> {
1463
+ let id = base.id.clone();
1463
1464
  let (file_id, grid_config) = read_document_grid_config(conn, &base.id)?;
1464
1465
  let mut stmt = conn.prepare_cached(
1465
1466
  "SELECT text FROM element_doc WHERE element_id = ?1"
1466
1467
  )?;
1467
1468
  let text: String = stmt.query_row(params![base.id], |row| row.get(0))?;
1469
+ let referenced_file_ids = read_doc_referenced_file_ids(conn, &id)?;
1468
1470
  Ok(DucElementEnum::DucDocElement(DucDocElement {
1469
1471
  base,
1470
1472
  style: DucDocStyle {},
1471
1473
  text,
1472
1474
  grid_config,
1473
1475
  file_id,
1476
+ referenced_file_ids,
1474
1477
  }))
1475
1478
  }
1476
1479
 
@@ -1512,11 +1515,7 @@ fn read_model_element(conn: &Connection, base: DucElementBase) -> ParseResult<Du
1512
1515
  Ok((row.get::<_, Option<String>>(0)?, row.get::<_, Option<String>>(1)?, row.get::<_, Option<Vec<u8>>>(2)?))
1513
1516
  })?;
1514
1517
 
1515
- let mut f_stmt = conn.prepare_cached(
1516
- "SELECT file_id FROM model_element_files WHERE element_id = ?1 ORDER BY sort_order"
1517
- )?;
1518
- let file_ids: Vec<String> = f_stmt.query_map(params![id], |row| row.get(0))?
1519
- .collect::<Result<Vec<_>, _>>()?;
1518
+ let file_ids = read_element_file_ids(conn, &id)?;
1520
1519
 
1521
1520
  let viewer_state = read_model_viewer_state(conn, &id)?;
1522
1521
 
@@ -1525,6 +1524,36 @@ fn read_model_element(conn: &Connection, base: DucElementBase) -> ParseResult<Du
1525
1524
  }))
1526
1525
  }
1527
1526
 
1527
+ fn read_element_file_ids(conn: &Connection, element_id: &str) -> ParseResult<Vec<String>> {
1528
+ let mut stmt = conn.prepare_cached(
1529
+ "SELECT file_id FROM model_element_files WHERE element_id = ?1 ORDER BY sort_order"
1530
+ )?;
1531
+ let file_ids = stmt.query_map(params![element_id], |row| row.get(0))?
1532
+ .collect::<Result<Vec<_>, _>>()?;
1533
+ Ok(file_ids)
1534
+ }
1535
+
1536
+ fn read_doc_referenced_file_ids(conn: &Connection, element_id: &str) -> ParseResult<Vec<String>> {
1537
+ if !table_exists(conn, "doc_element_referenced_files")? {
1538
+ return Ok(Vec::new());
1539
+ }
1540
+
1541
+ let mut stmt = conn.prepare_cached(
1542
+ "SELECT file_id FROM doc_element_referenced_files WHERE element_id = ?1 ORDER BY sort_order"
1543
+ )?;
1544
+ let file_ids = stmt.query_map(params![element_id], |row| row.get(0))?
1545
+ .collect::<Result<Vec<_>, _>>()?;
1546
+ Ok(file_ids)
1547
+ }
1548
+
1549
+ fn table_exists(conn: &Connection, table_name: &str) -> ParseResult<bool> {
1550
+ Ok(conn.query_row(
1551
+ "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1)",
1552
+ params![table_name],
1553
+ |row| row.get::<_, i64>(0),
1554
+ )? != 0)
1555
+ }
1556
+
1528
1557
  fn read_model_viewer_state(conn: &Connection, element_id: &str) -> ParseResult<Option<Viewer3DState>> {
1529
1558
  let mut stmt = conn.prepare_cached(
1530
1559
  "SELECT
@@ -456,6 +456,7 @@ fn write_element_wrapper(tx: &Transaction, wrapper: &ElementWrapper) -> Serializ
456
456
  "INSERT INTO element_doc (element_id, text) VALUES (?1, ?2)",
457
457
  params![e.base.id, e.text],
458
458
  )?;
459
+ write_doc_referenced_file_ids(tx, &e.base.id, &e.referenced_file_ids)?;
459
460
  }
460
461
  DucElementEnum::DucTableElement(e) => {
461
462
  write_base_element(tx, "table", &e.base)?;
@@ -834,14 +835,7 @@ fn write_model_element(tx: &Transaction, e: &DucModelElement) -> SerializeResult
834
835
  params![e.base.id, e.model_type, e.code, e.thumbnail],
835
836
  )?;
836
837
 
837
- {
838
- let mut stmt = tx.prepare_cached(
839
- "INSERT INTO model_element_files (element_id, file_id, sort_order) VALUES (?1, ?2, ?3)"
840
- )?;
841
- for (i, fid) in e.file_ids.iter().enumerate() {
842
- stmt.execute(params![e.base.id, fid, i as i32])?;
843
- }
844
- }
838
+ write_element_file_ids(tx, &e.base.id, &e.file_ids)?;
845
839
 
846
840
  if let Some(ref vs) = e.viewer_state {
847
841
  write_model_viewer_state(tx, &e.base.id, vs)?;
@@ -850,6 +844,26 @@ fn write_model_element(tx: &Transaction, e: &DucModelElement) -> SerializeResult
850
844
  Ok(())
851
845
  }
852
846
 
847
+ fn write_element_file_ids(tx: &Transaction, element_id: &str, file_ids: &[String]) -> SerializeResult<()> {
848
+ let mut stmt = tx.prepare_cached(
849
+ "INSERT INTO model_element_files (element_id, file_id, sort_order) VALUES (?1, ?2, ?3)"
850
+ )?;
851
+ for (i, fid) in file_ids.iter().enumerate() {
852
+ stmt.execute(params![element_id, fid, i as i32])?;
853
+ }
854
+ Ok(())
855
+ }
856
+
857
+ fn write_doc_referenced_file_ids(tx: &Transaction, element_id: &str, file_ids: &[String]) -> SerializeResult<()> {
858
+ let mut stmt = tx.prepare_cached(
859
+ "INSERT INTO doc_element_referenced_files (element_id, file_id, sort_order) VALUES (?1, ?2, ?3)"
860
+ )?;
861
+ for (i, fid) in file_ids.iter().enumerate() {
862
+ stmt.execute(params![element_id, fid, i as i32])?;
863
+ }
864
+ Ok(())
865
+ }
866
+
853
867
  fn write_model_viewer_state(tx: &Transaction, element_id: &str, vs: &Viewer3DState) -> SerializeResult<()> {
854
868
  let cam = &vs.camera;
855
869
  let disp = &vs.display;
@@ -1045,6 +1045,8 @@ pub struct DucDocElement {
1045
1045
  pub text: String,
1046
1046
  pub grid_config: DocumentGridConfig,
1047
1047
  pub file_id: Option<String>,
1048
+ #[serde(default)]
1049
+ pub referenced_file_ids: Vec<String>,
1048
1050
  }
1049
1051
 
1050
1052
  #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -51,6 +51,10 @@ dev = [
51
51
  "sphinx-autoapi>=3.6.0",
52
52
  "pytest>=8.3.5",
53
53
  "rich>=13.0.0",
54
+ "typst>=0.14.9",
55
+ "build123d>=0.10.0",
56
+ "ocp-vscode>=3.4.0",
57
+ "ifcopenshell>=0.8.5",
54
58
  ]
55
59
 
56
60
 
@@ -1,7 +1,7 @@
1
1
  -- "DUC_" in ASCII
2
2
  -- Apply in order: duc.sql → version_control.sql → search.sql
3
3
  PRAGMA application_id = 1146569567;
4
- PRAGMA user_version = 3000003;
4
+ PRAGMA user_version = 3000004;
5
5
  PRAGMA journal_mode = WAL;
6
6
  PRAGMA foreign_keys = ON;
7
7
  PRAGMA synchronous = NORMAL;
@@ -702,6 +702,16 @@ CREATE TABLE element_doc (
702
702
  text TEXT NOT NULL DEFAULT ''
703
703
  ); -- no WITHOUT ROWID: implicit integer rowid needed for FTS5 content sync
704
704
 
705
+ -- External files referenced by a doc element's Typst source (images, data files, etc.).
706
+ CREATE TABLE doc_element_referenced_files (
707
+ element_id TEXT NOT NULL REFERENCES element_doc(element_id) ON DELETE CASCADE,
708
+ file_id TEXT NOT NULL,
709
+ sort_order INTEGER NOT NULL DEFAULT 0,
710
+ PRIMARY KEY (element_id, file_id)
711
+ ) WITHOUT ROWID;
712
+
713
+ CREATE INDEX idx_doc_referenced_files_file ON doc_element_referenced_files(file_id);
714
+
705
715
  -- Table element. Source of truth is the linked xlsx file.
706
716
  CREATE TABLE element_table (
707
717
  element_id TEXT PRIMARY KEY REFERENCES elements(id) ON DELETE CASCADE,
@@ -844,4 +854,4 @@ CREATE INDEX idx_external_file_revisions_file ON external_file_revisions(file_id
844
854
  CREATE TABLE external_file_revision_data (
845
855
  revision_id TEXT PRIMARY KEY REFERENCES external_file_revisions(id) ON DELETE CASCADE,
846
856
  data BLOB NOT NULL
847
- ) WITHOUT ROWID;
857
+ ) WITHOUT ROWID;
@@ -0,0 +1,41 @@
1
+ -- Migration: 3000003 → 3000004
2
+ -- Rename element_model.svg_path (TEXT) to thumbnail (BLOB), and add dedicated
3
+ -- references for external files used by doc element Typst source.
4
+
5
+ CREATE TABLE element_model_new (
6
+ element_id TEXT PRIMARY KEY REFERENCES elements(id) ON DELETE CASCADE,
7
+ model_type TEXT,
8
+ code TEXT,
9
+ thumbnail BLOB
10
+ );
11
+
12
+ INSERT INTO element_model_new (element_id, model_type, code, thumbnail)
13
+ SELECT element_id, model_type, code, CAST(svg_path AS BLOB)
14
+ FROM element_model;
15
+
16
+ DROP TABLE element_model;
17
+
18
+ ALTER TABLE element_model_new RENAME TO element_model;
19
+
20
+ CREATE INDEX idx_element_model_type ON element_model(model_type);
21
+
22
+ CREATE TABLE IF NOT EXISTS doc_element_referenced_files (
23
+ element_id TEXT NOT NULL REFERENCES element_doc(element_id) ON DELETE CASCADE,
24
+ file_id TEXT NOT NULL,
25
+ sort_order INTEGER NOT NULL DEFAULT 0,
26
+ PRIMARY KEY (element_id, file_id)
27
+ ) WITHOUT ROWID;
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_doc_referenced_files_file ON doc_element_referenced_files(file_id);
30
+
31
+ -- Defensive cleanup for prerelease files that may have stored doc references in
32
+ -- model_element_files before this dedicated table existed.
33
+ INSERT OR IGNORE INTO doc_element_referenced_files (element_id, file_id, sort_order)
34
+ SELECT mf.element_id, mf.file_id, mf.sort_order
35
+ FROM model_element_files mf
36
+ INNER JOIN element_doc d ON d.element_id = mf.element_id;
37
+
38
+ DELETE FROM model_element_files
39
+ WHERE element_id IN (SELECT element_id FROM element_doc);
40
+
41
+ PRAGMA user_version = 3000004;
@@ -27,6 +27,6 @@ from .classes import *
27
27
  from .enums import *
28
28
  from .parse import (DucData, get_external_file, list_external_files, parse_duc,
29
29
  parse_duc_lazy)
30
- from .serialize import DUC_SCHEMA_VERSION, serialize_duc
30
+ from .serialize import DUC_SCHEMA_VERSION, DucSerializationValidationError, serialize_duc
31
31
  from .search import *
32
32
  from .utils import *
@@ -55,45 +55,46 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit
55
55
  base_params.setdefault('region_ids', [])
56
56
  base_params.setdefault('instance_id', None)
57
57
 
58
- base_params.setdefault('styles', DucElementStylesBase(
59
- roundness=0.0,
60
- stroke=[ElementStroke(
61
- content=ElementContentBase(
62
- preference=None,
63
- src="",
64
- visible=True,
65
- opacity=1.0,
66
- tiling=None,
67
- hatch=None,
68
- image_filter=DucImageFilter(brightness=1.0, contrast=1.0)
69
- ),
70
- width=DEFAULT_STROKE_WIDTH,
71
- style=StrokeStyle(
72
- preference=STROKE_PREFERENCE.SOLID,
73
- dash=None,
74
- dash_line_override=None,
75
- cap=None,
76
- join=None,
77
- dash_cap=None,
78
- miter_limit=None
79
- ),
80
- placement=STROKE_PLACEMENT.CENTER,
81
- stroke_sides=None
82
- )],
83
- background=[ElementBackground(
84
- content=ElementContentBase(
85
- preference=None,
86
- src="",
87
- visible=True,
88
- opacity=1.0,
89
- tiling=None,
90
- hatch=None,
91
- image_filter=DucImageFilter(brightness=1.0, contrast=1.0)
92
- )
93
- )],
94
- opacity=1.0,
95
- blending=None
96
- ))
58
+ if base_params.get('styles') is None:
59
+ base_params['styles'] = DucElementStylesBase(
60
+ roundness=0.0,
61
+ stroke=[ElementStroke(
62
+ content=ElementContentBase(
63
+ preference=None,
64
+ src="",
65
+ visible=True,
66
+ opacity=1.0,
67
+ tiling=None,
68
+ hatch=None,
69
+ image_filter=DucImageFilter(brightness=1.0, contrast=1.0)
70
+ ),
71
+ width=DEFAULT_STROKE_WIDTH,
72
+ style=StrokeStyle(
73
+ preference=STROKE_PREFERENCE.SOLID,
74
+ dash=None,
75
+ dash_line_override=None,
76
+ cap=None,
77
+ join=None,
78
+ dash_cap=None,
79
+ miter_limit=None
80
+ ),
81
+ placement=STROKE_PLACEMENT.CENTER,
82
+ stroke_sides=None
83
+ )],
84
+ background=[ElementBackground(
85
+ content=ElementContentBase(
86
+ preference=None,
87
+ src="",
88
+ visible=True,
89
+ opacity=1.0,
90
+ tiling=None,
91
+ hatch=None,
92
+ image_filter=DucImageFilter(brightness=1.0, contrast=1.0)
93
+ )
94
+ )],
95
+ opacity=1.0,
96
+ blending=None
97
+ )
97
98
 
98
99
  base_element = DucElementBase(**base_params)
99
100
 
@@ -168,8 +169,9 @@ def _create_element_wrapper(element_class, base_params, element_params, explicit
168
169
  base=base_element,
169
170
  style=element_params.get('style') or DucDocStyle(),
170
171
  text=element_params.get('text', ""),
171
- grid_config=element_params.get('grid_config', default_grid_config),
172
+ grid_config=element_params.get('grid_config') or default_grid_config,
172
173
  file_id=element_params.get('file_id'),
174
+ referenced_file_ids=element_params.get('referenced_file_ids') or [],
173
175
  )
174
176
  elif element_class == DucFrameElement:
175
177
  stack_base = element_params.get('stack_base')
@@ -698,6 +700,8 @@ class DocElementBuilder(ElementSpecificBuilder):
698
700
  self.extra["style"] = style; return self
699
701
  def with_file_id(self, file_id: str):
700
702
  self.extra["file_id"] = file_id; return self
703
+ def with_referenced_file_ids(self, referenced_file_ids: List[str]):
704
+ self.extra["referenced_file_ids"] = referenced_file_ids; return self
701
705
  def with_grid_config(self, grid_config: DocumentGridConfig):
702
706
  self.extra["grid_config"] = grid_config; return self
703
707
 
@@ -707,6 +711,7 @@ class DocElementBuilder(ElementSpecificBuilder):
707
711
  "style": self.extra.get('style'),
708
712
  "text": self.extra.get('text', ""),
709
713
  "file_id": self.extra.get('file_id'),
714
+ "referenced_file_ids": self.extra.get('referenced_file_ids', []),
710
715
  "grid_config": self.extra.get('grid_config'),
711
716
  }
712
717
  return _create_element_wrapper(DucDocElement, base_params, element_params,
@@ -727,11 +732,12 @@ class EmbeddableElementBuilder(ElementSpecificBuilder):
727
732
 
728
733
  class ModelElementBuilder(ElementSpecificBuilder):
729
734
  def with_model_type(self, model_type: str):
735
+ valid_types = {"python", "ifc", "dxf", "dwg", "step", "stl"}
736
+ if model_type not in valid_types:
737
+ raise ValueError(f"Invalid model_type: {model_type}. Allowed types: {sorted(list(valid_types))}")
730
738
  self.extra["model_type"] = model_type; return self
731
739
  def with_code(self, code: str):
732
740
  self.extra["code"] = code; return self
733
- def with_svg_path(self, svg_path: str):
734
- self.extra["svg_path"] = svg_path; return self
735
741
  def with_file_ids(self, file_ids: List[str]):
736
742
  self.extra["file_ids"] = file_ids; return self
737
743
  def with_viewer_state(self, viewer_state):
@@ -739,11 +745,15 @@ class ModelElementBuilder(ElementSpecificBuilder):
739
745
 
740
746
  def build(self) -> ElementWrapper:
741
747
  base_params = self.base.__dict__.copy()
748
+ model_type = self.extra.get('model_type', 'python')
749
+ valid_types = {"python", "ifc", "dxf", "dwg", "step", "stl"}
750
+ if model_type not in valid_types:
751
+ raise ValueError(f"Invalid model_type: {model_type}. Allowed types: {sorted(list(valid_types))}")
752
+
742
753
  element_params = {
743
754
  "file_ids": self.extra.get('file_ids', []),
744
- "model_type": self.extra.get('model_type'),
755
+ "model_type": model_type,
745
756
  "code": self.extra.get('code'),
746
- "svg_path": self.extra.get('svg_path'),
747
757
  "viewer_state": self.extra.get('viewer_state'),
748
758
  }
749
759
  return _create_element_wrapper(DucModelElement, base_params, element_params,
@@ -446,9 +446,9 @@ def create_local_state_from_base(base: BaseStateParams, **kwargs) -> DucLocalSta
446
446
  current_item_stroke=kwargs.get('current_item_stroke', None),
447
447
  current_item_background=kwargs.get('current_item_background', None),
448
448
  current_item_opacity=kwargs.get('current_item_opacity', 1.0),
449
- current_item_font_family=kwargs.get('current_item_font_family', "Virgil"),
449
+ current_item_font_family=kwargs.get('current_item_font_family', "Roboto Mono"),
450
450
  current_item_font_size=kwargs.get('current_item_font_size', 20.0),
451
- current_item_text_align=kwargs.get('current_item_text_align', None),
451
+ current_item_text_align=kwargs.get('current_item_text_align', TEXT_ALIGN.LEFT),
452
452
  current_item_roundness=kwargs.get('current_item_roundness', 0.0),
453
453
  current_item_start_line_head=kwargs.get('current_item_start_line_head', None),
454
454
  current_item_end_line_head=kwargs.get('current_item_end_line_head', None),
@@ -457,7 +457,7 @@ def create_local_state_from_base(base: BaseStateParams, **kwargs) -> DucLocalSta
457
457
  objects_snap_mode_enabled=kwargs.get('objects_snap_mode_enabled', True),
458
458
  grid_mode_enabled=kwargs.get('grid_mode_enabled', True),
459
459
  outline_mode_enabled=kwargs.get('outline_mode_enabled', False),
460
- manual_save_mode=kwargs.get('manual_save_mode', None),
460
+ manual_save_mode=kwargs.get('manual_save_mode', False),
461
461
  decimal_places=kwargs.get('decimal_places', 2),
462
462
  )
463
463
 
@@ -393,6 +393,7 @@ class DucDocElement:
393
393
  text: str
394
394
  grid_config: DocumentGridConfig
395
395
  file_id: Optional[str] = None
396
+ referenced_file_ids: Optional[List[str]] = None
396
397
 
397
398
  @dataclass
398
399
  class DucTableElement:
@@ -482,6 +483,13 @@ class DucModelElement:
482
483
  thumbnail: Optional[bytes] = None
483
484
  viewer_state: Optional[Viewer3DState] = None
484
485
 
486
+ def __post_init__(self):
487
+ valid_types = {"python", "ifc", "dxf", "dwg", "step", "stl"}
488
+ if self.model_type is None:
489
+ self.model_type = "python"
490
+ if self.model_type not in valid_types:
491
+ raise ValueError(f"Invalid model_type: {self.model_type}. Allowed types: {sorted(list(valid_types))}")
492
+
485
493
 
486
494
  # =============== BLOCK DEFINITIONS ===============
487
495