python-materialsdb 0.0.2__py3-none-any.whl → 0.1.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.
- materialsdb/cache.py +2 -1
- materialsdb/ifc/project_library.py +4 -4
- materialsdb/query.py +24 -0
- materialsdb/serialiser.py +24 -1
- materialsdb/store.py +290 -0
- materialsdb/summary.py +75 -0
- {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.1.0.dist-info}/METADATA +21 -6
- python_materialsdb-0.1.0.dist-info/RECORD +17 -0
- {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.1.0.dist-info}/WHEEL +1 -1
- python_materialsdb-0.0.2.dist-info/RECORD +0 -14
- {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.1.0.dist-info/licenses}/LICENSE.md +0 -0
- {python_materialsdb-0.0.2.dist-info → python_materialsdb-0.1.0.dist-info}/top_level.txt +0 -0
materialsdb/cache.py
CHANGED
|
@@ -75,7 +75,8 @@ def update_producers_data(url_list=MATERIALSDBINDEXURLLIST):
|
|
|
75
75
|
def update_producers_from_index(index):
|
|
76
76
|
cached_index = parse_cached_index(index)
|
|
77
77
|
cached_root = cached_index.getroot()
|
|
78
|
-
|
|
78
|
+
with urllib.request.urlopen(index) as response:
|
|
79
|
+
new_index = etree.parse(response)
|
|
79
80
|
new_root = new_index.getroot()
|
|
80
81
|
producers_dir = get_producers_dir()
|
|
81
82
|
has_index_update = False
|
|
@@ -209,7 +209,7 @@ class ProjectLibrary:
|
|
|
209
209
|
ifcopenshell.api.run(
|
|
210
210
|
"material.assign_material",
|
|
211
211
|
file,
|
|
212
|
-
|
|
212
|
+
products=[wall],
|
|
213
213
|
material=assigned_material,
|
|
214
214
|
)
|
|
215
215
|
if material.information.roof:
|
|
@@ -221,7 +221,7 @@ class ProjectLibrary:
|
|
|
221
221
|
ifcopenshell.api.run(
|
|
222
222
|
"material.assign_material",
|
|
223
223
|
file,
|
|
224
|
-
|
|
224
|
+
products=[roof],
|
|
225
225
|
material=assigned_material,
|
|
226
226
|
)
|
|
227
227
|
if material.information.floor:
|
|
@@ -233,7 +233,7 @@ class ProjectLibrary:
|
|
|
233
233
|
ifcopenshell.api.run(
|
|
234
234
|
"material.assign_material",
|
|
235
235
|
file,
|
|
236
|
-
|
|
236
|
+
products=[slab],
|
|
237
237
|
material=assigned_material,
|
|
238
238
|
)
|
|
239
239
|
if material.information.door:
|
|
@@ -245,7 +245,7 @@ class ProjectLibrary:
|
|
|
245
245
|
ifcopenshell.api.run(
|
|
246
246
|
"material.assign_material",
|
|
247
247
|
file,
|
|
248
|
-
|
|
248
|
+
products=[door],
|
|
249
249
|
material=assigned_material,
|
|
250
250
|
)
|
|
251
251
|
|
materialsdb/query.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Convenience facade over the SQLite material store."""
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from typing import List, Optional
|
|
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) -> Optional[Material]:
|
|
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)
|
materialsdb/serialiser.py
CHANGED
|
@@ -9,6 +9,8 @@ See the LICENSE.md file for more details.
|
|
|
9
9
|
Author : Cyril Waechter
|
|
10
10
|
"""
|
|
11
11
|
import re
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
13
|
+
from functools import lru_cache
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
import typing
|
|
14
16
|
from typing import Protocol, Tuple, Dict, Type, Optional, Any, Union
|
|
@@ -50,6 +52,11 @@ def get_valid_root(tree: objectify.ObjectifiedElement) -> str:
|
|
|
50
52
|
return root
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
@lru_cache(maxsize=None)
|
|
56
|
+
def cached_type_hints(cls) -> dict:
|
|
57
|
+
return typing.get_type_hints(cls)
|
|
58
|
+
|
|
59
|
+
|
|
53
60
|
class XmlDeserialiser:
|
|
54
61
|
def __init__(self):
|
|
55
62
|
self.schema = etree.XMLSchema(file=get_xml_schema())
|
|
@@ -62,13 +69,29 @@ class XmlDeserialiser:
|
|
|
62
69
|
tree = objectify.parse(xml_path)
|
|
63
70
|
return self.from_element(get_valid_root(tree))
|
|
64
71
|
|
|
72
|
+
def from_xml_files(self, paths, max_workers=None):
|
|
73
|
+
"""Parse multiple producer XML files concurrently.
|
|
74
|
+
|
|
75
|
+
Yields (path, Materials) per successfully parsed file and
|
|
76
|
+
(path, None) for files that could not be parsed."""
|
|
77
|
+
|
|
78
|
+
def load(path):
|
|
79
|
+
try:
|
|
80
|
+
return Path(path), self.from_xml(str(path))
|
|
81
|
+
except Exception as err:
|
|
82
|
+
print(f"{Path(path).name}: could not parse file:\n\t{err}")
|
|
83
|
+
return Path(path), None
|
|
84
|
+
|
|
85
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
86
|
+
yield from executor.map(load, paths)
|
|
87
|
+
|
|
65
88
|
def from_element(self, element=None, base_class=None):
|
|
66
89
|
element_name = get_element_name(element)
|
|
67
90
|
element_class = base_class or getattr(classes, self.cls_name(element_name))
|
|
68
91
|
kwargs: Dict[str, Any] = {}
|
|
69
92
|
if element_class.xs_type != "element":
|
|
70
93
|
kwargs["object"] = element.text or ""
|
|
71
|
-
type_hints =
|
|
94
|
+
type_hints = cached_type_hints(element_class)
|
|
72
95
|
for attrib in getattr(element_class, "xml_attributes", ()):
|
|
73
96
|
value = element.get(attrib)
|
|
74
97
|
if value is None:
|
materialsdb/store.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from lxml import etree, objectify
|
|
10
|
+
|
|
11
|
+
from materialsdb import cache, config
|
|
12
|
+
from materialsdb.classes import Material
|
|
13
|
+
from materialsdb.serialiser import XmlDeserialiser, get_valid_root
|
|
14
|
+
from materialsdb.summary import MaterialSummary, summarize_material
|
|
15
|
+
|
|
16
|
+
Report = namedtuple("Report", ["existing", "updated", "deleted", "skipped"])
|
|
17
|
+
|
|
18
|
+
SCHEMA_VERSION = "1"
|
|
19
|
+
|
|
20
|
+
_SCHEMA = """
|
|
21
|
+
CREATE TABLE IF NOT EXISTS materials (
|
|
22
|
+
id TEXT PRIMARY KEY, company_id TEXT, company TEXT, category TEXT,
|
|
23
|
+
names TEXT, descriptions TEXT,
|
|
24
|
+
lambda_min REAL, lambda_max REAL, thick_min REAL, thick_max REAL,
|
|
25
|
+
usage TEXT, source_file TEXT, xml BLOB);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_company ON materials(company);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_category ON materials(category);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_lambda ON materials(lambda_min);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS producer_files (
|
|
30
|
+
path TEXT PRIMARY KEY, sha256 TEXT, built_at REAL);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_MATERIAL_COLUMNS = (
|
|
35
|
+
"id",
|
|
36
|
+
"company_id",
|
|
37
|
+
"company",
|
|
38
|
+
"category",
|
|
39
|
+
"names",
|
|
40
|
+
"descriptions",
|
|
41
|
+
"lambda_min",
|
|
42
|
+
"lambda_max",
|
|
43
|
+
"thick_min",
|
|
44
|
+
"thick_max",
|
|
45
|
+
"usage",
|
|
46
|
+
"source_file",
|
|
47
|
+
"xml",
|
|
48
|
+
)
|
|
49
|
+
_COLUMN_LIST = ", ".join(_MATERIAL_COLUMNS)
|
|
50
|
+
|
|
51
|
+
_NUMERIC_SORTS = {"lambda": "lambda_min", "thick": "thick_min"}
|
|
52
|
+
_STRING_SORTS = {"company": "company", "category": "category"}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _sha256(path: Path) -> str:
|
|
56
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MaterialStore:
|
|
60
|
+
SCHEMA_VERSION = SCHEMA_VERSION
|
|
61
|
+
|
|
62
|
+
def __init__(self, db_path: Optional[Path] = None):
|
|
63
|
+
self.db_path = (
|
|
64
|
+
Path(db_path) if db_path else cache.get_cache_folder() / "materials.db"
|
|
65
|
+
)
|
|
66
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
self.connection = sqlite3.connect(str(self.db_path))
|
|
68
|
+
self.connection.execute("PRAGMA journal_mode=WAL")
|
|
69
|
+
self.connection.executescript(_SCHEMA)
|
|
70
|
+
self._ensure_schema_version()
|
|
71
|
+
|
|
72
|
+
# ---------- meta / lifecycle ----------
|
|
73
|
+
|
|
74
|
+
def _ensure_schema_version(self):
|
|
75
|
+
row = self.connection.execute(
|
|
76
|
+
"SELECT value FROM meta WHERE key='schema_version'"
|
|
77
|
+
).fetchone()
|
|
78
|
+
stored = row[0] if row else None
|
|
79
|
+
if stored != SCHEMA_VERSION:
|
|
80
|
+
self.connection.execute("DROP TABLE IF EXISTS materials")
|
|
81
|
+
self.connection.execute("DROP TABLE IF EXISTS producer_files")
|
|
82
|
+
self.connection.executescript(_SCHEMA)
|
|
83
|
+
self.connection.execute(
|
|
84
|
+
"INSERT INTO meta(key, value) VALUES ('schema_version', ?) "
|
|
85
|
+
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
|
86
|
+
(SCHEMA_VERSION,),
|
|
87
|
+
)
|
|
88
|
+
self.connection.commit()
|
|
89
|
+
|
|
90
|
+
def close(self):
|
|
91
|
+
self.connection.close()
|
|
92
|
+
|
|
93
|
+
# ---------- build / refresh ----------
|
|
94
|
+
|
|
95
|
+
def refresh(self, force=False, paths=None) -> Report:
|
|
96
|
+
if paths is None:
|
|
97
|
+
paths = list(cache.producers())
|
|
98
|
+
else:
|
|
99
|
+
paths = [Path(p) for p in paths]
|
|
100
|
+
|
|
101
|
+
kept = {str(p) for p in paths}
|
|
102
|
+
deleted = []
|
|
103
|
+
for (stored_path,) in self.connection.execute(
|
|
104
|
+
"SELECT path FROM producer_files"
|
|
105
|
+
).fetchall():
|
|
106
|
+
if stored_path not in kept:
|
|
107
|
+
self.connection.execute(
|
|
108
|
+
"DELETE FROM materials WHERE source_file=?", (stored_path,)
|
|
109
|
+
)
|
|
110
|
+
self.connection.execute(
|
|
111
|
+
"DELETE FROM producer_files WHERE path=?", (stored_path,)
|
|
112
|
+
)
|
|
113
|
+
deleted.append(Path(stored_path))
|
|
114
|
+
|
|
115
|
+
existing, updated, skipped = [], [], []
|
|
116
|
+
deserialiser = XmlDeserialiser()
|
|
117
|
+
for path in paths:
|
|
118
|
+
row = self.connection.execute(
|
|
119
|
+
"SELECT sha256 FROM producer_files WHERE path=?", (str(path),)
|
|
120
|
+
).fetchone()
|
|
121
|
+
try:
|
|
122
|
+
digest = _sha256(path)
|
|
123
|
+
if not force and row and row[0] == digest:
|
|
124
|
+
existing.append(path)
|
|
125
|
+
continue
|
|
126
|
+
self._upsert_file(deserialiser, path)
|
|
127
|
+
except Exception as err:
|
|
128
|
+
print(f"{path.name}: skipped during store refresh:\n\t{err}")
|
|
129
|
+
skipped.append(path)
|
|
130
|
+
continue
|
|
131
|
+
self.connection.execute(
|
|
132
|
+
"INSERT INTO producer_files(path, sha256, built_at) VALUES (?, ?, ?) "
|
|
133
|
+
"ON CONFLICT(path) DO UPDATE SET sha256=excluded.sha256, "
|
|
134
|
+
"built_at=excluded.built_at",
|
|
135
|
+
(str(path), digest, datetime.datetime.now().timestamp()),
|
|
136
|
+
)
|
|
137
|
+
updated.append(path)
|
|
138
|
+
|
|
139
|
+
self.connection.commit()
|
|
140
|
+
return Report(existing, updated, deleted, skipped)
|
|
141
|
+
|
|
142
|
+
def _upsert_file(self, deserialiser: XmlDeserialiser, path: Path):
|
|
143
|
+
tree = objectify.parse(str(path))
|
|
144
|
+
root = get_valid_root(tree)
|
|
145
|
+
source = deserialiser.from_element(root)
|
|
146
|
+
self.connection.execute(
|
|
147
|
+
"DELETE FROM materials WHERE source_file=?", (str(path),)
|
|
148
|
+
)
|
|
149
|
+
for element in root.material:
|
|
150
|
+
material = deserialiser.from_element(element)
|
|
151
|
+
summary = summarize_material(
|
|
152
|
+
material, company_id=str(source.companyid), company=source.company
|
|
153
|
+
)
|
|
154
|
+
self.connection.execute(
|
|
155
|
+
f"INSERT INTO materials ({_COLUMN_LIST}) "
|
|
156
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
157
|
+
(
|
|
158
|
+
summary.id,
|
|
159
|
+
summary.company_id,
|
|
160
|
+
summary.company,
|
|
161
|
+
summary.category,
|
|
162
|
+
json.dumps(summary.names),
|
|
163
|
+
json.dumps(summary.descriptions),
|
|
164
|
+
summary.lambda_min,
|
|
165
|
+
summary.lambda_max,
|
|
166
|
+
summary.thick_min,
|
|
167
|
+
summary.thick_max,
|
|
168
|
+
json.dumps(summary.usage),
|
|
169
|
+
str(path),
|
|
170
|
+
sqlite3.Binary(etree.tostring(element)),
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# ---------- queries ----------
|
|
175
|
+
|
|
176
|
+
def summaries(
|
|
177
|
+
self,
|
|
178
|
+
company=None,
|
|
179
|
+
category=None,
|
|
180
|
+
min_lambda=None,
|
|
181
|
+
max_lambda=None,
|
|
182
|
+
min_thick=None,
|
|
183
|
+
max_thick=None,
|
|
184
|
+
usage=None,
|
|
185
|
+
text=None,
|
|
186
|
+
sort="company",
|
|
187
|
+
ascending=True,
|
|
188
|
+
lang=None,
|
|
189
|
+
) -> List[MaterialSummary]:
|
|
190
|
+
where, params = [], []
|
|
191
|
+
|
|
192
|
+
def add(condition, value):
|
|
193
|
+
where.append(condition)
|
|
194
|
+
params.append(value)
|
|
195
|
+
|
|
196
|
+
if company:
|
|
197
|
+
add("company=?", company)
|
|
198
|
+
if category:
|
|
199
|
+
add("category=?", category)
|
|
200
|
+
if min_lambda is not None:
|
|
201
|
+
add("lambda_max>=?", min_lambda)
|
|
202
|
+
if max_lambda is not None:
|
|
203
|
+
add("lambda_min<=?", max_lambda)
|
|
204
|
+
if min_thick is not None:
|
|
205
|
+
add("thick_max>=?", min_thick)
|
|
206
|
+
if max_thick is not None:
|
|
207
|
+
add("thick_min<=?", max_thick)
|
|
208
|
+
|
|
209
|
+
query = f"SELECT {_COLUMN_LIST} FROM materials"
|
|
210
|
+
if where:
|
|
211
|
+
query += " WHERE " + " AND ".join(where)
|
|
212
|
+
rows = self.connection.execute(query, params).fetchall()
|
|
213
|
+
|
|
214
|
+
lang = lang or config.get_lang()
|
|
215
|
+
results = [self._row_to_summary(row) for row in rows]
|
|
216
|
+
|
|
217
|
+
if usage:
|
|
218
|
+
results = [r for r in results if r.usage.get(usage)]
|
|
219
|
+
if text:
|
|
220
|
+
needle = text.lower()
|
|
221
|
+
results = [
|
|
222
|
+
r
|
|
223
|
+
for r in results
|
|
224
|
+
if needle in (r.names.get(lang) or r.names.get("") or "").lower()
|
|
225
|
+
]
|
|
226
|
+
return self._sorted(results, sort, ascending)
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def _sorted(
|
|
230
|
+
results: List[MaterialSummary], sort: str, ascending: bool
|
|
231
|
+
) -> List[MaterialSummary]:
|
|
232
|
+
reverse = not ascending
|
|
233
|
+
if sort == "name":
|
|
234
|
+
return sorted(
|
|
235
|
+
results,
|
|
236
|
+
key=lambda r: r.names.get("") or "",
|
|
237
|
+
reverse=reverse,
|
|
238
|
+
)
|
|
239
|
+
if sort in _NUMERIC_SORTS:
|
|
240
|
+
attr = _NUMERIC_SORTS[sort]
|
|
241
|
+
return sorted(
|
|
242
|
+
results,
|
|
243
|
+
key=lambda r: (
|
|
244
|
+
(getattr(r, attr) is None) != reverse,
|
|
245
|
+
getattr(r, attr) if getattr(r, attr) is not None else 0,
|
|
246
|
+
),
|
|
247
|
+
reverse=reverse,
|
|
248
|
+
)
|
|
249
|
+
attr = _STRING_SORTS.get(sort, "company")
|
|
250
|
+
return sorted(results, key=lambda r: str(getattr(r, attr)), reverse=reverse)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _row_to_summary(row) -> MaterialSummary:
|
|
254
|
+
(
|
|
255
|
+
id_,
|
|
256
|
+
company_id,
|
|
257
|
+
company,
|
|
258
|
+
category,
|
|
259
|
+
names,
|
|
260
|
+
descriptions,
|
|
261
|
+
lambda_min,
|
|
262
|
+
lambda_max,
|
|
263
|
+
thick_min,
|
|
264
|
+
thick_max,
|
|
265
|
+
usage,
|
|
266
|
+
_source_file,
|
|
267
|
+
_xml,
|
|
268
|
+
) = row
|
|
269
|
+
return MaterialSummary(
|
|
270
|
+
id=id_,
|
|
271
|
+
company_id=company_id,
|
|
272
|
+
company=company,
|
|
273
|
+
category=category,
|
|
274
|
+
names=json.loads(names),
|
|
275
|
+
descriptions=json.loads(descriptions),
|
|
276
|
+
lambda_min=lambda_min,
|
|
277
|
+
lambda_max=lambda_max,
|
|
278
|
+
thick_min=thick_min,
|
|
279
|
+
thick_max=thick_max,
|
|
280
|
+
usage=json.loads(usage),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def get(self, material_id: str) -> Optional[Material]:
|
|
284
|
+
row = self.connection.execute(
|
|
285
|
+
"SELECT xml FROM materials WHERE id=?", (material_id,)
|
|
286
|
+
).fetchone()
|
|
287
|
+
if row is None:
|
|
288
|
+
return None
|
|
289
|
+
element = objectify.fromstring(bytes(row[0]))
|
|
290
|
+
return XmlDeserialiser().from_element(element)
|
materialsdb/summary.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from materialsdb import config, utils
|
|
5
|
+
from materialsdb.classes import Material
|
|
6
|
+
|
|
7
|
+
USAGE_FLAGS = ("wall", "roof", "floor", "door")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class MaterialSummary:
|
|
12
|
+
id: str
|
|
13
|
+
company_id: str
|
|
14
|
+
company: str
|
|
15
|
+
category: str
|
|
16
|
+
names: Dict[str, str]
|
|
17
|
+
descriptions: Dict[str, str]
|
|
18
|
+
lambda_min: Optional[float]
|
|
19
|
+
lambda_max: Optional[float]
|
|
20
|
+
thick_min: Optional[float]
|
|
21
|
+
thick_max: Optional[float]
|
|
22
|
+
usage: Dict[str, bool]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _localized_dict(items) -> Dict[str, str]:
|
|
26
|
+
result: Dict[str, str] = {}
|
|
27
|
+
for item in items or ():
|
|
28
|
+
result[str(item.lang or "")] = str(item)
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _min_max(values) -> Tuple[Optional[float], Optional[float]]:
|
|
33
|
+
values = [v for v in values if v is not None]
|
|
34
|
+
if not values:
|
|
35
|
+
return None, None
|
|
36
|
+
return min(values), max(values)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def summarize_material(
|
|
40
|
+
material: Material,
|
|
41
|
+
company_id: str = "",
|
|
42
|
+
company: str = "",
|
|
43
|
+
country: Optional[str] = None,
|
|
44
|
+
) -> MaterialSummary:
|
|
45
|
+
country = country or config.get_country()
|
|
46
|
+
information = material.information
|
|
47
|
+
|
|
48
|
+
lambdas = []
|
|
49
|
+
thicks = []
|
|
50
|
+
for layer in utils.get_material_layers(material):
|
|
51
|
+
thermal = utils.get_by_country(layer.thermal or (), country)
|
|
52
|
+
geometry = utils.get_by_country(layer.geometry or (), country)
|
|
53
|
+
if thermal is not None:
|
|
54
|
+
lambdas.append(thermal.lambda_value)
|
|
55
|
+
if geometry is not None:
|
|
56
|
+
thicks.append(geometry.thick)
|
|
57
|
+
|
|
58
|
+
lambda_min, lambda_max = _min_max(lambdas)
|
|
59
|
+
thick_min, thick_max = _min_max(thicks)
|
|
60
|
+
|
|
61
|
+
return MaterialSummary(
|
|
62
|
+
id=str(material.id),
|
|
63
|
+
company_id=str(company_id),
|
|
64
|
+
company=str(company),
|
|
65
|
+
category=str(information.group or ""),
|
|
66
|
+
names=_localized_dict(getattr(information.names, "name", ())),
|
|
67
|
+
descriptions=_localized_dict(
|
|
68
|
+
getattr(getattr(information, "explanations", None), "explanation", ())
|
|
69
|
+
),
|
|
70
|
+
lambda_min=lambda_min,
|
|
71
|
+
lambda_max=lambda_max,
|
|
72
|
+
thick_min=thick_min,
|
|
73
|
+
thick_max=thick_max,
|
|
74
|
+
usage={flag: str(getattr(information, flag)) == "1" for flag in USAGE_FLAGS},
|
|
75
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-materialsdb
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: A library to work with materialsdb.org open standard for building materials.
|
|
5
5
|
Home-page: https://github.com/CyrilWaechter/python-materialsdb
|
|
6
6
|
Author: Cyril Waechter
|
|
@@ -18,7 +18,10 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
License-File: LICENSE.md
|
|
19
19
|
Requires-Dist: lxml
|
|
20
20
|
Provides-Extra: ifc
|
|
21
|
-
Requires-Dist: ifcopenshell
|
|
21
|
+
Requires-Dist: ifcopenshell; extra == "ifc"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-benchmark; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
22
25
|
|
|
23
26
|
python-materialsdb is an unofficial python library for [materialsdb.org][1] an open format and database for building materials.
|
|
24
27
|
|
|
@@ -47,9 +50,21 @@ Note: in materialsdb standard languages are [ISO 639-1](https://en.wikipedia.org
|
|
|
47
50
|
|
|
48
51
|
# Usage examples :
|
|
49
52
|
Check out some [examples](examples):
|
|
50
|
-
* [Convert
|
|
53
|
+
* [Convert latest materials data to ifc](examples/generate_ifc_project_libraries.py)
|
|
51
54
|
* [Create your own materialsdb.org compliant XML](examples/create_layers.py)
|
|
52
55
|
|
|
56
|
+
# Querying materials :
|
|
57
|
+
The library keeps an sqlite index of the cached materials data for fast
|
|
58
|
+
filtering and single-material access:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from materialsdb import query
|
|
62
|
+
|
|
63
|
+
query.refresh() # incremental update from cached xml
|
|
64
|
+
rows = query.search("isolant", sort="lambda") # filtered, sorted summaries
|
|
65
|
+
material = query.get_material(rows[0].id) # full material dataclass
|
|
66
|
+
```
|
|
67
|
+
|
|
53
68
|
# How to install
|
|
54
69
|
## Using pip
|
|
55
70
|
```bash
|
|
@@ -57,8 +72,8 @@ pip install python-materialsdb
|
|
|
57
72
|
```
|
|
58
73
|
|
|
59
74
|
# Dependencies
|
|
60
|
-
* [lxml][2] (BSD) : xml parser
|
|
61
|
-
* [ifcopenshell][3] (LGPL) : ifc read/write
|
|
75
|
+
* [lxml][2] (BSD) : xml parser (tested with version 6.1.1)
|
|
76
|
+
* [ifcopenshell][3] (LGPL) : ifc read/write (tested with version 0.8.5)
|
|
62
77
|
|
|
63
78
|
# Third parties :
|
|
64
79
|
* [materialsdb.org][1] (GPL) : materials schema
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
materialsdb/__init__.py,sha256=2UuaCCmONhxIvpxB1eXHh88eb05i2ilR0-F0o6rKlCQ,63
|
|
2
|
+
materialsdb/cache.py,sha256=ags7_jiAwq5q2nKZZxoA7ybwF5sMVLSKpNe8Ps-iU0o,3386
|
|
3
|
+
materialsdb/classes.py,sha256=obuS_JRfdvqKJ8RMOx65X7AzI2J5W0xNgIXJ2y7evbM,21194
|
|
4
|
+
materialsdb/config.py,sha256=Bz16QTIl8u81g07www_zGdA-Ilsii_kbi3NYuSXlrlQ,1331
|
|
5
|
+
materialsdb/query.py,sha256=xoClMj0Xl7nQ_6DjETTc59gUYYaNH75lDje0BDwrxQQ,656
|
|
6
|
+
materialsdb/serialiser.py,sha256=_QENTdjoX1yeDFMfpUWtrC1-sN-Vusi5QpJL5l-Evck,6677
|
|
7
|
+
materialsdb/store.py,sha256=B5j99dqr5H1_dnTGceowAoLpFRq6CGG5w_fL_o-XGig,9734
|
|
8
|
+
materialsdb/summary.py,sha256=hBL8l27vKF7qgZXtYgA9O3WQRH3Ai-VZ43_PTTe-mjQ,2196
|
|
9
|
+
materialsdb/utils.py,sha256=jwJETjvBuHb6_K_7STsNaM-bE2Oc1X-th1xTrllW-Go,3081
|
|
10
|
+
materialsdb/ifc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
materialsdb/ifc/material_psets.json,sha256=g4Z-wOsnOP-TZR2R_9ikDpa9GalfyA8o3gDHvQtMAgg,15472
|
|
12
|
+
materialsdb/ifc/project_library.py,sha256=yZtI3qRHHDYib61CEVQb0G_X63_Ot_THqqljqUJY3DY,12202
|
|
13
|
+
python_materialsdb-0.1.0.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
14
|
+
python_materialsdb-0.1.0.dist-info/METADATA,sha256=t-77t35f4VValTki4NbypXfZzcl-Ccli4aSACONPOO8,3112
|
|
15
|
+
python_materialsdb-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
+
python_materialsdb-0.1.0.dist-info/top_level.txt,sha256=5ZHbF8Oj1W24bifG65Fl3hCaivZpkO6EyOHHaRavZxE,12
|
|
17
|
+
python_materialsdb-0.1.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
materialsdb/__init__.py,sha256=2UuaCCmONhxIvpxB1eXHh88eb05i2ilR0-F0o6rKlCQ,63
|
|
2
|
-
materialsdb/cache.py,sha256=4YYaX4cyp-zahhdd5ejppk8_GZ3exwEb6GUeww2wmpQ,3327
|
|
3
|
-
materialsdb/classes.py,sha256=obuS_JRfdvqKJ8RMOx65X7AzI2J5W0xNgIXJ2y7evbM,21194
|
|
4
|
-
materialsdb/config.py,sha256=Bz16QTIl8u81g07www_zGdA-Ilsii_kbi3NYuSXlrlQ,1331
|
|
5
|
-
materialsdb/serialiser.py,sha256=27W9W2ux_00F5QPCUWFVwNcQJnrGy3loCRMXjm2x1Qk,5881
|
|
6
|
-
materialsdb/utils.py,sha256=jwJETjvBuHb6_K_7STsNaM-bE2Oc1X-th1xTrllW-Go,3081
|
|
7
|
-
materialsdb/ifc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
materialsdb/ifc/material_psets.json,sha256=g4Z-wOsnOP-TZR2R_9ikDpa9GalfyA8o3gDHvQtMAgg,15472
|
|
9
|
-
materialsdb/ifc/project_library.py,sha256=3H9LzIIpJEiygtx5ar8BFA-MkJy4n8F-TQ_uXX6PlK4,12190
|
|
10
|
-
python_materialsdb-0.0.2.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
11
|
-
python_materialsdb-0.0.2.dist-info/METADATA,sha256=MneyvAMo67vbqky_7fwLrFvc4KU91vGArYg7SjxNRo0,2546
|
|
12
|
-
python_materialsdb-0.0.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
13
|
-
python_materialsdb-0.0.2.dist-info/top_level.txt,sha256=5ZHbF8Oj1W24bifG65Fl3hCaivZpkO6EyOHHaRavZxE,12
|
|
14
|
-
python_materialsdb-0.0.2.dist-info/RECORD,,
|
{python_materialsdb-0.0.2.dist-info → python_materialsdb-0.1.0.dist-info/licenses}/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|