First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,246 @@
import sys
from itertools import accumulate
from typing import IO, Optional
from . import ifcopenshell_wrapper
class TransformDefaultDict:
def __init__(self, keyfunc=lambda x: x, default_factory=None):
"""
Similar to collections.defaultdict, but allows for storing
non-hashable keys by means of a transform function
"""
self.keyfunc = keyfunc
self.default_factory = default_factory
self._data = {}
self._original_keys = {}
def __setitem__(self, key, value):
k = self.keyfunc(key)
self._data[k] = value
self._original_keys[k] = key
def __getitem__(self, key):
k = self.keyfunc(key)
if k in self._data:
return self._data[k]
if self.default_factory is not None:
value = self.default_factory()
self._data[k] = value
self._original_keys[k] = key
return value
raise KeyError(key)
def __contains__(self, key):
return self.keyfunc(key) in self._data
def __delitem__(self, key):
k = self.keyfunc(key)
del self._data[k]
del self._original_keys[k]
def __iter__(self):
return iter(self._original_keys.values())
def __len__(self):
return len(self._data)
def items(self):
return ((self._original_keys[k], v) for k, v in self._data.items())
def keys(self):
return self._original_keys.values()
def values(self):
return self._data.values()
def get(self, key, default=None):
return self._data.get(self.keyfunc(key), default)
def setdefault(self, key, default=None):
k = self.keyfunc(key)
if k not in self._data:
self._data[k] = default
self._original_keys[k] = key
return self._data[k]
def update(self, other):
for k, v in other.items():
self[k] = v
def clear(self):
self._data.clear()
self._original_keys.clear()
def __repr__(self):
items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
return f"{self.__class__.__name__}({{{items}}})"
class ProductCounter:
def __init__(self, only_with_representation=True):
self.total = 0
transform_func = lambda decl: decl.name() if hasattr(decl, "name") else decl
self.counts = TransformDefaultDict(keyfunc=transform_func, default_factory=int)
self.counts_excl = TransformDefaultDict(keyfunc=transform_func, default_factory=int)
self.only_with_representation = only_with_representation
def count(self, decl, values):
if decl._is("IfcProduct"):
if self.only_with_representation and values.get("Representation") is None:
return
self.counts_excl[decl] += 1
while decl.name() != "IfcProduct":
self.counts[decl] += 1
decl = decl.supertype()
self.total += 1
def print(self, out=sys.stdout):
if self.counts:
results = []
def process(ent, level=-1):
if ent in self.counts:
results.append((level, ent.name(), self.counts[ent]))
for subtype in ent.subtypes():
process(subtype, level=level + 1)
prod = next(iter(self.counts.keys())).schema().declaration_by_name("ifcproduct")
process(prod)
max_width = max(level * 2 + len(name) for level, name, _ in results)
for level, name, count in results:
print(" " * (level * 2), f"{name:<{max_width - level * 2}}", ": ", count, file=out, sep="")
class BooleanResultCounter:
def __init__(self, exclude_union=True):
self.total = 0
self.exclude_union = exclude_union
def count(self, decl, values):
if decl._is("IfcBooleanResult"):
if self.exclude_union and values.get("Operator") == "UNION":
return
self.total += 1
def print(self, out=sys.stdout):
print("IfcBooleanResult:", self.total, file=out)
class StatsCollector:
streamer: ifcopenshell_wrapper.InstanceStreamer
page_size: Optional[int]
file_stream: Optional[IO[str]]
finalized: bool = False
needs_data: bool = False
counters: list
num_semis: int = 0
def __init__(self):
self.streamer = ifcopenshell_wrapper.InstanceStreamer()
self.needs_data = True
self.counters = [ProductCounter(), BooleanResultCounter()]
def feedFromFile(self, f: Optional[IO[str]] = None):
if f:
self.file_stream = f
self.feed(self.file_stream.read(self.page_size))
def feed(self, data: str):
self.streamer.pushPage(data)
self.needs_data = False
self.num_semis = self.streamer.semicolonCount()
@staticmethod
def fromFilePath(fn, page_size: int = 102400):
collector = StatsCollector()
collector.page_size = page_size
collector.feedFromFile(open(str(fn), encoding="ascii"))
return collector
def next(self):
if self.num_semis > 0:
if inst := self.streamer.readInstancePy(True):
self.num_semis -= 1
return inst["type"], dict(list(inst.items())[2:])
else:
self.finalized = True
return None
elif self.file_stream:
self.feedFromFile()
return self.next()
else:
self.needs_data = True
return None
def process(self):
if n := self.next():
decl, values = n
else:
return
if decl.schema().name() == "HEADER_SECTION_SCHEMA":
return
for cnt in self.counters:
cnt.count(decl, values)
def print(self, out=sys.stdout):
for cnt in self.counters:
print(type(cnt).__name__, file=out)
print("=" * len(type(cnt).__name__), file=out)
cnt.print(out)
def includeElementTypesBasedOnBudget(self, priorities: dict, budget):
def specificity(decl, target):
if decl._is(target):
i = 0
while decl.name() != target:
decl = decl.supertype()
i += 1
return i
return None
def calc_prio(ty):
# find most specific priority directive for the given type in ty
return min(((specificity(ty, k), v) for k, v in priorities.items() if ty._is(k)), default=(0, 0))[1]
sorted_types = sorted(self.counters[0].counts_excl.keys(), key=calc_prio, reverse=True)
sorted_types = [s for s in sorted_types if not s._is("IfcFeatureElement")]
counts = [self.counters[0].counts_excl[v] for v in sorted_types]
ccounts = list(accumulate(counts))
if ccounts[0] >= budget:
# At the very least include one type
return sorted_types[0:1]
for i, s in enumerate(ccounts):
if s >= budget:
over = s - budget
under = budget - ccounts[i - 1]
k = i if over <= under else i - 1
return sorted_types[0 : k + 1]
# We're lucky we can render everything in budget
return sorted_types[:]
if __name__ == "__main__":
collector = StatsCollector.fromFilePath(sys.argv[1])
while not collector.finalized:
collector.process()
collector.print()
print(
*collector.includeElementTypesBasedOnBudget(
{
"IfcWall": 3,
"IfcSlab": 3,
"IfcWindow": 2,
"IfcDoor": 2,
"IfcBuildingElement": 1,
}
)
)