Merge branch 'v0.7.0' of https://github.com/bdamay/IfcOpenShell into v0.7.0

This commit is contained in:
Benoit DAMAY
2024-03-04 17:11:54 +01:00
11 changed files with 1944 additions and 78 deletions
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1504,7 +1504,7 @@ class Drawing(blenderbim.core.tool.Drawing):
def get_drawing_metadata(cls, drawing): def get_drawing_metadata(cls, drawing):
return [ return [
v.strip() v.strip()
for v in ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "").split(",") for v in (ifcopenshell.util.element.get_psets(drawing)["EPset_Drawing"].get("Metadata", "") or "").split(",")
if v if v
] ]
@@ -16,7 +16,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy import bpy
import csv
import random import random
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
@@ -30,6 +32,10 @@ from mathutils import Vector, Matrix
from random import uniform from random import uniform
# When run from Blender
BLEND_DIR = os.path.dirname(bpy.data.filepath)
OUT_PATH = os.path.join(BLEND_DIR, "..", "blenderbim", "bim", "data", "libraries", "IFC4 Landscape Library.ifc")
SimpleTreeParams = namedtuple("SimpleTreeParams", "plant_height crown_diameter trunk_diameter") SimpleTreeParams = namedtuple("SimpleTreeParams", "plant_height crown_diameter trunk_diameter")
LowPolyTreeParams = namedtuple( LowPolyTreeParams = namedtuple(
"LowPolyTreeParams", "plant_height crown_diameter crown_max_loc crown_taper trunk_height trunk_diameter random_seed" "LowPolyTreeParams", "plant_height crown_diameter crown_max_loc crown_taper trunk_height trunk_diameter random_seed"
@@ -301,7 +307,7 @@ tree_presets = {
class LibraryGenerator: class LibraryGenerator:
def generate(self, library_name, output_filename="IFC4 EU Steel.ifc"): def generate(self, library_name, output_filename):
ifcopenshell.api.pre_listeners = {} ifcopenshell.api.pre_listeners = {}
ifcopenshell.api.post_listeners = {} ifcopenshell.api.post_listeners = {}
@@ -368,7 +374,7 @@ class LibraryGenerator:
representations[rep_key] = rep_obj.name representations[rep_key] = rep_obj.name
self.create_type("IfcGeographicElementType", obj.name, representations) self.create_type("IfcGeographicElementType", obj.name, representations)
# Auto generated trees # Auto generated generic trees
self.builder = ShapeBuilder(self.file) self.builder = ShapeBuilder(self.file)
builder = self.builder builder = self.builder
@@ -401,6 +407,21 @@ class LibraryGenerator:
"IfcGeographicElementType", tree_data.tree_name, **self.get_representations(tree_geometry) "IfcGeographicElementType", tree_data.tree_name, **self.get_representations(tree_geometry)
) )
# From tree species table
with open(os.path.join(BLEND_DIR, "tree_species.csv"), 'r') as csvfile:
reader = csv.reader(csvfile)
for i, row in enumerate(reader):
if i == 0:
continue
preset = tree_presets.get(row[0])
data = [float(x) for x in row[2:]]
preset_data = preset.preset_class(*data)._asdict()
tree_geometry = preset.generator(builder, **preset_data)
self.create_explicit_type(
"IfcGeographicElementType", row[1], **self.get_representations(tree_geometry)
)
self.file.write(output_filename) self.file.write(output_filename)
def get_representations(self, generated_geometry: GeneratedGeometry): def get_representations(self, generated_geometry: GeneratedGeometry):
@@ -464,4 +485,4 @@ class LibraryGenerator:
if __name__ == "__main__": if __name__ == "__main__":
LibraryGenerator().generate("Landscape Assets Library", output_filename="IFC4 Landscape Library.ifc") LibraryGenerator().generate("Landscape Assets Library", output_filename=OUT_PATH)
+27
View File
@@ -0,0 +1,27 @@
"f","name","PlantHeight","CrownDiameter","CrownMaxLoc","CrownTaper","TrunkHeight","TrunkDiameter","RandomSeed"
"low_poly","Apple",8,9,0.5,0.2,1.5,0.2,1
"low_poly","Beech",30,20,0,0.4,3,0.75,2
"low_poly","Cedar",25,17,0,1,1,0.625,3
"low_poly","Dogwood",8,6,1,0.9,0.3,0.2,4
"low_poly","Elm",17,12,0.6,0.7,2,0.425,5
"low_poly","Fir",27,15,0.1,1,0.1,0.675,6
"low_poly","Ginkgo",25,9,0.5,0.6,7,0.625,7
"low_poly","Horsechesnut",30,23,0.5,0.2,3,0.75,8
"low_poly","Ilex",26,13,0.4,0.3,12,0.65,9
"low_poly","Juniper",30,15,0.7,1,1,0.75,10
"low_poly","Kentucky Coffeetree",20,10,0.5,0,6,0.5,11
"low_poly","Linden",28,18,0.3,0.2,3,0.7,12
"low_poly","Mulberry",15,12,0.5,0.5,2,0.375,13
"low_poly","Nannybery",5,4,0.7,0.8,1,0.125,14
"low_poly","Oak",25,13,0.5,0.5,6,0.625,15
"low_poly","Pine",27,9,0.5,0.2,14,0.675,16
"low_poly","Quaking Aspen",13,4,0.5,0.3,4,0.325,17
"low_poly","Redwood",90,24,0.1,0.8,8,2.25,18
"low_poly","Sycamore",35,22,0.2,0.6,5,0.875,19
"low_poly","Tree of Heaven",20,15,0.2,0.8,3,0.5,20
"low_poly","Umbrella Tree",8,5,0.4,0.5,2,0.2,21
"low_poly","Viburnum",4,3,0.8,0.7,0.2,0.1,22
"low_poly","Weeping Willow",13,12,0.9,0.3,1,0.325,23
"low_poly","Zanthoxylum",10,8,0.4,0.6,2,0.25,24
"low_poly","Yellowwood",25,20,0.2,0.3,5,0.625,25
"low_poly","Zelkova",12,8,0.4,0.4,2,0.3,26
1 f name PlantHeight CrownDiameter CrownMaxLoc CrownTaper TrunkHeight TrunkDiameter RandomSeed
2 low_poly Apple 8 9 0.5 0.2 1.5 0.2 1
3 low_poly Beech 30 20 0 0.4 3 0.75 2
4 low_poly Cedar 25 17 0 1 1 0.625 3
5 low_poly Dogwood 8 6 1 0.9 0.3 0.2 4
6 low_poly Elm 17 12 0.6 0.7 2 0.425 5
7 low_poly Fir 27 15 0.1 1 0.1 0.675 6
8 low_poly Ginkgo 25 9 0.5 0.6 7 0.625 7
9 low_poly Horsechesnut 30 23 0.5 0.2 3 0.75 8
10 low_poly Ilex 26 13 0.4 0.3 12 0.65 9
11 low_poly Juniper 30 15 0.7 1 1 0.75 10
12 low_poly Kentucky Coffeetree 20 10 0.5 0 6 0.5 11
13 low_poly Linden 28 18 0.3 0.2 3 0.7 12
14 low_poly Mulberry 15 12 0.5 0.5 2 0.375 13
15 low_poly Nannybery 5 4 0.7 0.8 1 0.125 14
16 low_poly Oak 25 13 0.5 0.5 6 0.625 15
17 low_poly Pine 27 9 0.5 0.2 14 0.675 16
18 low_poly Quaking Aspen 13 4 0.5 0.3 4 0.325 17
19 low_poly Redwood 90 24 0.1 0.8 8 2.25 18
20 low_poly Sycamore 35 22 0.2 0.6 5 0.875 19
21 low_poly Tree of Heaven 20 15 0.2 0.8 3 0.5 20
22 low_poly Umbrella Tree 8 5 0.4 0.5 2 0.2 21
23 low_poly Viburnum 4 3 0.8 0.7 0.2 0.1 22
24 low_poly Weeping Willow 13 12 0.9 0.3 1 0.325 23
25 low_poly Zanthoxylum 10 8 0.4 0.6 2 0.25 24
26 low_poly Yellowwood 25 20 0.2 0.3 5 0.625 25
27 low_poly Zelkova 12 8 0.4 0.4 2 0.3 26
@@ -84,12 +84,12 @@ class Usecase:
# Now we can easily append our wall type from our libary # Now we can easily append our wall type from our libary
wall_type = ifcopenshell.api.run("project.append_asset", model, library=library, element=wall_type) wall_type = ifcopenshell.api.run("project.append_asset", model, library=library, element=wall_type)
""" """
self.file = file self.file: ifcopenshell.file = file
self.settings = {"library": library, "element": element} self.settings = {"library": library, "element": element}
def execute(self): def execute(self):
# mapping of old element ids to new elements # mapping of old element ids to new elements
self.added_elements:dict[int, ifcopenshell.entity_instance] = {} self.added_elements: dict[int, ifcopenshell.entity_instance] = {}
self.whitelisted_inverse_attributes = {} self.whitelisted_inverse_attributes = {}
if self.settings["element"].is_a("IfcTypeProduct"): if self.settings["element"].is_a("IfcTypeProduct"):
self.target_class = "IfcTypeProduct" self.target_class = "IfcTypeProduct"
@@ -268,6 +268,7 @@ class Usecase:
def reuse_existing_contexts(self): def reuse_existing_contexts(self):
added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")]) added_contexts = set([e for e in self.added_elements.values() if e.is_a("IfcGeometricRepresentationContext")])
added_contexts -= set(self.existing_contexts)
for added_context in added_contexts: for added_context in added_contexts:
equivalent_existing_context = self.get_equivalent_existing_context(added_context) equivalent_existing_context = self.get_equivalent_existing_context(added_context)
if not equivalent_existing_context: if not equivalent_existing_context:
@@ -30,6 +30,7 @@ import functools
import subprocess import subprocess
import sys import sys
import time import time
from typing import Union
from . import ifcopenshell_wrapper from . import ifcopenshell_wrapper
from . import settings from . import settings
@@ -117,6 +118,8 @@ class entity_instance(object):
>>> #423=IfcProductDefinitionShape($,$,(#409,#421)) >>> #423=IfcProductDefinitionShape($,$,(#409,#421))
""" """
wrapped_data: ifcopenshell_wrapper.entity_instance
def __init__(self, e, file=None): def __init__(self, e, file=None):
if isinstance(e, tuple): if isinstance(e, tuple):
e = ifcopenshell_wrapper.new_IfcBaseClass(*e) e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
@@ -221,7 +224,7 @@ class entity_instance(object):
return entity_instance.walk(is_instance, unwrap, v) return entity_instance.walk(is_instance, unwrap, v)
def attribute_type(self, attr): def attribute_type(self, attr: int) -> str:
"""Return the data type of a positional attribute of the element """Return the data type of a positional attribute of the element
:param attr: The index of the attribute :param attr: The index of the attribute
@@ -231,7 +234,7 @@ class entity_instance(object):
attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr) attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr)
return self.wrapped_data.get_argument_type(attr_idx) return self.wrapped_data.get_argument_type(attr_idx)
def attribute_name(self, attr_idx): def attribute_name(self, attr_idx: int) -> str:
"""Return the name of a positional attribute of the element """Return the name of a positional attribute of the element
:param attr_idx: The index of the attribute :param attr_idx: The index of the attribute
@@ -272,7 +275,7 @@ class entity_instance(object):
def __repr__(self): def __repr__(self):
return repr(self.wrapped_data) return repr(self.wrapped_data)
def to_string(self, valid_spf=True): def to_string(self, valid_spf=True) -> str:
"""Returns a string representation of the current entity instance. """Returns a string representation of the current entity instance.
Equal to str(self) when valid_spf=False. When valid_spf is True Equal to str(self) when valid_spf=False. When valid_spf is True
returns a representation of the string that conforms to valid Step returns a representation of the string that conforms to valid Step
@@ -283,7 +286,7 @@ class entity_instance(object):
return self.wrapped_data.to_string(valid_spf) return self.wrapped_data.to_string(valid_spf)
def is_a(self, *args): def is_a(self, *args) -> Union[str, bool]:
"""Return the IFC class name of an instance, or checks if an instance belongs to a class. """Return the IFC class name of an instance, or checks if an instance belongs to a class.
The check will also return true if a parent class name is provided. The check will also return true if a parent class name is provided.
@@ -306,7 +309,7 @@ class entity_instance(object):
""" """
return self.wrapped_data.is_a(*args) return self.wrapped_data.is_a(*args)
def id(self): def id(self) -> int:
"""Return the STEP numerical identifier """Return the STEP numerical identifier
:rtype: int :rtype: int
@@ -335,7 +338,7 @@ class entity_instance(object):
other.wrapped_data.file_pointer(), other.wrapped_data.file_pointer(),
) )
def is_entity(self): def is_entity(self) -> bool:
"""Tests whether the instance is an entity type as opposed to a simple data type. """Tests whether the instance is an entity type as opposed to a simple data type.
Returns: Returns:
@@ -430,7 +433,9 @@ class entity_instance(object):
) )
) )
def get_info(self, include_identifier=True, recursive=False, return_type=dict, ignore=(), scalar_only=False): def get_info(
self, include_identifier=True, recursive=False, return_type=dict, ignore=(), scalar_only=False
) -> dict:
"""Return a dictionary of the entity_instance's properties (Python and IFC) and their values. """Return a dictionary of the entity_instance's properties (Python and IFC) and their values.
:param include_identifier: Whether or not to include the STEP numerical identifier :param include_identifier: Whether or not to include the STEP numerical identifier
+35 -13
View File
@@ -20,6 +20,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import annotations
import os import os
import re import re
@@ -27,6 +28,7 @@ import numbers
import zipfile import zipfile
import functools import functools
from pathlib import Path from pathlib import Path
from typing import Tuple, List
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.file import ifcopenshell.util.file
@@ -195,7 +197,9 @@ class file(object):
print(products[0] == ifc_file[122] == ifc_file["2XQ$n5SLP5MBLyL442paFx"]) # True print(products[0] == ifc_file[122] == ifc_file["2XQ$n5SLP5MBLyL442paFx"]) # True
""" """
def __init__(self, f=None, schema=None, schema_version=None): wrapped_data: ifcopenshell_wrapper.file
def __init__(self, f: ifcopenshell_wrapper.file = None, schema: str = None, schema_version: Tuple[int] = None):
"""Create a new blank IFC model """Create a new blank IFC model
This IFC model does not have any entities in it yet. See the This IFC model does not have any entities in it yet. See the
@@ -253,8 +257,9 @@ class file(object):
self.transaction = None self.transaction = None
import weakref import weakref
file_dict[self.file_pointer()] = weakref.ref(self) file_dict[self.file_pointer()] = weakref.ref(self)
def __del__(self): def __del__(self):
del file_dict[self.file_pointer()] del file_dict[self.file_pointer()]
@@ -294,7 +299,7 @@ class file(object):
transaction.commit() transaction.commit()
self.history.append(transaction) self.history.append(transaction)
def create_entity(self, type, *args, **kwargs): def create_entity(self, type: str, *args, **kwargs) -> ifcopenshell.entity_instance:
"""Create a new IFC entity in the file. """Create a new IFC entity in the file.
:param type: Case insensitive name of the IFC class :param type: Case insensitive name of the IFC class
@@ -392,30 +397,43 @@ class file(object):
elif isinstance(key, basestring): elif isinstance(key, basestring):
return entity_instance(self.wrapped_data.by_guid(str(key)), self) return entity_instance(self.wrapped_data.by_guid(str(key)), self)
def by_id(self, id): def by_id(self, id: int) -> ifcopenshell.entity_instance:
"""Return an IFC entity instance filtered by IFC ID. """Return an IFC entity instance filtered by IFC ID.
:param id: STEP numerical identifier :param id: STEP numerical identifier
:type id: int :type id: int
:raises RuntimeError: If `id` is not found.
:returns: An ifcopenshell.entity_instance.entity_instance :returns: An ifcopenshell.entity_instance.entity_instance
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
""" """
return self[id] return self[id]
def by_guid(self, guid): def by_guid(self, guid: str) -> ifcopenshell.entity_instance:
"""Return an IFC entity instance filtered by IFC GUID. """Return an IFC entity instance filtered by IFC GUID.
:param guid: GlobalId value in 22-character encoded form :param guid: GlobalId value in 22-character encoded form
:type guid: string :type guid: string
:raises RuntimeError: If `guid` is not found.
:returns: An ifcopenshell.entity_instance.entity_instance :returns: An ifcopenshell.entity_instance.entity_instance
:rtype: ifcopenshell.entity_instance.entity_instance :rtype: ifcopenshell.entity_instance.entity_instance
""" """
return self[guid] return self[guid]
def add(self, inst, _id=None): def add(self, inst: ifcopenshell.entity_instance, _id: int = None) -> ifcopenshell.entity_instance:
"""Adds an entity including any dependent entities to an IFC file. """Adds an entity including any dependent entities to an IFC file.
If the entity already exists, it is not re-added. Existence of entity is checked by it's `.identity()`.
:param inst: The entity instance to add
:type inst: ifcopenshell.entity_instance.entity_instance
:returns: An ifcopenshell.entity_instance.entity_instance
:rtype: ifcopenshell.entity_instance.entity_instance
"""
If the entity already exists, it is not re-added."""
if self.transaction: if self.transaction:
max_id = self.wrapped_data.getMaxId() max_id = self.wrapped_data.getMaxId()
inst.wrapped_data.this.disown() inst.wrapped_data.this.disown()
@@ -425,7 +443,7 @@ class file(object):
[self.transaction.store_create(e) for e in reversed(added_elements)] [self.transaction.store_create(e) for e in reversed(added_elements)]
return result return result
def by_type(self, type, include_subtypes=True): def by_type(self, type: str, include_subtypes=True) -> List[ifcopenshell.entity_instance]:
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class. """Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
If an IFC type class has subclasses, all entities of those subclasses are also returned. If an IFC type class has subclasses, all entities of those subclasses are also returned.
@@ -441,7 +459,9 @@ class file(object):
return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)] return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)]
return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(type)] return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(type)]
def traverse(self, inst, max_levels=None, breadth_first=False): def traverse(
self, inst: ifcopenshell.entity_instance, max_levels=None, breadth_first=False
) -> List[ifcopenshell.entity_instance]:
"""Get a list of all referenced instances for a particular instance including itself """Get a list of all referenced instances for a particular instance including itself
:param inst: The entity instance to get all sub instances :param inst: The entity instance to get all sub instances
@@ -463,7 +483,9 @@ class file(object):
return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)] return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)]
def get_inverse(self, inst, allow_duplicate=False, with_attribute_indices=False): def get_inverse(
self, inst: ifcopenshell.entity_instance, allow_duplicate=False, with_attribute_indices=False
) -> List[ifcopenshell.entity_instance]:
"""Return a list of entities that reference this entity """Return a list of entities that reference this entity
:param inst: The entity instance to get inverse relationships :param inst: The entity instance to get inverse relationships
@@ -488,7 +510,7 @@ class file(object):
return set(inverses) return set(inverses)
def get_total_inverses(self, inst): def get_total_inverses(self, inst: ifcopenshell.entity_instance) -> int:
"""Returns the number of entities that reference this entity """Returns the number of entities that reference this entity
:param inst: The entity instance to get inverse relationships :param inst: The entity instance to get inverse relationships
@@ -498,7 +520,7 @@ class file(object):
""" """
return self.wrapped_data.get_total_inverses(inst.wrapped_data) return self.wrapped_data.get_total_inverses(inst.wrapped_data)
def remove(self, inst): def remove(self, inst: ifcopenshell.entity_instance) -> None:
"""Deletes an IFC object in the file. """Deletes an IFC object in the file.
Attribute values in other entity instances that reference the deleted Attribute values in other entity instances that reference the deleted
@@ -573,7 +595,7 @@ class file(object):
return return
@staticmethod @staticmethod
def from_string(s): def from_string(s: str) -> ifcopenshell.entity_instance:
return file(ifcopenshell_wrapper.read(s)) return file(ifcopenshell_wrapper.read(s))
@staticmethod @staticmethod
@@ -44,6 +44,26 @@ class TestAppendAsset(test.bootstrap.IFC4):
ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element) ifcopenshell.api.run("project.append_asset", self.file, library=library, element=element)
assert len(self.file.by_type("IfcWallType")) == 1 assert len(self.file.by_type("IfcWallType")) == 1
def test_reuse_an_existing_context_if_it_was_added_from_library_previously(self):
library = ifcopenshell.api.run("project.create_file")
project = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject")
lib_context = ifcopenshell.api.run("context.add_context", library, context_type="Model")
self.file.add(project) # will add project and it's contexts
material = ifcopenshell.api.run("material.add_material", library, name="Material")
style = ifcopenshell.api.run("style.add_style", library)
ifcopenshell.api.run(
"style.assign_material_style", library, material=material, style=style, context=lib_context
)
ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material)
assert len(self.file.by_type("IfcMaterial")) == 1
assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1
context = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems
# make sure it's still valid
assert context.WorldCoordinateSystem
def test_append_a_single_type_product_even_though_an_inverse_material_relationship_is_shared(self): def test_append_a_single_type_product_even_though_an_inverse_material_relationship_is_shared(self):
library = ifcopenshell.api.run("project.create_file") library = ifcopenshell.api.run("project.create_file")
element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType") element = ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcWallType")
@@ -202,24 +222,40 @@ class TestAppendAsset(test.bootstrap.IFC4):
style = ifcopenshell.api.run("style.add_style", library) style = ifcopenshell.api.run("style.add_style", library)
context = ifcopenshell.api.run("context.add_context", library, context_type="Model") context = ifcopenshell.api.run("context.add_context", library, context_type="Model")
ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=context) ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=context)
ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material)
assert len(self.file.by_type("IfcMaterial")) == 1 assert len(self.file.by_type("IfcMaterial")) == 1
assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1 assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1
context = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems context = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems
assert context == file_context assert context == file_context
# make sure it's still valid
assert context.WorldCoordinateSystem assert context.WorldCoordinateSystem
def test_append_a_material_with_a_representation_and_reuse_an_existing_subcontext(self): def test_append_a_material_with_a_representation_and_reuse_an_existing_subcontext(self):
ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject")
file_context = ifcopenshell.api.run("context.add_context", self.file, context_type="Model") file_context = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
file_subcontext = ifcopenshell.api.run("context.add_context", self.file, parent=file_context, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW") file_subcontext = ifcopenshell.api.run(
"context.add_context",
self.file,
parent=file_context,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
)
library = ifcopenshell.api.run("project.create_file") library = ifcopenshell.api.run("project.create_file")
ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject") ifcopenshell.api.run("root.create_entity", library, ifc_class="IfcProject")
material = ifcopenshell.api.run("material.add_material", library, name="Material") material = ifcopenshell.api.run("material.add_material", library, name="Material")
style = ifcopenshell.api.run("style.add_style", library) style = ifcopenshell.api.run("style.add_style", library)
context = ifcopenshell.api.run("context.add_context", library, context_type="Model") context = ifcopenshell.api.run("context.add_context", library, context_type="Model")
subcontext = ifcopenshell.api.run("context.add_context", library, parent=context, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW") subcontext = ifcopenshell.api.run(
"context.add_context",
library,
parent=context,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
)
ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=subcontext) ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=subcontext)
ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material)
assert len(self.file.by_type("IfcMaterial")) == 1 assert len(self.file.by_type("IfcMaterial")) == 1
@@ -227,6 +263,7 @@ class TestAppendAsset(test.bootstrap.IFC4):
assert len(self.file.by_type("IfcGeometricRepresentationSubContext", include_subtypes=False)) == 1 assert len(self.file.by_type("IfcGeometricRepresentationSubContext", include_subtypes=False)) == 1
subcontext = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems subcontext = self.file.by_type("IfcMaterial")[0].HasRepresentation[0].Representations[0].ContextOfItems
assert subcontext == file_subcontext assert subcontext == file_subcontext
# make sure it's still valid
assert subcontext.ParentContext.WorldCoordinateSystem assert subcontext.ParentContext.WorldCoordinateSystem
def test_append_a_material_with_a_representation_and_reuse_an_existing_context_by_a_new_subcontext(self): def test_append_a_material_with_a_representation_and_reuse_an_existing_context_by_a_new_subcontext(self):
@@ -238,8 +275,14 @@ class TestAppendAsset(test.bootstrap.IFC4):
material = ifcopenshell.api.run("material.add_material", library, name="Material") material = ifcopenshell.api.run("material.add_material", library, name="Material")
style = ifcopenshell.api.run("style.add_style", library) style = ifcopenshell.api.run("style.add_style", library)
context = ifcopenshell.api.run("context.add_context", library, context_type="Model") context = ifcopenshell.api.run("context.add_context", library, context_type="Model")
subcontext = ifcopenshell.api.run("context.add_context", library, context_type="Model", subcontext = ifcopenshell.api.run(
context_identifier="Body", target_view="MODEL_VIEW", parent=context) "context.add_context",
library,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=context,
)
ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=subcontext) ifcopenshell.api.run("style.assign_material_style", library, material=material, style=style, context=subcontext)
ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material) ifcopenshell.api.run("project.append_asset", self.file, library=library, element=material)
assert len(self.file.by_type("IfcMaterial")) == 1 assert len(self.file.by_type("IfcMaterial")) == 1
+3 -2
View File
@@ -25,9 +25,10 @@ import typing
import inspect import inspect
import collections import collections
import importlib import importlib
from typing import Union
def execute(args): def execute(args: dict) -> Union[ifcopenshell.file, str]:
"""Execute a patch recipe """Execute a patch recipe
The details of how the patch recipe is executed depends on the definition of The details of how the patch recipe is executed depends on the definition of
@@ -80,7 +81,7 @@ def execute(args):
return output return output
def write(output, filepath): def write(output: Union[ifcopenshell.file, str], filepath: str) -> None:
"""Write the output of an IFC patch to a file """Write the output of an IFC patch to a file
Typically a patch output would be a patched IFC model file object, or as a Typically a patch output would be a patched IFC model file object, or as a
+2 -1
View File
@@ -18,7 +18,6 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
from toposort import toposort_flatten as toposort
class Patcher: class Patcher:
@@ -53,6 +52,8 @@ class Patcher:
self.optimized_file = ifcopenshell.file(schema=self.file.schema) self.optimized_file = ifcopenshell.file(schema=self.file.schema)
def patch(self): def patch(self):
from toposort import toposort_flatten as toposort
def generate_instances_and_references(): def generate_instances_and_references():
""" """
Generator which yields an entity id and Generator which yields an entity id and
+3
View File
@@ -493,6 +493,9 @@ namespace {
for (auto& prop : *props) { for (auto& prop : *props) {
if (prop->declaration().is("IfcPropertySingleValue")) { if (prop->declaration().is("IfcPropertySingleValue")) {
std::string name = *((IfcUtil::IfcBaseEntity*) prop)->get("Name"); std::string name = *((IfcUtil::IfcBaseEntity*) prop)->get("Name");
if (((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue")->isNull()) {
continue;
}
IfcUtil::IfcBaseClass* v = *((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue"); IfcUtil::IfcBaseClass* v = *((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue");
auto value = v->data().getArgument(0); auto value = v->data().getArgument(0);
if (value->type() == IfcUtil::Argument_STRING) { if (value->type() == IfcUtil::Argument_STRING) {