diff --git a/src/blenderbim/scripts/standalone_blender_import.py b/src/blenderbim/scripts/standalone_blender_import.py new file mode 100644 index 0000000000..c91c2af0da --- /dev/null +++ b/src/blenderbim/scripts/standalone_blender_import.py @@ -0,0 +1,125 @@ +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.geom +import multiprocessing + + +class BlenderImporter: + def __init__(self): + self.file = ifcopenshell.open('/home/dion/untitled.ifc') + self.cache_path = "cache.h5" + self.should_use_cpu_multiprocessing = True + self.deflection_tolerance = 0.001 + self.angular_tolerance = 0.5 + + def execute(self): + self.created_guids = set() + + self.settings = ifcopenshell.geom.settings() + self.settings.set_deflection_tolerance(self.deflection_tolerance) + self.settings.set_angular_tolerance(self.angular_tolerance) + self.settings.set(self.settings.STRICT_TOLERANCE, True) + self.settings_2d = ifcopenshell.geom.settings() + self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True) + self.settings_2d.set(self.settings.STRICT_TOLERANCE, True) + + self.process_element_filter() + self.process_context_filter() + self.create_elements(self.elements) + + def process_element_filter(self): + # Blender users can typically customise a filtered set of elements to import. + # For simplicity, let's select everything: + self.elements = set(self.file.by_type("IfcProduct")) + + def process_context_filter(self): + # Facetation is to accommodate broken Revit files + # See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult + self.body_contexts = [ + c.id() + for c in self.file.by_type("IfcGeometricRepresentationSubContext") + if c.ContextIdentifier in ["Body", "Facetation"] + ] + # Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly + self.body_contexts.extend( + [ + c.id() + for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False) + if c.ContextType == "Model" + ] + ) + if self.body_contexts: + self.settings.set_context_ids(self.body_contexts) + # Annotation is to accommodate broken Revit files + # See https://github.com/Autodesk/revit-ifc/issues/187 + self.plan_contexts = [ + c.id() + for c in self.file.by_type("IfcGeometricRepresentationContext") + if c.ContextType in ["Plan", "Annotation"] + ] + if self.plan_contexts: + self.settings_2d.set_context_ids(self.plan_contexts) + + def create_elements(self, elements): + # Based on my experience in viewing BIM models, representations are prioritised as follows: + # 1. 3D Body, 2. 2D Plans, 3. Point clouds, 4. No representation + # If an element has a representation that doesn't follow 1, 2, or 3, it will not show by default. + # The user can load them later if they want to view them. + products = self.create_products(elements) + elements -= products + products = self.create_products(elements, is_curve=True) + elements -= products + # Creating point clouds and non geometric objects are specific to Blender so no code is shown. + pass + + def create_products(self, products, is_curve=False): + results = set() + if not products: + return results + settings = self.settings_2d if is_curve else self.settings + if self.should_use_cpu_multiprocessing: + iterator = ifcopenshell.geom.iterator( + settings, self.file, multiprocessing.cpu_count(), include=products + ) + else: + iterator = ifcopenshell.geom.iterator(settings, self.file, include=products) + cache = self.get_cache() + if cache: + iterator.set_cache(cache) + valid_file = iterator.initialize() + if not valid_file: + return results + total = 0 + while True: + total += 1 + if total % 250 == 0: + print(f"{total} elements processed") + shape = iterator.get() + if shape: + product = self.file.by_id(shape.guid) + if self.body_contexts: + print("Created", product) + self.created_guids.add(shape.guid) + results.add(product) + else: + if shape.context not in ["Body", "Facetation"] and shape.guid in self.created_guids: + # We only load a single context, and we prioritise the Body context. See #1290. + pass + else: + print("Created", product) + self.created_guids.add(shape.guid) + results.add(product) + if not iterator.next(): + break + print("Done creating geometry") + return results + + def get_cache(self): + cache_settings = ifcopenshell.geom.settings() + cache_settings.set(cache_settings.STRICT_TOLERANCE, True) + try: + return ifcopenshell.geom.serializers.hdf5(self.cache_path, cache_settings) + except: + return + +BlenderImporter().execute()