Skip to content

Commit 4e62c27

Browse files
committed
Fix IfcOpenShell#1111 - issue where sometimes class and product dropdown gets out of sync. Also new utility function to query is_a() in the schema.
1 parent c70b41f commit 4e62c27

File tree

2 files changed

+103
-46
lines changed

2 files changed

+103
-46
lines changed

src/ifcblenderexport/blenderbim/bim/operator.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import ifcopenshell.util.selector
1111
import ifcopenshell.util.geolocation
1212
import ifcopenshell.util.pset
13+
import ifcopenshell.util.schema
1314
import tempfile
1415
from . import export_ifc
1516
from . import import_ifc
@@ -220,7 +221,22 @@ class ReassignClass(bpy.types.Operator):
220221

221222
def execute(self, context):
222223
obj = bpy.context.active_object
224+
ifc_class = obj.name.split("/")[0]
225+
ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema)
223226
bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True
227+
ifc_products = [
228+
"IfcElement",
229+
"IfcElementType",
230+
"IfcSpatialElement",
231+
"IfcGroup",
232+
"IfcStructural",
233+
"IfcPositioningElement",
234+
"IfcContext",
235+
"IfcAnnotation",
236+
]
237+
for ifc_product in ifc_products:
238+
if ifcopenshell.util.schema.is_a(ifc_schema.declaration_by_name(ifc_class), ifc_product):
239+
bpy.context.scene.BIMProperties.ifc_product = ifc_product
224240
bpy.context.scene.BIMProperties.ifc_class = obj.name.split("/")[0]
225241
predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType")
226242
if predefined_type:
@@ -1801,6 +1817,7 @@ def invoke(self, context, event):
18011817
context.window_manager.fileselect_add(self)
18021818
return {"RUNNING_MODAL"}
18031819

1820+
18041821
class SelectClashResults(bpy.types.Operator):
18051822
bl_idname = "bim.select_clash_results"
18061823
bl_label = "Select Clash Results"
@@ -1814,6 +1831,7 @@ def invoke(self, context, event):
18141831
context.window_manager.fileselect_add(self)
18151832
return {"RUNNING_MODAL"}
18161833

1834+
18171835
class SelectSmartGroupedClashesPath(bpy.types.Operator):
18181836
bl_idname = "bim.select_smart_grouped_clashes_path"
18191837
bl_label = "Select Smart-Grouped Clashes Path"
@@ -1827,6 +1845,7 @@ def invoke(self, context, event):
18271845
context.window_manager.fileselect_add(self)
18281846
return {"RUNNING_MODAL"}
18291847

1848+
18301849
class ExecuteIfcClash(bpy.types.Operator):
18311850
bl_idname = "bim.execute_ifc_clash"
18321851
bl_label = "Execute IFC Clash"
@@ -1901,6 +1920,7 @@ def execute(self, context):
19011920
obj.select_set(True)
19021921
return {"FINISHED"}
19031922

1923+
19041924
class SmartClashGroup(bpy.types.Operator):
19051925
bl_idname = "bim.smart_clash_group"
19061926
bl_label = "Smart Group Clashes"
@@ -1918,19 +1938,21 @@ def execute(self, context):
19181938

19191939
with open(self.filepath) as f:
19201940
clash_sets = json.load(f)
1921-
1941+
19221942
# execute the smart grouping
19231943
save_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json")
1924-
smart_grouped_clashes = ifc_clasher.smart_group_clashes(clash_sets, bpy.context.scene.BIMProperties.smart_clash_grouping_max_distance)
1944+
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
1945+
clash_sets, bpy.context.scene.BIMProperties.smart_clash_grouping_max_distance
1946+
)
19251947

19261948
# save smart_groups to json
1927-
with open(save_path, 'w') as f:
1949+
with open(save_path, "w") as f:
19281950
f.write(json.dumps(smart_grouped_clashes))
19291951

19301952
clash_set_name = bpy.context.scene.BIMProperties.clash_sets[
19311953
bpy.context.scene.BIMProperties.active_clash_set_index
19321954
].name
1933-
1955+
19341956
# Reset the list of smart_clash_groups for the UI
19351957
bpy.context.scene.BIMProperties.smart_clash_groups.clear()
19361958

@@ -1954,7 +1976,7 @@ def execute(self, context):
19541976
class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
19551977
bl_idname = "bim.load_smart_groups_for_active_clash_set"
19561978
bl_label = "Load Smart Groups for Active Clash Set"
1957-
1979+
19581980
def execute(self, context):
19591981
smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json")
19601982

@@ -1967,7 +1989,7 @@ def execute(self, context):
19671989

19681990
# Reset the list of smart_clash_groups for the UI
19691991
bpy.context.scene.BIMProperties.smart_clash_groups.clear()
1970-
1992+
19711993
for clash_set, smart_groups in smart_grouped_clashes.items():
19721994
# Only select the clashes that correspond to the actively selected IFC Clash Set
19731995
if clash_set != clash_set_name:
@@ -1990,21 +2012,24 @@ class SelectSmartGroup(bpy.types.Operator):
19902012

19912013
def execute(self, context):
19922014
# Select smart group in view
1993-
selected_smart_group = bpy.context.scene.BIMProperties.smart_clash_groups[bpy.context.scene.BIMProperties.active_smart_group_index]
1994-
#print(selected_smart_group.number)
2015+
selected_smart_group = bpy.context.scene.BIMProperties.smart_clash_groups[
2016+
bpy.context.scene.BIMProperties.active_smart_group_index
2017+
]
2018+
# print(selected_smart_group.number)
19952019

19962020
for obj in bpy.context.visible_objects:
19972021
global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
19982022
if global_id:
19992023
for id in selected_smart_group.global_ids:
2000-
#print("Id: ", id)
2001-
#print("Global id: ", global_id.string_value)
2024+
# print("Id: ", id)
2025+
# print("Global id: ", global_id.string_value)
20022026
if global_id.string_value in id.name:
2003-
#print("object match: ", global_id)
2027+
# print("object match: ", global_id)
20042028
obj.select_set(True)
20052029

20062030
return {"FINISHED"}
2007-
2031+
2032+
20082033
class SelectBcfFile(bpy.types.Operator):
20092034
bl_idname = "bim.select_bcf_file"
20102035
bl_label = "Select BCF File"
@@ -2555,7 +2580,8 @@ def create_raster(self, camera, drawing_style):
25552580

25562581
def does_obj_have_target_view_representation(self, obj, camera):
25572582
return camera.data.BIMCameraProperties.target_view in [
2558-
c.target_view for c in obj.BIMObjectProperties.representation_contexts
2583+
c.target_view
2584+
for c in obj.BIMObjectProperties.representation_contexts
25592585
if c.context == "Plan" and c.name == "Annotation"
25602586
]
25612587

@@ -2713,7 +2739,11 @@ def execute(self, context):
27132739
self.obj.data.name = "Model/Body/MODEL_VIEW/" + self.obj.data.name
27142740
has_default_context = False
27152741
for subcontext in self.obj.BIMObjectProperties.representation_contexts:
2716-
if subcontext.context == "Model" and subcontext.name == "Body" and subcontext.target_view == "MODEL_VIEW":
2742+
if (
2743+
subcontext.context == "Model"
2744+
and subcontext.name == "Body"
2745+
and subcontext.target_view == "MODEL_VIEW"
2746+
):
27172747
has_default_context = True
27182748
break
27192749
if not has_default_context:

src/ifcopenshell-python/ifcopenshell/util/schema.py

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
cwd = os.path.dirname(os.path.realpath(__file__))
1010

11+
12+
def is_a(entity, ifc_class):
13+
ifc_class = ifc_class.lower()
14+
if entity.name_lc() == ifc_class:
15+
return True
16+
if entity.supertype():
17+
return is_a(entity.supertype(), ifc_class)
18+
return False
19+
20+
1121
class Migrator:
1222
def __init__(self):
1323
self.migrated_ids = {}
@@ -70,13 +80,13 @@ def migrate(self, element, new_file):
7080
return new_file.by_id(self.migrated_ids[element.id()])
7181
except:
7282
pass
73-
#print("Migrating", element)
83+
# print("Migrating", element)
7484
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(new_file.schema)
7585
new_element = self.migrate_class(element, new_file)
76-
#print("Migrated class from {} to {}".format(element, new_element))
86+
# print("Migrated class from {} to {}".format(element, new_element))
7787
new_element_schema = schema.declaration_by_name(new_element.is_a())
7888
if not hasattr(new_element_schema, "all_attributes"):
79-
return element # The element has no attributes, so migration is done
89+
return element # The element has no attributes, so migration is done
8090
new_element = self.migrate_attributes(element, new_file, new_element, new_element_schema)
8191
self.migrated_ids[element.id()] = new_element.id()
8292
return new_element
@@ -102,39 +112,47 @@ def migrate_attributes(self, element, new_file, new_element, new_element_schema)
102112
return new_element
103113

104114
def migrate_attribute(self, attribute, element, new_file, new_element, new_element_schema):
105-
#print("Migrating attribute", element, new_element, attribute.name())
115+
# print("Migrating attribute", element, new_element, attribute.name())
106116
if hasattr(element, attribute.name()):
107117
value = getattr(element, attribute.name())
108-
#print("Attribute names matched", value)
118+
# print("Attribute names matched", value)
109119
elif new_file.schema == "IFC2X3":
110120
# IFC4 to IFC2X3: We know the IFC2X3 attribute name, but not its IFC4 equivalent
111-
#print("Searching for an equivalent", new_element, attribute.name())
121+
# print("Searching for an equivalent", new_element, attribute.name())
112122
try:
113123
equivalent_map = self.attribute_4_to_2x3[new_element.is_a()]
114124
equivalent = list(equivalent_map.keys())[list(equivalent_map.values()).index(attribute.name())]
115125
if hasattr(element, equivalent):
116-
#print("Equivalent found", equivalent)
126+
# print("Equivalent found", equivalent)
117127
value = getattr(element, equivalent)
118128
else:
119129
return
120130
except:
121-
print("Unable to find equivalent attribute of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
122-
return # We tried our best
131+
print(
132+
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
133+
attribute.name(), element, new_element
134+
)
135+
)
136+
return # We tried our best
123137
elif new_file.schema == "IFC4":
124138
# IFC2X3 to IFC4: We know the IFC4 attribute name, but not its IFC2X3 equivalent
125-
#print("Searching for an equivalent", element, new_element, attribute.name())
139+
# print("Searching for an equivalent", element, new_element, attribute.name())
126140
try:
127141
equivalent = self.attribute_4_to_2x3[new_element.is_a()][attribute.name()]
128-
#print("Searching for equivalent", equivalent)
142+
# print("Searching for equivalent", equivalent)
129143
if hasattr(element, equivalent):
130144
value = getattr(element, equivalent)
131145
else:
132146
return
133147
except:
134-
print("Unable to find equivalent attribute of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
135-
return # We tried our best
148+
print(
149+
"Unable to find equivalent attribute of {} to migrate from {} to {}".format(
150+
attribute.name(), element, new_element
151+
)
152+
)
153+
return # We tried our best
136154

137-
#print("Continuing migration of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
155+
# print("Continuing migration of {} to migrate from {} to {}".format(attribute.name(), element, new_element))
138156
if value is None and not attribute.optional():
139157
value = self.generate_default_value(attribute, new_file)
140158
if value is None:
@@ -155,24 +173,33 @@ def generate_default_value(self, attribute, new_file):
155173
elif self.default_entities[attribute.name()]:
156174
return self.default_entities[attribute.name()]
157175
elif attribute.name() == "OwnerHistory":
158-
self.default_entities[attribute.name()] = new_file.create_entity("IfcOwnerHistory", **{
159-
"OwningUser": new_file.create_entity("IfcPersonAndOrganization", **{
160-
"ThePerson": new_file.create_entity("IfcPerson"),
161-
"TheOrganization": new_file.create_entity("IfcOrganization", **{
162-
"Name": "IfcOpenShell Migrator"
163-
})
164-
}),
165-
"OwningApplication": new_file.create_entity("IfcApplication", **{
166-
"ApplicationDeveloper": new_file.create_entity("IfcOrganization", **{
167-
"Name": "IfcOpenShell Migrator"
168-
}),
169-
"Version": "Works for me",
170-
"ApplicationFullName": "IfcOpenShell Migrator",
171-
"ApplicationIdentifier": "IfcOpenShell Migrator",
172-
}),
173-
"ChangeAction": "NOCHANGE",
174-
"CreationDate": int(time.time())
175-
})
176+
self.default_entities[attribute.name()] = new_file.create_entity(
177+
"IfcOwnerHistory",
178+
**{
179+
"OwningUser": new_file.create_entity(
180+
"IfcPersonAndOrganization",
181+
**{
182+
"ThePerson": new_file.create_entity("IfcPerson"),
183+
"TheOrganization": new_file.create_entity(
184+
"IfcOrganization", **{"Name": "IfcOpenShell Migrator"}
185+
),
186+
}
187+
),
188+
"OwningApplication": new_file.create_entity(
189+
"IfcApplication",
190+
**{
191+
"ApplicationDeveloper": new_file.create_entity(
192+
"IfcOrganization", **{"Name": "IfcOpenShell Migrator"}
193+
),
194+
"Version": "Works for me",
195+
"ApplicationFullName": "IfcOpenShell Migrator",
196+
"ApplicationIdentifier": "IfcOpenShell Migrator",
197+
}
198+
),
199+
"ChangeAction": "NOCHANGE",
200+
"CreationDate": int(time.time()),
201+
}
202+
)
176203
return self.default_entities[attribute.name()]
177204

178205

0 commit comments

Comments
 (0)