python-materialsdb 0.1.0__tar.gz → 0.3.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.
Files changed (64) hide show
  1. python_materialsdb-0.3.0/PKG-INFO +179 -0
  2. python_materialsdb-0.3.0/README.md +152 -0
  3. python_materialsdb-0.3.0/pyproject.toml +98 -0
  4. python_materialsdb-0.3.0/setup.cfg +4 -0
  5. python_materialsdb-0.3.0/src/materialsdb/__init__.py +3 -0
  6. python_materialsdb-0.3.0/src/materialsdb/cache.py +141 -0
  7. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/classes.py +14 -6
  8. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/config.py +3 -6
  9. python_materialsdb-0.3.0/src/materialsdb/construction.py +464 -0
  10. python_materialsdb-0.3.0/src/materialsdb/gui/__main__.py +43 -0
  11. python_materialsdb-0.3.0/src/materialsdb/gui/discovery.py +28 -0
  12. python_materialsdb-0.3.0/src/materialsdb/gui/listener.py +155 -0
  13. python_materialsdb-0.3.0/src/materialsdb/gui/server.py +832 -0
  14. python_materialsdb-0.3.0/src/materialsdb/gui/static/app-constructions.js +677 -0
  15. python_materialsdb-0.3.0/src/materialsdb/gui/static/app.js +516 -0
  16. python_materialsdb-0.3.0/src/materialsdb/gui/static/constructions.html +107 -0
  17. python_materialsdb-0.3.0/src/materialsdb/gui/static/index.html +58 -0
  18. python_materialsdb-0.3.0/src/materialsdb/gui/static/picker-core.js +111 -0
  19. python_materialsdb-0.3.0/src/materialsdb/ifc/__init__.py +0 -0
  20. python_materialsdb-0.3.0/src/materialsdb/ifc/material_builder.py +295 -0
  21. python_materialsdb-0.3.0/src/materialsdb/ifc/material_psets.json +596 -0
  22. python_materialsdb-0.3.0/src/materialsdb/ifc/project_library.py +164 -0
  23. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/query.py +3 -3
  24. python_materialsdb-0.3.0/src/materialsdb/schema/MaterialsDBIndex100.xsd +60 -0
  25. python_materialsdb-0.3.0/src/materialsdb/schema/materialsdb102.xsd +820 -0
  26. python_materialsdb-0.3.0/src/materialsdb/schema/materialsdb103.xsd +938 -0
  27. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/serialiser.py +24 -20
  28. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/store.py +78 -75
  29. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/summary.py +30 -22
  30. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/materialsdb/utils.py +15 -21
  31. python_materialsdb-0.3.0/src/python_materialsdb.egg-info/PKG-INFO +179 -0
  32. python_materialsdb-0.3.0/src/python_materialsdb.egg-info/SOURCES.txt +52 -0
  33. python_materialsdb-0.3.0/src/python_materialsdb.egg-info/entry_points.txt +2 -0
  34. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/python_materialsdb.egg-info/requires.txt +3 -0
  35. python_materialsdb-0.3.0/tests/test_bonsai_discovery.py +90 -0
  36. python_materialsdb-0.3.0/tests/test_bonsai_insert.py +515 -0
  37. python_materialsdb-0.3.0/tests/test_build_bonsai_addon.py +37 -0
  38. python_materialsdb-0.3.0/tests/test_cache.py +135 -0
  39. python_materialsdb-0.3.0/tests/test_construction.py +390 -0
  40. python_materialsdb-0.3.0/tests/test_construction_frontend.py +41 -0
  41. python_materialsdb-0.3.0/tests/test_construction_ifc.py +83 -0
  42. python_materialsdb-0.3.0/tests/test_gui_server.py +1025 -0
  43. python_materialsdb-0.3.0/tests/test_listener_payload.py +262 -0
  44. python_materialsdb-0.3.0/tests/test_material_builder.py +305 -0
  45. python_materialsdb-0.3.0/tests/test_picker_frontend.py +40 -0
  46. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/tests/test_query.py +1 -0
  47. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/tests/test_serialiser.py +1 -4
  48. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/tests/test_store.py +89 -3
  49. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/tests/test_summary.py +27 -0
  50. python_materialsdb-0.1.0/PKG-INFO +0 -83
  51. python_materialsdb-0.1.0/README.md +0 -58
  52. python_materialsdb-0.1.0/pyproject.toml +0 -14
  53. python_materialsdb-0.1.0/setup.cfg +0 -43
  54. python_materialsdb-0.1.0/src/materialsdb/__init__.py +0 -1
  55. python_materialsdb-0.1.0/src/materialsdb/cache.py +0 -115
  56. python_materialsdb-0.1.0/src/materialsdb/ifc/material_psets.json +0 -582
  57. python_materialsdb-0.1.0/src/materialsdb/ifc/project_library.py +0 -299
  58. python_materialsdb-0.1.0/src/python_materialsdb.egg-info/PKG-INFO +0 -83
  59. python_materialsdb-0.1.0/src/python_materialsdb.egg-info/SOURCES.txt +0 -26
  60. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/LICENSE.md +0 -0
  61. {python_materialsdb-0.1.0/src/materialsdb/ifc → python_materialsdb-0.3.0/src/materialsdb/gui}/__init__.py +0 -0
  62. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/python_materialsdb.egg-info/dependency_links.txt +0 -0
  63. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/src/python_materialsdb.egg-info/top_level.txt +0 -0
  64. {python_materialsdb-0.1.0 → python_materialsdb-0.3.0}/tests/test_project_library.py +0 -0
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-materialsdb
3
+ Version: 0.3.0
4
+ Summary: A library to work with materialsdb.org open standard for building materials.
5
+ Author-email: Cyril Waechter <cyrwae@hotmail.com>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/CyrilWaechter/python-materialsdb
8
+ Project-URL: Bug Tracker, https://github.com/CyrilWaechter/python-materialsdb/issues
9
+ Project-URL: Community, https://community.osarch.org/
10
+ Keywords: materials,BIM,ifcopenshell,IFC
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.md
18
+ Requires-Dist: lxml
19
+ Provides-Extra: ifc
20
+ Requires-Dist: ifcopenshell; extra == "ifc"
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest; extra == "dev"
23
+ Requires-Dist: pytest-benchmark; extra == "dev"
24
+ Requires-Dist: ruff==0.16.4; extra == "dev"
25
+ Requires-Dist: ty==0.0.74; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ python-materialsdb is an unofficial python library for [materialsdb.org][1] an open format and database for building materials.
29
+
30
+ # Features :
31
+ ## Package
32
+ * serialiser.py :
33
+ * from xml : deserialise from materialsdb*.xsd compliant xml file
34
+ * to xml : serealise classes to a materialsdb*.xsd compliant xml file
35
+ * classes.py : generated classes corresponding to XML elements
36
+ * cache.py : cache latest materials data from producers
37
+ * config.py : set and get user config as language and country
38
+ * ifc/project_library.py : convert deserialised source into IFC (IfcProjectLibrary)
39
+ * gui/server.py : stdlib-only web application to browse and export cached materials (materialsdb-gui)
40
+
41
+ ## devutils
42
+ * classes_generator.py : generate classes (dataclasses except for simple type) for materialsdb*.xsd elements
43
+
44
+ # config
45
+ Materials data are often localized. You can set your language and country this way:
46
+ ```python
47
+ from materialsdb import config
48
+
49
+ config.set_lang("fr")
50
+ config.set_country("CH")
51
+ ```
52
+ Note: in materialsdb standard languages are [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes and countries are [ISO_3166-1_alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes.
53
+
54
+ # Usage examples :
55
+ Check out some [examples](examples):
56
+ * [Convert latest materials data to ifc](examples/generate_ifc_project_libraries.py)
57
+ * [Create your own materialsdb.org compliant XML](examples/create_layers.py)
58
+
59
+ # Querying materials :
60
+ The library keeps an sqlite index of the cached materials data for fast
61
+ filtering and single-material access:
62
+
63
+ ```python
64
+ from materialsdb import query
65
+
66
+ query.refresh() # incremental update from cached xml
67
+ rows = query.search("isolant", sort="lambda") # filtered, sorted summaries
68
+ material = query.get_material(rows[0].id) # full material dataclass
69
+ ```
70
+
71
+ # Create a single material in IFC :
72
+ Append one material into an existing ifcopenshell file (idempotent), or build a minimal standalone file.
73
+
74
+ ```python
75
+ from materialsdb import query
76
+ from materialsdb.ifc.material_builder import add_material, create_material_file
77
+
78
+ material = query.get_material("<materialsdb-id>")
79
+
80
+ add_material(existing_ifc_file, material, company="Producer") # idempotent append
81
+ file = create_material_file("<materialsdb-id>") # standalone .ifc
82
+ file.write("single_material.ifc")
83
+ ```
84
+
85
+ # Material picker GUI :
86
+ Launch the local web application (stdlib only, no extra dependencies):
87
+
88
+ ```bash
89
+ materialsdb-gui # opens http://127.0.0.1:8619 in your browser
90
+ ```
91
+
92
+ Or run straight from a source checkout without installing:
93
+
94
+ ```bash
95
+ PYTHONPATH=src python3 -m materialsdb.gui
96
+ ```
97
+
98
+ The materials index uses schema v2: the first launch after upgrading rebuilds
99
+ it automatically (previous index is discarded — source of truth is your cached
100
+ XML). *Refresh cache* downloads any newer producer files from
101
+ materialsdb.org and re-ingests them — on a fresh machine it fills the
102
+ empty list after one click, behind a progress dialog (cancellable). The
103
+ app shows a notice when updates are available on materialsdb.org.
104
+
105
+ Browse, sort and filter all cached materials; multi-select then either export
106
+ a standalone `.ifc`, or open one of your own `.ifc` files and append the
107
+ selected materials into it. The same HTTP API powers future BIM software
108
+ plugins (all mutating calls require a per-launch token).
109
+
110
+ # Construction maker :
111
+ Compose thermal constructions from materialsdb materials and compute their
112
+ U-value (ISO 6946 / SIA 180 surface resistance presets):
113
+
114
+ ```bash
115
+ PYTHONPATH=src python3 -m materialsdb.gui # then open constructions.html
116
+ ```
117
+
118
+ Create a construction, add materials from the catalog, adjust layer
119
+ thicknesses in millimetres and read the resulting U-value live. Save
120
+ constructions as JSON in your cache directory, export them as standalone
121
+ `.ifc` files containing an `IfcMaterialLayerSet`, or append them into an
122
+ already-open session file.
123
+
124
+ You can also push the construction straight into the IFC model open in
125
+ Bonsai (same target picker as the materials page): it is created or updated
126
+ as an `IfcWallType`/`IfcSlabType`/`IfcRoofType` (per the design usage,
127
+ generic creating all three) with its `IfcMaterialLayerSet` — undoable with
128
+ Ctrl+Z.
129
+
130
+ # Bonsai integration :
131
+ Push materials straight from the picker into the IFC model open in
132
+ [Bonsai](https://bonsai.ifcopenshell.org). Install it from the
133
+ extension repository: in Blender, *Preferences ▸ Get Extensions ▸ ⌄ ▸
134
+ Add Remote Repository* and add
135
+ `https://cyrilwaechter.github.io/python-materialsdb/index.json`, then
136
+ install
137
+ *materialsdb listener* (updates appear in the same panel). Manual
138
+ install stays available: build `dist/materialsdb_listener.zip` with
139
+ `python3 dev_utils/build_bonsai_addon.py` and use
140
+ *Extensions ▸ Install from Disk*. Start the listener from the
141
+ materialsdb panel in the 3D-view sidebar. The panel can also start the
142
+ GUI server itself (*Start server & open picker*) — it needs
143
+ `pip install python-materialsdb` in a system Python (set the
144
+ interpreter in the add-on preferences if autodiscovery picks the wrong
145
+ one). Then run `materialsdb-gui` as
146
+ usual and hit *send to:* (picking the target instance) in the picker or
147
+ the construction maker. Materials are registered
148
+ into the model's material library (identity + per-layer psets included);
149
+ assigning them to objects stays a normal Bonsai action. Pushed
150
+ constructions become typed elements (`IfcWallType`/`IfcSlabType`/
151
+ `IfcRoofType` per the design usage) with their `IfcMaterialLayerSet`,
152
+ visible in the outliner and revertible with Ctrl+Z. Each pushed type
153
+ also carries its computed U-value as `ThermalTransmittance` in
154
+ `Pset_WallCommon`/`Pset_SlabCommon`/`Pset_RoofCommon` (per the element
155
+ type). Round-trip: select
156
+ the wall (or its type) in Bonsai and hit *Send type to composer* in the
157
+ materialsdb panel — the construction appears under *incoming from model*
158
+ on the constructions page for editing. Any layer's material can be
159
+ replaced from the catalog with the pencil button or a double-click on
160
+ the material row. Layers whose material is not a
161
+ materialsdb material are kept as "model material" placeholders (λ read
162
+ from the model) and re-attach to the same material on push-back.
163
+
164
+ # How to install
165
+ ## Using pip
166
+ ```bash
167
+ pip install python-materialsdb
168
+ ```
169
+
170
+ # Dependencies
171
+ * [lxml][2] (BSD) : xml parser (tested with version 6.1.1)
172
+ * [ifcopenshell][3] (LGPL) : ifc read/write (tested with version 0.8.5)
173
+
174
+ # Third parties :
175
+ * [materialsdb.org][1] (GPL) : materials schema
176
+
177
+ [1]: http://www.materialsdb.org
178
+ [2]: https://lxml.de
179
+ [3]: ifcopenshell.org
@@ -0,0 +1,152 @@
1
+ python-materialsdb is an unofficial python library for [materialsdb.org][1] an open format and database for building materials.
2
+
3
+ # Features :
4
+ ## Package
5
+ * serialiser.py :
6
+ * from xml : deserialise from materialsdb*.xsd compliant xml file
7
+ * to xml : serealise classes to a materialsdb*.xsd compliant xml file
8
+ * classes.py : generated classes corresponding to XML elements
9
+ * cache.py : cache latest materials data from producers
10
+ * config.py : set and get user config as language and country
11
+ * ifc/project_library.py : convert deserialised source into IFC (IfcProjectLibrary)
12
+ * gui/server.py : stdlib-only web application to browse and export cached materials (materialsdb-gui)
13
+
14
+ ## devutils
15
+ * classes_generator.py : generate classes (dataclasses except for simple type) for materialsdb*.xsd elements
16
+
17
+ # config
18
+ Materials data are often localized. You can set your language and country this way:
19
+ ```python
20
+ from materialsdb import config
21
+
22
+ config.set_lang("fr")
23
+ config.set_country("CH")
24
+ ```
25
+ Note: in materialsdb standard languages are [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes and countries are [ISO_3166-1_alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes.
26
+
27
+ # Usage examples :
28
+ Check out some [examples](examples):
29
+ * [Convert latest materials data to ifc](examples/generate_ifc_project_libraries.py)
30
+ * [Create your own materialsdb.org compliant XML](examples/create_layers.py)
31
+
32
+ # Querying materials :
33
+ The library keeps an sqlite index of the cached materials data for fast
34
+ filtering and single-material access:
35
+
36
+ ```python
37
+ from materialsdb import query
38
+
39
+ query.refresh() # incremental update from cached xml
40
+ rows = query.search("isolant", sort="lambda") # filtered, sorted summaries
41
+ material = query.get_material(rows[0].id) # full material dataclass
42
+ ```
43
+
44
+ # Create a single material in IFC :
45
+ Append one material into an existing ifcopenshell file (idempotent), or build a minimal standalone file.
46
+
47
+ ```python
48
+ from materialsdb import query
49
+ from materialsdb.ifc.material_builder import add_material, create_material_file
50
+
51
+ material = query.get_material("<materialsdb-id>")
52
+
53
+ add_material(existing_ifc_file, material, company="Producer") # idempotent append
54
+ file = create_material_file("<materialsdb-id>") # standalone .ifc
55
+ file.write("single_material.ifc")
56
+ ```
57
+
58
+ # Material picker GUI :
59
+ Launch the local web application (stdlib only, no extra dependencies):
60
+
61
+ ```bash
62
+ materialsdb-gui # opens http://127.0.0.1:8619 in your browser
63
+ ```
64
+
65
+ Or run straight from a source checkout without installing:
66
+
67
+ ```bash
68
+ PYTHONPATH=src python3 -m materialsdb.gui
69
+ ```
70
+
71
+ The materials index uses schema v2: the first launch after upgrading rebuilds
72
+ it automatically (previous index is discarded — source of truth is your cached
73
+ XML). *Refresh cache* downloads any newer producer files from
74
+ materialsdb.org and re-ingests them — on a fresh machine it fills the
75
+ empty list after one click, behind a progress dialog (cancellable). The
76
+ app shows a notice when updates are available on materialsdb.org.
77
+
78
+ Browse, sort and filter all cached materials; multi-select then either export
79
+ a standalone `.ifc`, or open one of your own `.ifc` files and append the
80
+ selected materials into it. The same HTTP API powers future BIM software
81
+ plugins (all mutating calls require a per-launch token).
82
+
83
+ # Construction maker :
84
+ Compose thermal constructions from materialsdb materials and compute their
85
+ U-value (ISO 6946 / SIA 180 surface resistance presets):
86
+
87
+ ```bash
88
+ PYTHONPATH=src python3 -m materialsdb.gui # then open constructions.html
89
+ ```
90
+
91
+ Create a construction, add materials from the catalog, adjust layer
92
+ thicknesses in millimetres and read the resulting U-value live. Save
93
+ constructions as JSON in your cache directory, export them as standalone
94
+ `.ifc` files containing an `IfcMaterialLayerSet`, or append them into an
95
+ already-open session file.
96
+
97
+ You can also push the construction straight into the IFC model open in
98
+ Bonsai (same target picker as the materials page): it is created or updated
99
+ as an `IfcWallType`/`IfcSlabType`/`IfcRoofType` (per the design usage,
100
+ generic creating all three) with its `IfcMaterialLayerSet` — undoable with
101
+ Ctrl+Z.
102
+
103
+ # Bonsai integration :
104
+ Push materials straight from the picker into the IFC model open in
105
+ [Bonsai](https://bonsai.ifcopenshell.org). Install it from the
106
+ extension repository: in Blender, *Preferences ▸ Get Extensions ▸ ⌄ ▸
107
+ Add Remote Repository* and add
108
+ `https://cyrilwaechter.github.io/python-materialsdb/index.json`, then
109
+ install
110
+ *materialsdb listener* (updates appear in the same panel). Manual
111
+ install stays available: build `dist/materialsdb_listener.zip` with
112
+ `python3 dev_utils/build_bonsai_addon.py` and use
113
+ *Extensions ▸ Install from Disk*. Start the listener from the
114
+ materialsdb panel in the 3D-view sidebar. The panel can also start the
115
+ GUI server itself (*Start server & open picker*) — it needs
116
+ `pip install python-materialsdb` in a system Python (set the
117
+ interpreter in the add-on preferences if autodiscovery picks the wrong
118
+ one). Then run `materialsdb-gui` as
119
+ usual and hit *send to:* (picking the target instance) in the picker or
120
+ the construction maker. Materials are registered
121
+ into the model's material library (identity + per-layer psets included);
122
+ assigning them to objects stays a normal Bonsai action. Pushed
123
+ constructions become typed elements (`IfcWallType`/`IfcSlabType`/
124
+ `IfcRoofType` per the design usage) with their `IfcMaterialLayerSet`,
125
+ visible in the outliner and revertible with Ctrl+Z. Each pushed type
126
+ also carries its computed U-value as `ThermalTransmittance` in
127
+ `Pset_WallCommon`/`Pset_SlabCommon`/`Pset_RoofCommon` (per the element
128
+ type). Round-trip: select
129
+ the wall (or its type) in Bonsai and hit *Send type to composer* in the
130
+ materialsdb panel — the construction appears under *incoming from model*
131
+ on the constructions page for editing. Any layer's material can be
132
+ replaced from the catalog with the pencil button or a double-click on
133
+ the material row. Layers whose material is not a
134
+ materialsdb material are kept as "model material" placeholders (λ read
135
+ from the model) and re-attach to the same material on push-back.
136
+
137
+ # How to install
138
+ ## Using pip
139
+ ```bash
140
+ pip install python-materialsdb
141
+ ```
142
+
143
+ # Dependencies
144
+ * [lxml][2] (BSD) : xml parser (tested with version 6.1.1)
145
+ * [ifcopenshell][3] (LGPL) : ifc read/write (tested with version 0.8.5)
146
+
147
+ # Third parties :
148
+ * [materialsdb.org][1] (GPL) : materials schema
149
+
150
+ [1]: http://www.materialsdb.org
151
+ [2]: https://lxml.de
152
+ [3]: ifcopenshell.org
@@ -0,0 +1,98 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-materialsdb"
7
+ version = "0.3.0"
8
+ description = "A library to work with materialsdb.org open standard for building materials."
9
+ readme = "README.md"
10
+ license = "GPL-3.0-or-later"
11
+ authors = [{ name = "Cyril Waechter", email = "cyrwae@hotmail.com" }]
12
+ keywords = ["materials", "BIM", "ifcopenshell", "IFC"]
13
+ dependencies = ["lxml"]
14
+ requires-python = ">=3.10"
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Topic :: Scientific/Engineering",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/CyrilWaechter/python-materialsdb"
24
+ "Bug Tracker" = "https://github.com/CyrilWaechter/python-materialsdb/issues"
25
+ Community = "https://community.osarch.org/"
26
+
27
+ [project.scripts]
28
+ materialsdb-gui = "materialsdb.gui.__main__:main"
29
+
30
+ [project.optional-dependencies]
31
+ ifc = ["ifcopenshell"]
32
+ dev = [
33
+ "pytest",
34
+ "pytest-benchmark",
35
+ "ruff==0.16.4",
36
+ "ty==0.0.74",
37
+ ]
38
+
39
+ [tool.setuptools]
40
+ include-package-data = true
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ "*" = ["*.json", "*.xsd", "*.html", "*.js"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = [
50
+ "tests",
51
+ ]
52
+ markers = [
53
+ "benchmark: performance benchmarks (deselected by default)",
54
+ ]
55
+ addopts = "-m 'not benchmark'"
56
+
57
+ [tool.ruff]
58
+ line-length = 120
59
+ target-version = "py310"
60
+ extend-exclude = [
61
+ "src/materialsdb/classes.py",
62
+ "*.md",
63
+ ]
64
+
65
+ [tool.ty.src]
66
+ include = ["src", "tests", "dev_utils", "examples"]
67
+
68
+ [[tool.ty.overrides]]
69
+ # Generated file: excluded from ruff/ty per Global Constraints (no-drift policy).
70
+ include = ["src/materialsdb/classes.py"]
71
+ [tool.ty.overrides.rules]
72
+ all = "ignore"
73
+
74
+ [tool.ty.analysis]
75
+ # lxml ships no type information; treat as Any instead of erroring on every use.
76
+ replace-imports-with-any = ["lxml.**"]
77
+
78
+ [[tool.ty.overrides]]
79
+ # Generator emits dynamic setattr-based classes; ty cannot follow the flow of
80
+ # self.file (assigned inside parse_schema) nor os.startfile (Windows-only).
81
+ include = ["dev_utils/classes_generator.py", "examples/generate_ifc_project_libraries.py"]
82
+ [tool.ty.overrides.rules]
83
+ unresolved-attribute = "ignore"
84
+
85
+ [[tool.ty.overrides]]
86
+ # Examples exercise the generated model whose Optional list fields are
87
+ # populated before use; runtime-valid, inference-hostile.
88
+ include = ["examples/create_layers.py"]
89
+ [tool.ty.overrides.rules]
90
+ unresolved-attribute = "ignore"
91
+ possibly-missing-attribute = "ignore"
92
+
93
+ [[tool.ty.overrides]]
94
+ # ifcopenshell's own annotations disagree with its runtime API here.
95
+ include = ["src/materialsdb/ifc/project_library.py"]
96
+ [tool.ty.overrides.rules]
97
+ invalid-argument-type = "ignore"
98
+ invalid-assignment = "ignore"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from materialsdb import cache, classes, config, ifc, serialiser
2
+
3
+ __all__ = ["cache", "classes", "config", "ifc", "serialiser"]
@@ -0,0 +1,141 @@
1
+ import os
2
+ import pathlib
3
+ import urllib.request
4
+ from collections import namedtuple
5
+
6
+ from lxml import etree
7
+
8
+ MATERIALSDBINDEXURLLIST = [
9
+ "http://www.materialsdb.org/download/ProducerIndex.xml",
10
+ "http://www.materialsdb.org/download/generic/GenericIndex.xml",
11
+ ]
12
+
13
+
14
+ def get_cache_folder():
15
+ cache_dir = pathlib.Path(
16
+ os.environ.get("APPDATA") or os.environ.get("XDG_CACHE_HOME") or pathlib.Path.home() / ".cache"
17
+ ).joinpath(
18
+ "materialsdb",
19
+ )
20
+ pathlib.Path(cache_dir).mkdir(parents=True, exist_ok=True)
21
+ return cache_dir
22
+
23
+
24
+ def get_cached_index_path(index) -> pathlib.Path:
25
+ return get_cache_folder() / pathlib.Path(index).name
26
+
27
+
28
+ def parse_cached_index(index) -> etree._ElementTree:
29
+ path = get_cached_index_path(index)
30
+ if path.exists():
31
+ return etree.parse(str(path))
32
+ root = etree.Element("root")
33
+ return etree.ElementTree(root)
34
+
35
+
36
+ def get_by_id(root: etree._Element, id: str) -> etree._Element | None:
37
+ for company in root:
38
+ if company.get("id") == id:
39
+ return company
40
+ return None
41
+
42
+
43
+ def require_update(cached_company, company) -> bool:
44
+ if cached_company is None:
45
+ return True
46
+ for attrib in ["LastKnownDate", "KnownVersion"]:
47
+ if company.get(attrib) > cached_company.get(attrib):
48
+ return True
49
+ return False
50
+
51
+
52
+ def get_producers_dir() -> pathlib.Path:
53
+ producer_path = get_cache_folder().joinpath("Producers")
54
+ pathlib.Path(producer_path).mkdir(parents=True, exist_ok=True)
55
+ return producer_path
56
+
57
+
58
+ Report = namedtuple("Report", ["existing", "updated", "deleted"])
59
+
60
+
61
+ class _IndexPlan:
62
+ def __init__(self, index, new_index, unchanged, todos):
63
+ self.index = index
64
+ self.new_index = new_index
65
+ self.unchanged = unchanged
66
+ self.todos = todos # list[(company, cached_producer|None, producer_path)]
67
+
68
+
69
+ def _plan_index(index):
70
+ cached_index = parse_cached_index(index)
71
+ cached_root = cached_index.getroot()
72
+ with urllib.request.urlopen(index) as response:
73
+ new_index = etree.parse(response)
74
+ new_root = new_index.getroot()
75
+ producers_dir = get_producers_dir()
76
+ unchanged = []
77
+ todos = []
78
+ for company in new_root:
79
+ cached_producer = get_by_id(cached_root, company.get("id"))
80
+ producer_path = producers_dir / pathlib.Path(company.get("href")).name
81
+ if not require_update(cached_producer, company) and producer_path.exists():
82
+ unchanged.append(producer_path)
83
+ continue
84
+ todos.append((company, cached_producer, producer_path))
85
+ return _IndexPlan(index, new_index, unchanged, todos)
86
+
87
+
88
+ def update_producers_data(url_list=MATERIALSDBINDEXURLLIST, on_progress=None):
89
+ """on_progress(done, total, name) is called after each producer download
90
+ with a global running total; returning a truthy value stops the update
91
+ between two downloads (the partial Report is returned as-is)."""
92
+ plans = [_plan_index(index) for index in url_list]
93
+ total = sum(len(plan.todos) for plan in plans)
94
+ existing, updated, deleted = [], [], []
95
+ done = 0
96
+ for plan in plans:
97
+ for company, cached_producer, producer_path in plan.todos:
98
+ if cached_producer is not None:
99
+ cached_path = get_producers_dir() / pathlib.Path(cached_producer.get("href")).name
100
+ deleted.append(cached_path)
101
+ cached_path.unlink(True)
102
+ urllib.request.urlretrieve(company.get("href"), producer_path)
103
+ updated.append(producer_path)
104
+ done += 1
105
+ if on_progress is not None and on_progress(done, total, pathlib.Path(company.get("href")).name):
106
+ return Report(existing, updated, deleted)
107
+ if plan.todos:
108
+ plan.new_index.write(str(get_cached_index_path(plan.index)))
109
+ existing.extend(plan.unchanged)
110
+ return Report(existing, updated, deleted)
111
+
112
+
113
+ def update_producers_from_index(index):
114
+ """Legacy single-index entry point (no progress callback)."""
115
+ return update_producers_data([index])
116
+
117
+
118
+ def updates_available(url_list=MATERIALSDBINDEXURLLIST) -> bool:
119
+ """True when any remote company differs from the cached index."""
120
+ for index in url_list:
121
+ cached_root = parse_cached_index(index).getroot()
122
+ with urllib.request.urlopen(index) as response:
123
+ new_root = etree.parse(response).getroot()
124
+ for company in new_root:
125
+ if require_update(get_by_id(cached_root, company.get("id")), company):
126
+ return True
127
+ return False
128
+
129
+
130
+ def producers():
131
+ for producer in get_producers_dir().iterdir():
132
+ if producer.suffix.lower() == ".xml":
133
+ yield producer
134
+
135
+
136
+ def main():
137
+ update_producers_data()
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -14,6 +14,12 @@ from dataclasses import dataclass
14
14
  from typing import Tuple, Optional, List
15
15
 
16
16
 
17
+ class TDesignUsage(str):
18
+ xs_type: str = "simpleType"
19
+ xml_enum: Tuple[str, ...] = ('consDesignForWall', 'consDesignForRoof', 'consDesignForFloor')
20
+ xml_name: str = "TDesignUsage"
21
+
22
+
17
23
  class TLCADB(str):
18
24
  xs_type: str = "simpleType"
19
25
  xml_enum: Tuple[str, ...] = ('dbKBOB', 'dbOkobauDat', 'dbLux')
@@ -58,7 +64,7 @@ class T2Lowercase(str):
58
64
 
59
65
  class Groupkind(str):
60
66
  xs_type: str = "simpleType"
61
- xml_enum: Tuple[str, ...] = ('Others', 'Water_Proof', 'Vapour_Proof', 'Concrete', 'Wood_Timberproducts', 'Insulation', 'Masonry', 'Metal', 'Mortar', 'Plastics', 'Stone', 'Composite', 'Films', 'Render', 'Covering', 'Glas', 'Soil', 'air')
67
+ xml_enum: Tuple[str, ...] = ('Others', 'Water_Proof', 'Vapour_Proof', 'Concrete', 'Wood_Timberproducts', 'Insulation', 'Masonry', 'Metal', 'Mortar', 'Plastics', 'Stone', 'Composite', 'Films', 'Render', 'Covering', 'Glas', 'Soil', 'Air')
62
68
  xml_name: str = "groupkind"
63
69
 
64
70
 
@@ -408,9 +414,10 @@ class Lcia:
408
414
  @dataclass
409
415
  class Lcaversion:
410
416
  id: str
411
- code: Brackguid
412
417
  xs_type: str = "element"
413
- xml_attributes: Tuple[str, ...] = ('id', 'code')
418
+ code: Optional[Brackguid] = None
419
+ disabled: Optional[Boolean] = None
420
+ xml_attributes: Tuple[str, ...] = ('id', 'code', 'disabled')
414
421
  xml_name = "lcaversion"
415
422
  xml_elements: Tuple[str, ...] = ()
416
423
 
@@ -555,11 +562,11 @@ class Vlca:
555
562
  @dataclass
556
563
  class Variation:
557
564
  id: Guid
558
- vgeometry: List[Vgeometry]
559
- vthermal: List[Vthermal]
560
565
  xs_type: str = "element"
561
566
  displayorder: Optional[int] = None
562
567
  xml_attributes: Tuple[str, ...] = ('id', 'displayorder')
568
+ vgeometry: Optional[List[Vgeometry]] = None
569
+ vthermal: Optional[List[Vthermal]] = None
563
570
  vacoustic: Optional[List[Vacoustic]] = None
564
571
  vother: Optional[List[Vother]] = None
565
572
  vlcia: Optional[List[Vlcia]] = None
@@ -582,7 +589,8 @@ class Construction:
582
589
  xs_type: str = "element"
583
590
  source: Optional[str] = None
584
591
  consref: Optional[str] = None
585
- xml_attributes: Tuple[str, ...] = ('source', 'consref')
592
+ designusage: Optional[TDesignUsage] = None
593
+ xml_attributes: Tuple[str, ...] = ('source', 'consref', 'designusage')
586
594
  xml_name = "construction"
587
595
  xml_elements: Tuple[str, ...] = ()
588
596
 
@@ -1,14 +1,11 @@
1
+ import json
1
2
  import os
2
3
  import pathlib
3
- import json
4
- from typing import Dict
5
4
 
6
5
 
7
6
  def get_config_dir():
8
7
  config_dir = pathlib.Path(
9
- os.environ.get("APPDATA")
10
- or os.environ.get("XDG_CACHE_HOME")
11
- or pathlib.Path.home() / ".config"
8
+ os.environ.get("APPDATA") or os.environ.get("XDG_CACHE_HOME") or pathlib.Path.home() / ".config"
12
9
  ).joinpath(
13
10
  "materialsdb",
14
11
  )
@@ -20,7 +17,7 @@ def get_base_config_path() -> pathlib.Path:
20
17
  return get_config_dir() / "config.json"
21
18
 
22
19
 
23
- def get_base_config() -> Dict[str, str]:
20
+ def get_base_config() -> dict[str, str]:
24
21
  base_config_path = get_base_config_path()
25
22
  if base_config_path.exists():
26
23
  return json.loads(get_base_config_path().read_text("utf-8"))