python-materialsdb 0.0.2__py3-none-any.whl → 0.2.0__py3-none-any.whl

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 (34) hide show
  1. materialsdb/__init__.py +3 -1
  2. materialsdb/cache.py +5 -6
  3. materialsdb/classes.py +14 -6
  4. materialsdb/config.py +3 -6
  5. materialsdb/construction.py +441 -0
  6. materialsdb/gui/__init__.py +0 -0
  7. materialsdb/gui/__main__.py +32 -0
  8. materialsdb/gui/discovery.py +28 -0
  9. materialsdb/gui/listener.py +151 -0
  10. materialsdb/gui/server.py +730 -0
  11. materialsdb/gui/static/app-constructions.js +639 -0
  12. materialsdb/gui/static/app.js +451 -0
  13. materialsdb/gui/static/constructions.html +107 -0
  14. materialsdb/gui/static/index.html +56 -0
  15. materialsdb/gui/static/picker-core.js +111 -0
  16. materialsdb/ifc/material_builder.py +295 -0
  17. materialsdb/ifc/material_psets.json +594 -580
  18. materialsdb/ifc/project_library.py +77 -212
  19. materialsdb/query.py +24 -0
  20. materialsdb/schema/MaterialsDBIndex100.xsd +60 -0
  21. materialsdb/schema/materialsdb102.xsd +820 -0
  22. materialsdb/schema/materialsdb103.xsd +938 -0
  23. materialsdb/serialiser.py +45 -18
  24. materialsdb/store.py +293 -0
  25. materialsdb/summary.py +83 -0
  26. materialsdb/utils.py +15 -21
  27. python_materialsdb-0.2.0.dist-info/METADATA +170 -0
  28. python_materialsdb-0.2.0.dist-info/RECORD +33 -0
  29. {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.2.0.dist-info}/WHEEL +1 -1
  30. python_materialsdb-0.2.0.dist-info/entry_points.txt +2 -0
  31. python_materialsdb-0.0.2.dist-info/METADATA +0 -68
  32. python_materialsdb-0.0.2.dist-info/RECORD +0 -14
  33. {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.2.0.dist-info/licenses}/LICENSE.md +0 -0
  34. {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,63 +1,33 @@
1
1
  import uuid
2
- import datetime
3
- import json
4
- from pathlib import Path
2
+
5
3
  import ifcopenshell
6
4
  import ifcopenshell.api
7
5
 
8
- from materialsdb.serialiser import XmlDeserialiser
9
6
  from materialsdb import config, utils
10
7
  from materialsdb.classes import (
11
8
  Materials,
12
- Material,
13
9
  )
10
+ from materialsdb.ifc.material_builder import (
11
+ CATEGORIES,
12
+ MATERIALSDB_PSET,
13
+ PSETS,
14
+ MaterialBuilder,
15
+ clean_psets,
16
+ get_value,
17
+ )
18
+ from materialsdb.serialiser import XmlDeserialiser
14
19
 
15
- CATEGORIES = {
16
- "Others": {"hatch": "", "color": (255, 255, 255)},
17
- "Water_Proof": {"hatch": "", "color": (255, 255, 255)},
18
- "Vapour_Proof": {"hatch": "", "color": (0, 0, 0)},
19
- "Concrete": {"hatch": "", "color": (0, 255, 0)},
20
- "Wood_Timberproducts": {"hatch": "", "color": (91, 60, 17)},
21
- "Insulation": {"hatch": "", "color": (253, 108, 158)},
22
- "Masonry": {"hatch": "", "color": (253, 70, 38)},
23
- "Metal": {"hatch": "", "color": (119, 181, 254)},
24
- "Mortar": {"hatch": "", "color": (102, 0, 153)},
25
- "Plastics": {"hatch": "", "color": (96, 96, 96)},
26
- "Stone": {"hatch": "", "color": (0, 0, 255)},
27
- "Composite": {"hatch": "", "color": (112, 141, 35)},
28
- "Films": {"hatch": "", "color": (0, 0, 0)},
29
- "Render": {"hatch": "", "color": (0, 0, 0)},
30
- "Covering": {"hatch": "", "color": (0, 0, 0)},
31
- "Glas": {"hatch": "", "color": (27, 79, 8)},
32
- "Soil": {"hatch": "", "color": (142, 84, 52)},
33
- }
34
-
35
-
36
- def clean_psets(psets):
37
- new_dict = {}
38
- for pset_name, props in psets.items():
39
- pset_dict = {}
40
- for prop_name, definition in props.items():
41
- if definition["path"]:
42
- pset_dict[prop_name] = definition
43
- if pset_dict:
44
- new_dict[pset_name] = pset_dict
45
- return new_dict
46
-
47
-
48
- PSETS = json.loads(Path(__file__).with_name("material_psets.json").read_text("utf-8"))
49
- PSETS = clean_psets(PSETS)
50
-
51
-
52
- def get_value(layer, definition, country=None):
53
- value = layer
54
- for attrib in definition["path"]:
55
- value = getattr(value, attrib)
56
- if isinstance(value, list):
57
- value = utils.get_by_country(value, country)
58
- if not value:
59
- return None
60
- return value
20
+ # Re-exports: external code may import these from project_library (historical API).
21
+ __all__ = [
22
+ "CATEGORIES",
23
+ "MATERIALSDB_PSET",
24
+ "PSETS",
25
+ "MaterialBuilder",
26
+ "ProjectLibrary",
27
+ "clean_psets",
28
+ "create_project_library_from_xml",
29
+ "get_value",
30
+ ]
61
31
 
62
32
 
63
33
  class ProjectLibrary:
@@ -68,6 +38,7 @@ class ProjectLibrary:
68
38
  self.lang = config.get_lang()
69
39
  self.country = config.get_country()
70
40
  self.owner_history = None
41
+ self.builder = MaterialBuilder(self.file, country=self.country, lang=self.lang)
71
42
 
72
43
  def create_application(self):
73
44
  file = self.file
@@ -94,198 +65,92 @@ class ProjectLibrary:
94
65
  ApplicationIdentifier="ifcopenshell",
95
66
  )
96
67
 
97
- def create_project_library(self, source: Materials, role: str = "MANUFACTURER"):
68
+ def create_project_library(self, company: str, companyid: str, ver: int, crd, role: str = "MANUFACTURER"):
98
69
  file = self.file
99
70
 
100
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcroleenum.htm
101
- role = file.createIfcActorRole(role)
102
-
103
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcperson.htm
71
+ role_entity = file.createIfcActorRole(role)
104
72
  person = file.createIfcPerson(
105
73
  Identification=str(uuid.uuid4()),
106
74
  FamilyName="Unknown",
107
75
  GivenName="Unknown",
108
- Roles=[role],
109
- )
110
-
111
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcorganization.htm
112
- organisation = file.createIfcOrganization(
113
- Identification=source.companyid, Name=source.company, Roles=[role]
114
- )
115
-
116
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcpersonandorganization.htm
117
- person_and_organisation = file.createIfcPersonAndOrganization(
118
- person, organisation, Roles=[role]
76
+ Roles=[role_entity],
119
77
  )
120
-
121
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcownerhistory.htm
78
+ organisation = file.createIfcOrganization(Identification=str(companyid), Name=str(company), Roles=[role_entity])
79
+ person_and_organisation = file.createIfcPersonAndOrganization(person, organisation, Roles=[role_entity])
122
80
  owner_history = file.createIfcOwnerHistory(
123
81
  OwningUser=person_and_organisation,
124
82
  OwningApplication=self.application,
125
- CreationDate=max(int(utils.date_from_xml(source.crd).timestamp()),-2147483648),
83
+ CreationDate=max(int(utils.date_from_xml(crd).timestamp()), -2147483648),
126
84
  )
127
85
  self.owner_history = owner_history
128
-
129
- # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcprojectlibrary.htm
130
86
  file.createIfcProjectLibrary(
131
87
  GlobalId=ifcopenshell.guid.new(),
132
88
  OwnerHistory=owner_history,
133
- Name=source.company,
134
- Description=f"Material library converted from materialsdb xml for company {source.company}",
89
+ Name=str(company),
90
+ Description=f"Material library converted from materialsdb xml for company {company}",
135
91
  ObjectType="MaterialLibrary",
136
- LongName=f"{source.company} version {source.ver}",
92
+ LongName=f"{company} version {ver}",
137
93
  )
138
94
 
139
95
  def create_materials(self, source: Materials):
140
- file = self.file
141
- context = file.createIfcRepresentationContext()
142
96
  for material in utils.get_materials(source, self.country):
97
+ created = self.builder.build(material)
98
+ if not created:
99
+ continue
143
100
  name = utils.get_material_name(material, self.lang)
144
- description = utils.get_material_description(material, self.lang)
145
- webinfo = utils.get_material_webinfo(material, self.lang)
146
- category = material.information.group
147
- labels = material.information.labels
148
- color = material.information.color
149
- brushstyle = material.information.BrushStyle
150
- surface_style = self.get_surface_style(color, category)
151
- styled_item = file.createIfcStyledItem(Styles=[surface_style])
152
- # TODO: hatch_item = file.createIfcFillAreaStyleHatching()
153
- representation = file.createIfcStyledRepresentation(
154
- ContextOfItems=context,
155
- RepresentationIdentifier="Body",
156
- Items=[styled_item],
157
- )
158
- for layer in getattr(getattr(material, "layers", ()), "layer", ()):
159
- ifc_material = file.createIfcMaterial(name, description, category)
160
- for pset_name, props in PSETS.items():
161
- properties = list()
162
- for prop_name, definition in props.items():
163
- primary_measure_type = definition["primary_measure_type"]
164
- if not primary_measure_type:
165
- continue
166
- value = get_value(layer, definition, self.country)
167
- if value:
168
- unit_factor = definition.get("unit_factor", None) or 1
169
- properties.append(
170
- file.create_entity(
171
- "IfcPropertySingleValue",
172
- Name=prop_name,
173
- NominalValue=file.create_entity(
174
- primary_measure_type, value * unit_factor
175
- ),
176
- )
177
- )
178
- if not properties:
179
- continue
180
- pset = file.create_entity(
181
- "IfcMaterialProperties",
182
- Name=pset_name,
183
- Properties=properties,
184
- Material=ifc_material,
185
- )
186
- geometry = utils.get_by_country(layer.geometry, self.country)
187
- if getattr(geometry, "thick", None):
188
- element_name = f"{name} | {geometry.thick}mm"
189
- ifc_layer = file.create_entity(
190
- "IfcMaterialLayer",
191
- Material=ifc_material,
192
- LayerThickness=geometry.thick / 1000,
193
- Name=element_name,
194
- )
195
- assigned_material = file.create_entity(
196
- "IfcMaterialLayerSet",
197
- MaterialLayers=[ifc_layer],
198
- LayerSetName=element_name,
199
- )
200
- else:
201
- element_name = name
202
- assigned_material = ifc_material
203
- if material.information.wall:
204
- wall = file.create_entity(
205
- "IfcWallType",
206
- GlobalId=ifcopenshell.guid.new(),
207
- Name=element_name,
208
- )
209
- ifcopenshell.api.run(
210
- "material.assign_material",
211
- file,
212
- product=wall,
213
- material=assigned_material,
214
- )
215
- if material.information.roof:
216
- roof = file.create_entity(
217
- "IfcRoofType",
218
- GlobalId=ifcopenshell.guid.new(),
219
- Name=element_name,
220
- )
221
- ifcopenshell.api.run(
222
- "material.assign_material",
223
- file,
224
- product=roof,
225
- material=assigned_material,
226
- )
227
- if material.information.floor:
228
- slab = file.create_entity(
229
- "IfcRoofType",
230
- GlobalId=ifcopenshell.guid.new(),
231
- Name=element_name,
232
- )
233
- ifcopenshell.api.run(
234
- "material.assign_material",
235
- file,
236
- product=slab,
237
- material=assigned_material,
238
- )
239
- if material.information.door:
240
- door = file.create_entity(
241
- "IfcDoorType",
242
- GlobalId=ifcopenshell.guid.new(),
243
- Name=element_name,
244
- )
245
- ifcopenshell.api.run(
246
- "material.assign_material",
247
- file,
248
- product=door,
249
- material=assigned_material,
250
- )
101
+ for layer, ifc_material in zip(utils.get_material_layers(material), created):
102
+ geometry = utils.get_by_country(layer.geometry or (), self.country)
103
+ thick = getattr(geometry, "thick", None)
104
+ element_name = f"{name} | {thick}mm" if thick else name
105
+ assigned_material = self._assigned_material(ifc_material, thick, element_name, name)
106
+ self._create_usage_types(material, assigned_material, element_name)
107
+
108
+ def _assigned_material(self, ifc_material, thick, element_name, name):
109
+ if not thick:
110
+ return ifc_material
111
+ # MaterialBuilder.build already created the layer + layer set for this
112
+ # layer; reuse them so batch export does not duplicate entities.
113
+ for rel in self.file.get_inverse(ifc_material):
114
+ if rel.is_a("IfcMaterialLayer"):
115
+ for layer_set in rel.ToMaterialLayerSet or ():
116
+ return layer_set
117
+ ifc_layer = self.file.create_entity(
118
+ "IfcMaterialLayer",
119
+ Material=ifc_material,
120
+ LayerThickness=thick / 1000,
121
+ Name=str(element_name),
122
+ )
123
+ return self.file.create_entity(
124
+ "IfcMaterialLayerSet",
125
+ MaterialLayers=[ifc_layer],
126
+ LayerSetName=str(element_name),
127
+ )
251
128
 
252
- def get_surface_style(self, color, category):
129
+ def _create_usage_types(self, material, assigned_material, element_name):
253
130
  file = self.file
254
- if color:
255
- name = f"color {color}"
256
- style = file.createIfcSurfaceStyleShading(
257
- SurfaceColour=self.color_xml_to_ifc(color)
258
- )
259
- else:
260
- if not category:
261
- category = "Others"
262
- name = f"category {category}"
263
- for style in file.by_type("IfcSurfaceStyle"):
264
- if style.Name == name:
265
- return style
266
- style = file.createIfcSurfaceStyleShading(
267
- SurfaceColour=self.file.createIfcColourRgb(
268
- None, *CATEGORIES[category]["color"]
269
- ),
270
- )
271
- for surface_style in file.by_type("IfcSurfaceStyle"):
272
- if surface_style.Name == name:
273
- return surface_style
274
- return file.createIfcSurfaceStyle(Name=name, Side="BOTH", Styles=[style])
131
+ kind_map = (("wall", "IfcWallType"), ("roof", "IfcRoofType"), ("floor", "IfcRoofType"), ("door", "IfcDoorType"))
132
+ for flag, entity_type in kind_map:
133
+ if getattr(material.information, flag, None):
134
+ product = file.create_entity(entity_type, GlobalId=ifcopenshell.guid.new(), Name=str(element_name))
135
+ ifcopenshell.api.run(
136
+ "material.assign_material",
137
+ file,
138
+ products=[product],
139
+ material=assigned_material,
140
+ )
141
+
142
+ def get_surface_style(self, color, category):
143
+ return self.builder.get_surface_style(color, category)
275
144
 
276
145
  def color_xml_to_ifc(self, color: int):
277
- """Color definition in xml is obscur. We assume that it is a decimal color.
278
- See: https://stackoverflow.com/a/2262152/4098083"""
279
- return self.file.createIfcColourRgb(
280
- Blue=color & 255, Green=(color >> 8) & 255, Red=(color >> 16) & 255
281
- )
146
+ return self.builder.color_xml_to_ifc(color)
282
147
 
283
148
 
284
149
  def create_project_library_from_xml(xml_path):
285
150
  library = ProjectLibrary()
286
151
  deserialiser = XmlDeserialiser()
287
152
  source = deserialiser.from_xml(str(xml_path))
288
- library.create_project_library(source)
153
+ library.create_project_library(company=source.company, companyid=source.companyid, ver=source.ver, crd=source.crd)
289
154
  library.create_materials(source)
290
155
  return library.file
291
156
 
materialsdb/query.py ADDED
@@ -0,0 +1,24 @@
1
+ """Convenience facade over the SQLite material store."""
2
+
3
+ from functools import lru_cache
4
+
5
+ from materialsdb.classes import Material
6
+ from materialsdb.store import MaterialStore, Report
7
+ from materialsdb.summary import MaterialSummary
8
+
9
+
10
+ @lru_cache(maxsize=1)
11
+ def get_store() -> MaterialStore:
12
+ return MaterialStore()
13
+
14
+
15
+ def get_material(material_id: str) -> Material | None:
16
+ return get_store().get(material_id)
17
+
18
+
19
+ def search(text: str, **filters) -> list[MaterialSummary]:
20
+ return get_store().summaries(text=text, **filters)
21
+
22
+
23
+ def refresh(force: bool = False) -> Report:
24
+ return get_store().refresh(force=force)
@@ -0,0 +1,60 @@
1
+ <?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ targetNamespace="http://www.materialsDB.org"
4
+ xmlns="http://www.materialsDB.org" elementFormDefault="qualified" version="1.00">
5
+ <!--
6
+ An XML XSD for Open Materials database (OMaterialsDB 1.00)
7
+ DOCTYPE MaterialsDBIndex
8
+ SYSTEM http://www.materialsdb.org/schemas/MaterialsDBIndex100.xsd
9
+ See the documentation and examples at http://www.materialsdb.org/
10
+ (c) 2007-2013 E4tech Software SA, Edouard Westphal
11
+ released under the GNU Public License (GPL)
12
+ -->
13
+ <!--
14
+ 1.00 : 2013.04.17 first version
15
+ -->
16
+ <xs:simpleType name="TDateTime">
17
+ <xs:annotation>
18
+ <xs:documentation>
19
+ The integral part of a TDateTime value is the number of days that have passed since 12/30/1899.
20
+ The fractional part of the TDateTime value is fraction of a 24 hour day that has elapsed.
21
+ </xs:documentation>
22
+ </xs:annotation>
23
+ <xs:restriction base="xs:float"/>
24
+ </xs:simpleType>
25
+ <xs:simpleType name="guid">
26
+ <xs:restriction base="xs:string">
27
+ <xs:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"/>
28
+ </xs:restriction>
29
+ </xs:simpleType>
30
+ <xs:element name='MaterialsDBIndex'>
31
+ <xs:complexType>
32
+ <xs:sequence>
33
+ <xs:element ref="company" maxOccurs="unbounded"/>
34
+ </xs:sequence>
35
+ <xs:attribute name="verXML" type="xs:unsignedInt" use="required"/>
36
+ </xs:complexType>
37
+ </xs:element>
38
+ <xs:element name="company">
39
+ <xs:complexType>
40
+ <xs:simpleContent>
41
+ <xs:extension base="xs:string">
42
+ <xs:attribute name="id" type="guid" use="required"/>
43
+ <xs:attribute name="name" type="xs:string" use="required"/>
44
+ <xs:attribute name="href" type="xs:anyURI" use="required"/>
45
+ <xs:attribute name="LastKnownDate" type="TDateTime" use="required"/>
46
+ <xs:attribute name="KnownVersion" type="xs:unsignedInt" use="required"/>
47
+ <xs:attribute name="KnownCRD" type="TDateTime" use="required"/>
48
+ </xs:extension>
49
+ </xs:simpleContent>
50
+ </xs:complexType>
51
+ </xs:element>
52
+ </xs:schema>
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+