mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 14:02:27 +00:00
Support appending IfcGroup assets via project.append_asset (#8162)
append_asset could not import an IfcGroup from a project library: IfcGroup was absent from APPENDABLE_ASSET, so execute() fell through every branch and returned None, appending nothing. Add IfcGroup to APPENDABLE_ASSET and an append_group() routine that mirrors append_product(): it whitelists the group's IsGroupedBy inverse so the members reached through IfcRelAssignsToGroup are copied via the regular element-append path (bringing their materials, properties and representations), and the relationship is recreated in the destination linking the copied group to the copied members. Idempotency comes from the existing GUID-based reuse, so re-appending the same group does not duplicate it. Verified headless: appending a group of three walls yields group=1, walls=3, IfcRelAssignsToGroup=1 with RelatingGroup and RelatedObjects correctly wired; re-appending is a no-op; appending a plain IfcProduct still works unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ APPENDABLE_ASSET = Literal[
|
|||||||
"IfcCostSchedule",
|
"IfcCostSchedule",
|
||||||
"IfcProfileDef",
|
"IfcProfileDef",
|
||||||
"IfcPresentationStyle",
|
"IfcPresentationStyle",
|
||||||
|
"IfcGroup",
|
||||||
]
|
]
|
||||||
APPENDABLE_ASSET_TYPES: tuple[APPENDABLE_ASSET, ...] = get_args(APPENDABLE_ASSET)
|
APPENDABLE_ASSET_TYPES: tuple[APPENDABLE_ASSET, ...] = get_args(APPENDABLE_ASSET)
|
||||||
MATERIAL_SETS = ("IfcMaterialLayerSet", "IfcMaterialConstituentSet", "IfcMaterialProfileSet")
|
MATERIAL_SETS = ("IfcMaterialLayerSet", "IfcMaterialConstituentSet", "IfcMaterialProfileSet")
|
||||||
@@ -68,8 +69,8 @@ def append_asset(
|
|||||||
|
|
||||||
:param library: The file object containing the asset.
|
:param library: The file object containing the asset.
|
||||||
:param element: An element in the library file of the asset. It may be
|
:param element: An element in the library file of the asset. It may be
|
||||||
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule, or
|
an IfcTypeProduct, IfcProduct, IfcMaterial, IfcCostSchedule,
|
||||||
IfcProfileDef.
|
IfcProfileDef, or IfcGroup.
|
||||||
:param reuse_identities: Optional dictionary of mapped entities' identities to the
|
:param reuse_identities: Optional dictionary of mapped entities' identities to the
|
||||||
already created elements. It will be used to avoid creating
|
already created elements. It will be used to avoid creating
|
||||||
duplicated inverse elements during multiple `project.append_asset` calls. If you want
|
duplicated inverse elements during multiple `project.append_asset` calls. If you want
|
||||||
@@ -236,6 +237,9 @@ class Usecase:
|
|||||||
elif self.settings["element"].is_a("IfcPresentationStyle"):
|
elif self.settings["element"].is_a("IfcPresentationStyle"):
|
||||||
self.target_class = "IfcPresentationStyle"
|
self.target_class = "IfcPresentationStyle"
|
||||||
return self.append_presentation_style()
|
return self.append_presentation_style()
|
||||||
|
elif self.settings["element"].is_a("IfcGroup"):
|
||||||
|
self.target_class = "IfcGroup"
|
||||||
|
return self.append_group()
|
||||||
|
|
||||||
def by_guid(self, guid: str) -> Union[ifcopenshell.entity_instance, None]:
|
def by_guid(self, guid: str) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
try:
|
try:
|
||||||
@@ -393,6 +397,33 @@ class Usecase:
|
|||||||
self.reuse_existing_contexts()
|
self.reuse_existing_contexts()
|
||||||
return element
|
return element
|
||||||
|
|
||||||
|
def append_group(self):
|
||||||
|
# A group (IfcGroup and subtypes like IfcSystem, IfcZone) is appended
|
||||||
|
# together with its members. Members are reached through the group's
|
||||||
|
# ``IsGroupedBy`` inverse (the IfcRelAssignsToGroup relationship), whose
|
||||||
|
# ``RelatedObjects`` are copied via the regular element-append path, so
|
||||||
|
# they bring their own materials, properties, and representations. The
|
||||||
|
# relationship itself is recreated in the destination linking the copied
|
||||||
|
# group to the copied members.
|
||||||
|
self.whitelisted_inverse_attributes = {
|
||||||
|
"IfcObjectDefinition": ["HasAssociations"],
|
||||||
|
"IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"],
|
||||||
|
"IfcGroup": ["IsGroupedBy"],
|
||||||
|
"IfcElement": ["HasOpenings"],
|
||||||
|
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
|
||||||
|
"IfcRepresentationItem": [
|
||||||
|
"StyledByItem",
|
||||||
|
"LayerAssignments" if self.file.schema == "IFC2X3" else "LayerAssignment",
|
||||||
|
],
|
||||||
|
"IfcRepresentation": ["LayerAssignments"],
|
||||||
|
"IfcProductDefinitionShape": ["HasShapeAspects"],
|
||||||
|
"IfcRepresentationMap": ["HasShapeAspects"],
|
||||||
|
}
|
||||||
|
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||||
|
element = self.add_element(self.settings["element"])
|
||||||
|
self.reuse_existing_contexts()
|
||||||
|
return element
|
||||||
|
|
||||||
def append_product(self):
|
def append_product(self):
|
||||||
self.whitelisted_inverse_attributes = {
|
self.whitelisted_inverse_attributes = {
|
||||||
"IfcObjectDefinition": ["HasAssociations"],
|
"IfcObjectDefinition": ["HasAssociations"],
|
||||||
|
|||||||
Reference in New Issue
Block a user