New hierarchical classification navigator, reading class data from IFCs. Thanks to Lucyon Oak (Teck-Freak)

This commit is contained in:
Dion Moult
2020-04-23 16:05:06 +10:00
parent 9217af315b
commit 19130b109e
10 changed files with 21324 additions and 85 deletions
@@ -72,9 +72,12 @@ if bpy is not None:
operator.EditAggregate,
operator.SaveAggregate,
operator.ExplodeAggregate,
operator.LoadClassification,
operator.AddClassification,
operator.RemoveClassification,
operator.AssignClassification,
operator.UnassignClassification,
operator.RemoveClassification,
operator.RemoveClassificationReference,
operator.FetchLibraryInformation,
operator.FetchExternalMaterial,
operator.FetchObjectPassport,
@@ -95,6 +98,11 @@ if bpy is not None:
operator.SetViewPreset1,
operator.OpenUpstream,
operator.BIM_OT_CopyAttributesToSelection,
operator.BIM_OT_ChangeClassificationLevel,
prop.StrProperty,
prop.Classification,
prop.ClassificationReference,
prop.ClassificationView,
prop.BcfTopic,
prop.BcfTopicLabel,
prop.BcfTopicFile,
@@ -112,7 +120,6 @@ if bpy is not None:
prop.BoundaryCondition,
prop.PsetQto,
prop.Document,
prop.Classification,
prop.GlobalId,
prop.BIMObjectProperties,
prop.BIMMaterialProperties,
@@ -121,6 +128,7 @@ if bpy is not None:
prop.BIMCameraProperties,
ui.BIM_PT_documentation,
ui.BIM_PT_bim,
ui.BIM_PT_classifications,
ui.BIM_PT_search,
ui.BIM_PT_bcf,
ui.BIM_PT_owner,
@@ -133,8 +141,10 @@ if bpy is not None:
ui.BIM_PT_material,
ui.BIM_PT_mesh,
ui.BIM_PT_object,
ui.BIM_PT_classification_references,
ui.BIM_PT_camera,
ui.BIM_UL_topics,
ui.BIM_UL_classifications,
ui.BIM_ADDON_preferences,
)
@@ -1,3 +0,0 @@
Source,Edition,EditionDate,Name,Description,Location,ReferenceTokens
RIBA Enterprises Ltd,2015,Aug-19,Uniclass 2015,Uniclass2015 is a unified classification for the UK industry covering all construction sectors.,https://www.thenbs.com/our-tools/uniclass-2015,"[""_""]"
OmniClass,2.1,May-12,OmniClass,The OmniClass Construction Classification System is a classification system for the construction industry.,https://www.csiresources.org/standards/omniclass,"[""-"","" ""]"
1 Source Edition EditionDate Name Description Location ReferenceTokens
2 RIBA Enterprises Ltd 2015 Aug-19 Uniclass 2015 Uniclass2015 is a unified classification for the UK industry covering all construction sectors. https://www.thenbs.com/our-tools/uniclass-2015 ["_"]
3 OmniClass 2.1 May-12 OmniClass The OmniClass Construction Classification System is a classification system for the construction industry. https://www.csiresources.org/standards/omniclass ["-"," "]
@@ -1,2 +0,0 @@
Identification,Name,ReferencedSource
Pr_20_85_14_16,Concrete solid slabs,0
1 Identification Name ReferencedSource
2 Pr_20_85_14_16 Concrete solid slabs 0
@@ -463,7 +463,7 @@ class IfcParser():
for classification in obj.BIMObjectProperties.classifications:
self.rel_associates_classification_object.setdefault(
classification.identification, []).append(product)
classification.name, []).append(product)
for key in obj.keys():
if key[0:9] == 'Objective':
@@ -634,32 +634,39 @@ class IfcParser():
return results
def get_classifications(self):
results = []
class_path = self.data_dir + 'class/'
with open(class_path + 'classifications.csv', 'r') as f:
data = list(csv.reader(f))
keys = data.pop(0)
for row in data:
row[-1] = json.loads(row[-1])
results.append({
'ifc': None,
'raw': row,
'attributes': dict(zip(keys, row))
})
classifications = bpy.context.scene.BIMProperties.classifications
results = {}
for classification in classifications:
results[classification.name] = {
'ifc': None,
'raw': classification,
'attributes': {
'Name': classification.name,
'Source': classification.source,
'Edition': classification.edition,
'EditionDate': classification.edition_date,
'Description': classification.description,
'Location': classification.location,
'ReferenceTokens': json.loads(
'[{}]'.format(classification.reference_tokens[1:-1].replace('\'', '"').strip(',')))
}
}
return results
def get_classification_references(self):
results = {}
class_path = self.data_dir + 'class/'
with open(class_path + 'references.csv', 'r') as f:
data = list(csv.reader(f))
keys = data.pop(0)
for row in data:
results[row[0]] = {
for product in self.selected_products:
for reference in product['raw'].BIMObjectProperties.classifications:
results[reference.name] = {
'ifc': None,
'raw': row,
'referenced_source': int(row.pop()),
'attributes': dict(zip(keys, row))
'raw': reference,
'referenced_source': reference.referenced_source,
'attributes': {
'Location': reference.location,
'Identification': reference.name,
'Name': reference.human_name,
'Description': reference.description
}
}
return results
@@ -1323,7 +1330,7 @@ class IfcExporter():
[self.ifc_parser.project['ifc']], document['ifc'])
def create_classifications(self):
for classification in self.ifc_parser.classifications:
for classification in self.ifc_parser.classifications.values():
classification['ifc'] = self.file.create_entity(
'IfcClassification',
**classification['attributes']
@@ -1198,14 +1198,64 @@ class ExplodeAggregate(bpy.types.Operator):
return {'FINISHED'}
class LoadClassification(bpy.types.Operator):
bl_idname = 'bim.load_classification'
bl_label = 'Load Classification'
def execute(self, context):
from . import prop
prop.ClassificationView.raw_data = schema.ifc.load_classification(
context.scene.BIMProperties.classification)
context.scene.BIMProperties.classification_references.root = ''
return {'FINISHED'}
class AddClassification(bpy.types.Operator):
bl_idname = 'bim.add_classification'
bl_label = 'Add Classification'
def execute(self, context):
if context.scene.BIMProperties.classification in schema.ifc.classifications:
data = schema.ifc.classifications[context.scene.BIMProperties.classification]
classification = context.scene.BIMProperties.classifications.add()
classification.source = data.Source
classification.edition = data.Edition
classification.edition_date = data.EditionDate
classification.name = data.Name
classification.description = data.Description
classification.location = data.Location
classification.reference_tokens = str(data.ReferenceTokens)
return {'FINISHED'}
class RemoveClassification(bpy.types.Operator):
bl_idname = 'bim.remove_classification'
bl_label = 'Remove Classification'
classification_index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.BIMProperties.classifications.remove(self.classification_index)
return {'FINISHED'}
class AssignClassification(bpy.types.Operator):
bl_idname = 'bim.assign_classification'
bl_label = 'Assign Classification'
def execute(self, context):
referenced_source = schema.ifc.classifications[bpy.context.scene.BIMProperties.classification].Name
for obj in bpy.context.selected_objects:
classification = obj.BIMObjectProperties.classifications.add()
classification.identification = bpy.context.scene.BIMProperties.reference
refs = bpy.context.scene.BIMProperties.classification_references
data = refs.root['children'][refs.children[refs.active_index].name]
if data['identification']:
classification.name = data['identification']
if data['name']:
classification.human_name = data['name']
for key in ['location', 'description']:
if data[key]:
setattr(classification, key, data[key])
classification.referenced_source = referenced_source
return {'FINISHED'}
@@ -1214,18 +1264,20 @@ class UnassignClassification(bpy.types.Operator):
bl_label = 'Unassign Classification'
def execute(self, context):
refs = bpy.context.scene.BIMProperties.classification_references
key = refs.children[refs.active_index].name
for obj in bpy.context.selected_objects:
index = object.BIMObjectProperties.classifications.find(bpy.context.scene.BIMProperties.classification)
index = obj.BIMObjectProperties.classifications.find(key)
if index != -1:
object.BIMObjectProperties.classifications.remove(index)
obj.BIMObjectProperties.classifications.remove(index)
obj.BIMObjectProperties.classification = ''
return {'FINISHED'}
class RemoveClassification(bpy.types.Operator):
bl_idname = 'bim.remove_classification'
bl_label = 'Remove Classification'
class RemoveClassificationReference(bpy.types.Operator):
bl_idname = 'bim.remove_classification_reference'
bl_label = 'Remove Classification Reference'
classification_index: bpy.props.IntProperty()
def execute(self, context):
@@ -1662,3 +1714,24 @@ class BIM_OT_CopyAttributesToSelection(bpy.types.Operator):
try:
setattr(new_prop_base, p, getattr(prop, p))
except: pass
class BIM_OT_ChangeClassificationLevel(bpy.types.Operator):
bl_idname = "bim.change_classification_level"
bl_label = "Change Classification Level"
# string representing the id-data (e.g. the scene).
path_sid: bpy.props.StringProperty()
# path from the id-data to the classification view object
path_lst: bpy.props.StringProperty()
# name of child entity to enter (empty = go up one level)
path_itm: bpy.props.StringProperty()
def invoke(self, context, event):
id_data = eval(self.path_sid)
lst = id_data.path_resolve(self.path_lst)
if self.path_itm:
lst.root = self.path_itm
else:
lst.root = ''
return {'FINISHED'}
+84 -28
View File
@@ -32,7 +32,6 @@ scenarios_enum = []
psetnames_enum = []
psetfiles_enum = []
classification_enum = []
reference_enum = []
attributes_enum = []
overridepsetnames_enum = []
qtonames_enum = []
@@ -236,31 +235,15 @@ def getClassifications(self, context):
global classification_enum
if len(classification_enum) < 1:
classification_enum.clear()
with open(os.path.join(self.data_dir, 'class', 'classifications.csv'), 'r') as f:
data = list(csv.reader(f))
keys = data.pop(0)
index = keys.index('Name')
classification_enum.extend([(str(i), d[index], '') for i, d in enumerate(data)])
files = os.listdir(os.path.join(self.schema_dir, 'classifications'))
classification_enum.extend([(f.replace('.ifc', ''), f.replace('.ifc', ''), '') for f in files])
return classification_enum
def refreshReferences(self, context):
global reference_enum
reference_enum.clear()
getReferences(self, context)
def getReferences(self, context):
global reference_enum
if len(reference_enum) < 1:
if not self.classification.strip():
return reference_enum
with open(os.path.join(self.data_dir, 'class', 'references.csv'), 'r') as f:
data = list(csv.reader(f))
keys = data.pop(0)
reference_enum.extend([(d[0], '{} - {}'.format(d[0], d[1]), '') for d in data if
d[2] == self.classification.strip()])
return reference_enum
context.scene.BIMProperties.classification_references.root = None
ClassificationView.raw_data = schema.ifc.load_classification(context.scene.BIMProperties.classification)
context.scene.BIMProperties.classification_references.root = ''
def getOverridePsetNames(self, context):
@@ -390,6 +373,10 @@ def getSheets(self, context):
return sheets_enum
class StrProperty(PropertyGroup):
pass
class Subcontext(PropertyGroup):
name: StringProperty(name='Name')
target_view: StringProperty(name='Target View')
@@ -587,6 +574,78 @@ def getBcfViewpoints(self, context):
return bcfviewpoints_enum
class Classification(PropertyGroup):
name: StringProperty(name="Name")
source: StringProperty(name="Source")
edition: StringProperty(name="Edition")
edition_date: StringProperty(name="Edition Date")
description: StringProperty(name="Description")
location: StringProperty(name="Location")
reference_tokens: StringProperty(name="Reference Tokens")
class ClassificationReference(PropertyGroup):
name: StringProperty(name="Identification")
location: StringProperty(name="Location")
human_name: StringProperty(name="Name")
referenced_source: StringProperty(name="Source")
description: StringProperty(name="Description")
sort: StringProperty(name="Sort")
class ClassificationView(PropertyGroup):
crumbs: None
children: None
active_index: bpy.props.IntProperty()
raw_data = {}
@property
def root(self):
data = self.raw_data
for crumb in self.crumbs:
data = data['children'].get(crumb.name)
if not data:
raise TypeError('Cannot resolve crumb path')
return data
@root.setter
def root(self, rt):
if rt == None:
self.crumbs.clear()
self.children.clear()
elif rt == '':
if self.crumbs:
self.crumbs.remove(len(self.crumbs)-1)
self.children.clear()
for child in self.root['children'].keys():
self.children.add().name = child
else:
data = self.root
if rt in data['children'].keys():
self.crumbs.add().name = rt
self.children.clear()
for child in data['children'][rt]['children'].keys():
self.children.add().name = child
def draw_stub(self, context, layout):
if not self.children:
op = layout.operator('bim.change_classification_level', text="@Toplevel")
else:
op = layout.operator('bim.change_classification_level', text=self.root['name'])
op.path_sid = "%r"%self.id_data
op.path_lst = self.path_from_id()
op.path_itm = ''
layout.template_list('BIM_UL_classifications',
self.path_from_id(), self, 'children', self, 'active_index')
# Monkey-patched, just to keep registration in one block
ClassificationView.__annotations__['crumbs'] = \
bpy.props.CollectionProperty(type=StrProperty)
ClassificationView.__annotations__['children'] = \
bpy.props.CollectionProperty(type=StrProperty)
class BIMProperties(PropertyGroup):
schema_dir: StringProperty(default=os.path.join(cwd ,'schema') + os.path.sep, name="Schema Directory")
data_dir: StringProperty(default=os.path.join(cwd, 'data') + os.path.sep, name="Data Directory")
@@ -639,7 +698,7 @@ class BIMProperties(PropertyGroup):
aggregate_class: EnumProperty(items=getIfcClasses, name="Aggregate Class")
aggregate_name: StringProperty(name="Aggregate Name")
classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences)
reference: EnumProperty(items=getReferences, name="Reference")
classifications: CollectionProperty(name="Classifications", type=Classification)
has_model_context: BoolProperty(name="Has Model Context", default=True)
has_plan_context: BoolProperty(name="Has Plan Context", default=False)
model_subcontexts: CollectionProperty(name='Model Subcontexts', type=Subcontext)
@@ -647,6 +706,7 @@ class BIMProperties(PropertyGroup):
available_contexts: EnumProperty(items=[('Model', 'Model', ''), ('Plan', 'Plan', '')], name="Available Contexts")
available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts")
available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views")
classification_references: PointerProperty(type=ClassificationView)
class BCFProperties(PropertyGroup):
@@ -720,10 +780,6 @@ class PsetQto(PropertyGroup):
class Document(PropertyGroup):
file: StringProperty(name="File")
class Classification(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
class GlobalId(PropertyGroup):
name: StringProperty(name="Name")
@@ -744,7 +800,7 @@ class BIMObjectProperties(PropertyGroup):
applicable_attributes: EnumProperty(items=getApplicableAttributes, name="Attribute Names")
documents: CollectionProperty(name="Documents", type=Document)
applicable_documents: EnumProperty(items=getApplicableDocuments, name="Available Documents")
classifications: CollectionProperty(name="Classifications", type=Classification)
classifications: CollectionProperty(name="Classifications", type=ClassificationReference)
material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
override_psets: CollectionProperty(name="Override Psets", type=PsetQto)
override_pset_name: EnumProperty(items=getOverridePsetNames, name='Override Pset Name')
@@ -21,14 +21,22 @@ class IfcSchema():
'IfcElementType'
]
self.elements = {}
self.property_files = []
property_paths = Path(self.schema_dir).glob('Pset_*.ifc')
for path in property_paths:
self.property_files.append(ifcopenshell.open(path))
self.classification_files = {}
classification_paths = Path(self.schema_dir).glob('classifications/*.ifc')
for path in classification_paths:
self.classification_files[os.path.basename(path).split('.')[0]] = ifcopenshell.open(path)
self.psets = {}
self.qtos = {}
self.applicable_psets = {}
self.applicable_qtos = {}
self.classifications = {}
self.load()
def load(self):
@@ -53,4 +61,31 @@ class IfcSchema():
entity = prop.ApplicableEntity if prop.ApplicableEntity else 'IfcRoot'
self.applicable_psets.setdefault(entity, []).append(prop.Name)
for filename, classification_file in self.classification_files.items():
classification = classification_file.by_type('IfcClassification')[0]
self.classifications[filename] = classification
def load_classification(self, filename):
classification = self.classifications[filename]
return {
'name': '',
'description': '',
'children': self.get_classification_references(classification)
}
def get_classification_references(self, classification):
references = {}
if not classification.HasReferences:
return references
for reference in classification.HasReferences:
references[reference.Identification] = {
'location': reference.Location,
'identification': reference.Identification,
'name': reference.Name,
'description': reference.Description,
'children': self.get_classification_references(reference)
}
return references
ifc = IfcSchema()
File diff suppressed because it is too large Load Diff
+99 -21
View File
@@ -111,17 +111,6 @@ class BIM_PT_object(Panel):
row = layout.row()
row.prop(props, 'documents')
layout.label(text="Classification:")
for index, classification in enumerate(props.classifications):
row = layout.row(align=True)
row.prop(classification, 'identification', text='')
row.prop(classification, 'name', text='')
row.operator('bim.remove_classification', icon='X', text='').classification_index = index
row = layout.row()
row.prop(props, 'classifications')
row = layout.row()
row.prop(props, 'material_type')
@@ -142,6 +131,87 @@ class BIM_PT_object(Panel):
row.prop(props, 'structural_member_connection')
class BIM_PT_classification_references(Panel):
bl_label = 'Classification References'
bl_idname = 'BIM_PT_classification_references'
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'object'
def draw(self, context):
layout = self.layout
props = context.active_object.BIMObjectProperties
layout.label(text="Classification:")
for index, classification in enumerate(props.classifications):
row = layout.row(align=True)
row.prop(classification, 'name')
row.operator('bim.remove_classification_reference', icon='X', text='').classification_index = index
row = layout.row(align=True)
row.prop(classification, 'human_name')
row = layout.row(align=True)
row.prop(classification, 'location')
row = layout.row(align=True)
row.prop(classification, 'description')
row = layout.row(align=True)
row.prop(classification, 'referenced_source')
row = layout.row()
row.prop(props, 'classifications')
class BIM_PT_classifications(Panel):
bl_label = 'Classifications'
bl_idname = 'BIM_PT_classifications'
bl_options = {'DEFAULT_CLOSED'}
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'scene'
def draw(self, context):
layout = self.layout
props = context.scene.BIMProperties
row = layout.row(align=True)
row.prop(props, "classification", text='')
row.operator("bim.add_classification", text='', icon='ADD')
if context.scene.BIMProperties.classification_references.raw_data:
context.scene.BIMProperties.classification_references.draw_stub(context, layout)
row = layout.row(align=True)
row.operator("bim.assign_classification")
row.operator("bim.unassign_classification")
else:
row = layout.row(align=True)
row.operator('bim.load_classification')
if not props.classifications:
return
layout.label(text="Classifications:")
for index, classification in enumerate(props.classifications):
row = layout.row(align=True)
row.prop(classification, 'name')
row.operator('bim.remove_classification', icon='X', text='').classification_index = index
row = layout.row(align=True)
row.prop(classification, 'source')
row = layout.row(align=True)
row.prop(classification, 'edition')
row = layout.row(align=True)
row.prop(classification, 'edition_date')
row = layout.row(align=True)
row.prop(classification, 'description')
row = layout.row(align=True)
row.prop(classification, 'location')
row = layout.row(align=True)
row.prop(classification, 'reference_tokens')
row = layout.row()
row.prop(props, 'classifications')
class BIM_PT_mesh(Panel):
bl_label = 'IFC Representations'
bl_idname = 'BIM_PT_mesh'
@@ -499,16 +569,6 @@ class BIM_PT_bim(Panel):
row.operator("bim.edit_aggregate")
row.operator("bim.save_aggregate")
layout.label(text="Classifications:")
row = layout.row()
row.prop(bim_properties, "classification")
row = layout.row()
row.prop(bim_properties, "reference")
row = layout.row(align=True)
row.operator("bim.assign_classification")
row.operator("bim.unassign_classification")
class BIM_PT_search(Panel):
bl_label = "BIM Search"
@@ -852,6 +912,24 @@ class BIM_UL_topics(bpy.types.UIList):
layout.label(text="", translate=False)
class BIM_UL_classifications(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
rt = data.root
ch = rt['children']
itemdata = ch[item.name]
if itemdata.get('children', {}):
op = layout.operator("bim.change_classification_level", text="",
emboss=False, icon="DISCLOSURE_TRI_RIGHT")
op.path_sid = "%r"%active_data.id_data # get id-data
op.path_lst = active_data.path_from_id() # path to view
op.path_itm = item.name # name of child. empty = go up
else:
layout.label(text='', icon='BLANK1')
layout.prop(item, "name", text="", emboss=False)
layout.label(text=itemdata['name'])
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
bl_idname = 'blenderbim'