first pass

This commit is contained in:
Jedd Morgan
2025-06-19 10:04:15 +01:00
parent 50d21313bb
commit f937df95e0
8 changed files with 238 additions and 48 deletions
+13 -5
View File
@@ -1,4 +1,5 @@
import time import time
from specklepy.api.operations import send from specklepy.api.operations import send
from specklepy.core.api.client import SpeckleClient from specklepy.core.api.client import SpeckleClient
from specklepy.core.api.credentials import get_accounts_for_server from specklepy.core.api.credentials import get_accounts_for_server
@@ -6,7 +7,8 @@ from specklepy.core.api.inputs.version_inputs import CreateVersionInput
from specklepy.core.api.models import Version from specklepy.core.api.models import Version
from specklepy.transports.server import ServerTransport from specklepy.transports.server import ServerTransport
from speckleifc.importer import convert_file from speckleifc.ifc_geometry_processing import open_ifc
from speckleifc.importer import ImportJob
### ###
@@ -22,12 +24,16 @@ def main() -> Version:
MODEL_ID = "0e23cfdea3" MODEL_ID = "0e23cfdea3"
SERVER_URL = "app.speckle.systems" SERVER_URL = "app.speckle.systems"
# FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\60mins.ifc" # FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\60mins.ifc"
FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\hillside_house_meters.ifc" # FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\hillside_house_meters.ifc"
# FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc"
# FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc" # FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc"
FILE_PATH = "C:\\Users\\Jedd\\Desktop\\openshell\\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc"
start = time.time() start = time.time()
data = convert_file(FILE)
ifc_file = open_ifc(FILE_PATH)
import_job = ImportJob(ifc_file)
data = import_job.convert()
print(f"File conversion complete after {(time.time() - start) * 1000}ms") print(f"File conversion complete after {(time.time() - start) * 1000}ms")
start = time.time() start = time.time()
@@ -35,7 +41,7 @@ def main() -> Version:
remote_transport = ServerTransport(PROJECT_ID, account=account) remote_transport = ServerTransport(PROJECT_ID, account=account)
root_id = send(data, transports=[remote_transport]) root_id = send(data, transports=[remote_transport], use_default_cache=False)
print(f"Sending to speckle complete after: {(time.time() - start) * 1000}ms") print(f"Sending to speckle complete after: {(time.time() - start) * 1000}ms")
start = time.time() start = time.time()
@@ -52,4 +58,6 @@ def main() -> Version:
if __name__ == "__main__": if __name__ == "__main__":
start = time.time()
main() main()
print(f"Total time: {(time.time() - start) * 1000}ms")
@@ -0,0 +1,29 @@
from typing import cast
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.ifcopenshell_wrapper import Triangulation, TriangulationElement
from specklepy.objects.data_objects import DataObject
from speckleifc.converter.geometry_converter import geometry_to_speckle
def data_object_to_speckle(
shape: TriangulationElement, step_element: entity_instance
) -> DataObject:
geometry = cast(Triangulation, shape.geometry)
display_value = geometry_to_speckle(geometry)
data_object = DataObject(
applicationId=cast(str, shape.guid),
properties={},
name=cast(str, shape.name) or cast(str, shape.guid),
displayValue=display_value,
)
# TODO: children as "elements"
# data_object["@elements"] = children_converter.convert_children(shape, ifc_model)
data_object["ifcType"] = cast(str, shape.type)
data_object["expressId"] = cast(int, shape.id)
data_object["description"] = cast(str, step_element.Description)
return data_object
@@ -1,16 +1,18 @@
from typing import cast from typing import cast
from ifcopenshell import file from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.ifcopenshell_wrapper import Triangulation, TriangulationElement from ifcopenshell.ifcopenshell_wrapper import Triangulation, TriangulationElement
from specklepy.objects.data_objects import DataObject from specklepy.objects.data_objects import DataObject
from speckleifc.converter.geometry_converter import geometry_to_speckle from speckleifc.converter.geometry_converter import geometry_to_speckle
def data_object_to_speckle(shape: TriangulationElement, ifc_model: file) -> DataObject: def data_object_to_speckle(
shape: TriangulationElement, step_element: entity_instance
) -> DataObject:
geometry = cast(Triangulation, shape.geometry) geometry = cast(Triangulation, shape.geometry)
display_value = geometry_to_speckle(geometry, ifc_model) display_value = geometry_to_speckle(geometry)
data_object = DataObject( data_object = DataObject(
applicationId=cast(str, shape.guid), applicationId=cast(str, shape.guid),
@@ -21,8 +23,7 @@ def data_object_to_speckle(shape: TriangulationElement, ifc_model: file) -> Data
# TODO: children as "elements" # TODO: children as "elements"
# data_object["@elements"] = children_converter.convert_children(shape, ifc_model) # data_object["@elements"] = children_converter.convert_children(shape, ifc_model)
data_object["ifcType"] = cast(str, shape.type) data_object["ifcType"] = step_element.is_a()
data_object["expressId"] = cast(str, shape.id) data_object["expressId"] = step_element.id()
data_object["ownerId"] = cast(str, shape.parent_id) data_object["description"] = cast(str | None, step_element.Description)
data_object["description"] = cast(str, shape.unique_id)
return data_object return data_object
@@ -2,13 +2,12 @@ from collections import defaultdict
from collections.abc import Sequence from collections.abc import Sequence
from typing import cast from typing import cast
from ifcopenshell import file
from ifcopenshell.ifcopenshell_wrapper import Triangulation from ifcopenshell.ifcopenshell_wrapper import Triangulation
from specklepy.objects import Base from specklepy.objects import Base
from specklepy.objects.geometry import Mesh from specklepy.objects.geometry import Mesh
def geometry_to_speckle(geometry: Triangulation, ifc_model: file) -> list[Base]: def geometry_to_speckle(geometry: Triangulation) -> list[Base]:
materials = cast(Sequence[int], geometry.materials) materials = cast(Sequence[int], geometry.materials)
MESH_COUNT = max(len(materials), 1) MESH_COUNT = max(len(materials), 1)
@@ -0,0 +1,32 @@
from typing import cast
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.ifcopenshell_wrapper import Triangulation
from specklepy.objects.data_objects import DataObject
from speckleifc.converter.geometry_converter import geometry_to_speckle
from speckleifc.ifc_geometry_processing import get_shape
def spatial_element_to_speckle(step_element: entity_instance) -> DataObject:
if step_element.Representation is not None:
shape = get_shape(step_element)
geometry = cast(Triangulation, shape.geometry)
display_value = geometry_to_speckle(geometry)
else:
display_value = []
data_object = DataObject(
applicationId=cast(str, shape.guid),
properties={},
name=cast(str, shape.name) or cast(str, shape.guid),
displayValue=display_value,
)
# TODO: children as "elements"
# data_object["@elements"] = children_converter.convert_children(shape, ifc_model)
data_object["ifcType"] = step_element.is_a()
data_object["expressId"] = step_element.id()
data_object["description"] = cast(str | None, step_element.Description)
return data_object
@@ -1,11 +1,13 @@
import multiprocessing import multiprocessing
from typing import cast
from ifcopenshell import file, ifcopenshell_wrapper, open, sqlite from ifcopenshell import file, ifcopenshell_wrapper, open, sqlite
from ifcopenshell.geom import iterator, settings from ifcopenshell.geom import create_shape, iterator, settings
from ifcopenshell.ifcopenshell_wrapper import TriangulationElement
from specklepy.logging.exceptions import SpeckleException from specklepy.logging.exceptions import SpeckleException
def _create_settings() -> settings: def _create_base_settings() -> settings:
ifc_settings = settings() ifc_settings = settings()
ifc_settings.set("triangulation-type", ifcopenshell_wrapper.TRIANGLE_MESH) ifc_settings.set("triangulation-type", ifcopenshell_wrapper.TRIANGLE_MESH)
ifc_settings.set("weld-vertices", False) ifc_settings.set("weld-vertices", False)
@@ -14,6 +16,16 @@ def _create_settings() -> settings:
return ifc_settings return ifc_settings
def _create_iterator_settings() -> settings:
ifc_settings = _create_base_settings()
return ifc_settings
_IFC_ITERATOR_SETTINGS = _create_iterator_settings()
_IFC_SETTINGS = _create_base_settings()
def open_ifc(file_path: str) -> file: def open_ifc(file_path: str) -> file:
ifc_file = open(file_path) ifc_file = open(file_path)
@@ -24,6 +36,10 @@ def open_ifc(file_path: str) -> file:
def create_geometry_iterator(ifc_file: file | sqlite) -> iterator: def create_geometry_iterator(ifc_file: file | sqlite) -> iterator:
settings = _create_settings()
return iterator(settings, ifc_file, multiprocessing.cpu_count() // 2) return iterator(_IFC_ITERATOR_SETTINGS, ifc_file, multiprocessing.cpu_count() // 2)
def get_shape(element) -> TriangulationElement:
shape = create_shape(_IFC_SETTINGS, element)
return cast(TriangulationElement, shape)
+54 -30
View File
@@ -1,36 +1,60 @@
from specklepy.logging.exceptions import SpeckleException # noqa: I001 from typing import cast
from ifcopenshell import file
from ifcopenshell.entity_instance import entity_instance
from ifcopenshell.geom import file
from ifcopenshell.ifcopenshell_wrapper import TriangulationElement from ifcopenshell.ifcopenshell_wrapper import TriangulationElement
from speckleifc.converter.data_object_converter import data_object_to_speckle from specklepy.logging.exceptions import SpeckleException
from speckleifc.ifc_iterator import create_geometry_iterator, open_ifc
from specklepy.objects import Base from specklepy.objects import Base
from specklepy.objects.models.collections.collection import Collection from specklepy.objects.models.collections.collection import Collection
from speckleifc.converter.data_object_converter import data_object_to_speckle
def convert_file(file_path: str) -> Collection: from speckleifc.ifc_geometry_processing import create_geometry_iterator
file = open_ifc(file_path) from speckleifc.root_object_builder import RootObjectBuilder
iterator = create_geometry_iterator(file)
if not iterator.initialize():
raise SpeckleException("Iterator failed to initialize")
converted_geometry: list[Base] = []
while True:
element = iterator.get()
assert isinstance(element, TriangulationElement)
converted = convert_geometry_element(element, file)
converted_geometry.append(converted)
if not iterator.next():
break
return Collection(name="root collection", elements=converted_geometry)
def convert_geometry_element( class ImportJob:
geometry_element: TriangulationElement, ifc_model: file
) -> Base: def __init__(self, ifc_file: file):
# step_entity = ifc_model.by_id(geometry_element.id()) self._ifc_file = ifc_file
return data_object_to_speckle(geometry_element, ifc_model) self.builder = RootObjectBuilder()
def convert(self) -> Collection:
self._convert_spatial_elements()
self._convert_geometry()
root = Collection(name="root collection") # todo: replace with project
self.builder.build_commit_object(root)
return root
def _convert_geometry(self) -> None:
geometry_iterator = create_geometry_iterator(self._ifc_file)
if not geometry_iterator.initialize():
raise SpeckleException("Iterator failed to initialize")
while True:
shape = geometry_iterator.get()
assert isinstance(shape, TriangulationElement)
step_id = cast(int, shape.id)
step_element = self._ifc_file.by_id(step_id)
converted = self.convert_geometry_element(shape, step_element)
self.builder.include_shape(converted, shape)
if not geometry_iterator.next():
break
def _convert_spatial_elements(self) -> None:
spatial_elements = self._ifc_file.by_type("IfcSpatialElement")
for element in spatial_elements:
element
@staticmethod
def convert_geometry_element(
geometry_element: TriangulationElement, step_element: entity_instance
) -> Base:
# step_entity = ifc_model.by_id(geometry_element.id())
return data_object_to_speckle(geometry_element, step_element)
+81
View File
@@ -0,0 +1,81 @@
from collections.abc import Sequence
from typing import cast
from attrs import define
from ifcopenshell.ifcopenshell_wrapper import Element
from specklepy.objects.base import Base
from specklepy.objects.graph_traversal.commit_object_builder import (
get_detached_prop,
set_detached_prop,
)
ROOT: int = -1
ELEMENTS = "elements"
PARENT_INFO = tuple[int | None, str]
@define(slots=True)
class RootObjectBuilder:
converted: dict[int, Base]
_parent_infos: dict[int, Sequence[PARENT_INFO]]
def __init__(self) -> None:
self.converted = {}
self._parent_infos = {}
def include_shape(self, conversion_result: Base, shape: Element) -> None:
step_id = cast(int, shape.id)
self.converted[step_id] = conversion_result
self.set_relationship(
step_id, ((cast(int, shape.parent_id), ELEMENTS), (ROOT, ELEMENTS))
)
def build_commit_object(self, root_commit_object: Base) -> None:
self.apply_relationships(root_commit_object)
def set_relationship(
self, step_id: int, parent_info: Sequence[PARENT_INFO]
) -> None:
self._parent_infos[step_id] = parent_info
def apply_relationships(self, root_commit_object: Base) -> None:
for step_id, c in self.converted.items():
try:
self.apply_relationship(c, step_id, root_commit_object)
except Exception as ex:
print(f"Failed to add object {type(c)} to commit object: {ex}")
def apply_relationship(
self, current: Base, step_id: int, root_commit_object: Base
) -> None:
parents = self._parent_infos[step_id]
for parent_id, prop_name in parents:
if not parent_id:
continue
parent: Base | None
if parent_id == ROOT:
parent = root_commit_object
else:
parent = self.converted.get(parent_id, None)
if not parent:
continue
elements = get_detached_prop(parent, prop_name)
if not isinstance(elements, list):
elements = []
set_detached_prop(parent, prop_name, elements)
elements.append(current)
return
raise Exception(
f"Could not find a valid parent for object of type {type(current)}."
f"Checked {len(parents)} potential parent, and non were converted!"
)