mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-28 03:05:33 +00:00
WIP psets now use partial editing with new features! See full message. See #1222.
* Psets are now sensitive to data type * Psets now support null vs empty * Psets now respect shared STEP IDs
This commit is contained in:
@@ -14,6 +14,7 @@ if bpy is not None:
|
|||||||
import blenderbim.bim.module.geometry as module_geometry
|
import blenderbim.bim.module.geometry as module_geometry
|
||||||
import blenderbim.bim.module.model as module_model
|
import blenderbim.bim.module.model as module_model
|
||||||
import blenderbim.bim.module.project as module_project
|
import blenderbim.bim.module.project as module_project
|
||||||
|
import blenderbim.bim.module.pset as module_pset
|
||||||
import blenderbim.bim.module.spatial as module_spatial
|
import blenderbim.bim.module.spatial as module_spatial
|
||||||
import blenderbim.bim.module.style as module_style
|
import blenderbim.bim.module.style as module_style
|
||||||
import blenderbim.bim.module.type as module_type
|
import blenderbim.bim.module.type as module_type
|
||||||
@@ -52,8 +53,6 @@ if bpy is not None:
|
|||||||
operator.SelectSweptSolidInnerCurves,
|
operator.SelectSweptSolidInnerCurves,
|
||||||
operator.AssignSweptSolidExtrusion,
|
operator.AssignSweptSolidExtrusion,
|
||||||
operator.SelectSweptSolidExtrusion,
|
operator.SelectSweptSolidExtrusion,
|
||||||
operator.AddPset,
|
|
||||||
operator.RemovePset,
|
|
||||||
operator.AddQto,
|
operator.AddQto,
|
||||||
operator.RemoveQto,
|
operator.RemoveQto,
|
||||||
operator.AddMaterialPset,
|
operator.AddMaterialPset,
|
||||||
@@ -286,7 +285,6 @@ if bpy is not None:
|
|||||||
ui.BIM_PT_material,
|
ui.BIM_PT_material,
|
||||||
ui.BIM_PT_presentation_layer_data,
|
ui.BIM_PT_presentation_layer_data,
|
||||||
ui.BIM_PT_object_material,
|
ui.BIM_PT_object_material,
|
||||||
ui.BIM_PT_object_psets,
|
|
||||||
ui.BIM_PT_object_qto,
|
ui.BIM_PT_object_qto,
|
||||||
ui.BIM_PT_classification_references,
|
ui.BIM_PT_classification_references,
|
||||||
ui.BIM_PT_documents,
|
ui.BIM_PT_documents,
|
||||||
@@ -320,6 +318,7 @@ if bpy is not None:
|
|||||||
classes.extend(module_geometry.classes)
|
classes.extend(module_geometry.classes)
|
||||||
classes.extend(module_model.classes)
|
classes.extend(module_model.classes)
|
||||||
classes.extend(module_project.classes)
|
classes.extend(module_project.classes)
|
||||||
|
classes.extend(module_pset.classes)
|
||||||
classes.extend(module_spatial.classes)
|
classes.extend(module_spatial.classes)
|
||||||
classes.extend(module_style.classes)
|
classes.extend(module_style.classes)
|
||||||
classes.extend(module_type.classes)
|
classes.extend(module_type.classes)
|
||||||
@@ -364,6 +363,7 @@ if bpy is not None:
|
|||||||
module_geometry.register()
|
module_geometry.register()
|
||||||
module_model.register()
|
module_model.register()
|
||||||
module_project.register()
|
module_project.register()
|
||||||
|
module_pset.register()
|
||||||
module_spatial.register()
|
module_spatial.register()
|
||||||
module_style.register()
|
module_style.register()
|
||||||
module_type.register()
|
module_type.register()
|
||||||
@@ -393,6 +393,7 @@ if bpy is not None:
|
|||||||
module_type.unregister()
|
module_type.unregister()
|
||||||
module_style.unregister()
|
module_style.unregister()
|
||||||
module_spatial.unregister()
|
module_spatial.unregister()
|
||||||
|
module_pset.unregister()
|
||||||
module_project.unregister()
|
module_project.unregister()
|
||||||
module_model.unregister()
|
module_model.unregister()
|
||||||
module_geometry.unregister()
|
module_geometry.unregister()
|
||||||
|
|||||||
@@ -849,7 +849,6 @@ class IfcImporter:
|
|||||||
self.material_creator.create(element, obj, mesh)
|
self.material_creator.create(element, obj, mesh)
|
||||||
self.add_element_classifications(element, obj)
|
self.add_element_classifications(element, obj)
|
||||||
self.add_element_document_relations(element, obj)
|
self.add_element_document_relations(element, obj)
|
||||||
self.add_type_product_psets(element, obj)
|
|
||||||
self.type_collection.objects.link(obj)
|
self.type_collection.objects.link(obj)
|
||||||
self.type_products[element.GlobalId] = obj
|
self.type_products[element.GlobalId] = obj
|
||||||
|
|
||||||
@@ -1364,18 +1363,9 @@ class IfcImporter:
|
|||||||
for definition in element.IsDefinedBy:
|
for definition in element.IsDefinedBy:
|
||||||
if not definition.is_a("IfcRelDefinesByProperties"):
|
if not definition.is_a("IfcRelDefinesByProperties"):
|
||||||
continue
|
continue
|
||||||
if definition.RelatingPropertyDefinition.is_a("IfcPropertySet"):
|
if definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
|
||||||
self.add_pset(definition.RelatingPropertyDefinition, obj.BIMObjectProperties)
|
|
||||||
elif definition.RelatingPropertyDefinition.is_a("IfcElementQuantity"):
|
|
||||||
self.add_qto(definition.RelatingPropertyDefinition, obj)
|
self.add_qto(definition.RelatingPropertyDefinition, obj)
|
||||||
|
|
||||||
def add_type_product_psets(self, element, obj):
|
|
||||||
if not hasattr(element, "HasPropertySets") or not element.HasPropertySets:
|
|
||||||
return
|
|
||||||
for definition in element.HasPropertySets:
|
|
||||||
if definition.is_a("IfcPropertySet"):
|
|
||||||
self.add_pset(definition, obj.BIMObjectProperties)
|
|
||||||
|
|
||||||
def add_pset(self, pset, props):
|
def add_pset(self, pset, props):
|
||||||
new_pset = props.psets.add()
|
new_pset = props.psets.add()
|
||||||
new_pset.name = pset.Name
|
new_pset.name = pset.Name
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Data:
|
|||||||
return
|
return
|
||||||
product = file.by_id(product_id)
|
product = file.by_id(product_id)
|
||||||
cls.products[product_id] = []
|
cls.products[product_id] = []
|
||||||
attributes = product.get_info()
|
|
||||||
declaration = IfcStore.get_schema().declaration_by_name(product.is_a())
|
declaration = IfcStore.get_schema().declaration_by_name(product.is_a())
|
||||||
for attribute in declaration.all_attributes():
|
for attribute in declaration.all_attributes():
|
||||||
data_type = str(attribute.type_of_attribute())
|
data_type = str(attribute.type_of_attribute())
|
||||||
|
|||||||
@@ -76,11 +76,10 @@ class EditAttributes(bpy.types.Operator):
|
|||||||
attributes[attribute["name"]] = blender_attribute.float_value
|
attributes[attribute["name"]] = blender_attribute.float_value
|
||||||
elif attribute["type"] == "enum":
|
elif attribute["type"] == "enum":
|
||||||
attributes[attribute["name"]] = blender_attribute.enum_value
|
attributes[attribute["name"]] = blender_attribute.enum_value
|
||||||
usecase = edit_attributes.Usecase(self.file, {
|
edit_attributes.Usecase(self.file, {
|
||||||
"product": self.file.by_id(props.ifc_definition_id),
|
"product": self.file.by_id(props.ifc_definition_id),
|
||||||
"attributes": attributes
|
"attributes": attributes
|
||||||
})
|
}).execute()
|
||||||
usecase.execute()
|
|
||||||
Data.load(props.ifc_definition_id)
|
Data.load(props.ifc_definition_id)
|
||||||
bpy.ops.bim.disable_editing_attributes()
|
bpy.ops.bim.disable_editing_attributes()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ class BIM_PT_attributes(Panel):
|
|||||||
if props.ifc_definition_id not in Data.products:
|
if props.ifc_definition_id not in Data.products:
|
||||||
Data.load(props.ifc_definition_id)
|
Data.load(props.ifc_definition_id)
|
||||||
|
|
||||||
props = context.active_object.BIMObjectProperties
|
|
||||||
|
|
||||||
|
|
||||||
if props.is_editing_attributes:
|
if props.is_editing_attributes:
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import bpy
|
||||||
|
from . import ui, operator
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
operator.TogglePsetExpansion,
|
||||||
|
operator.EnablePsetEditing,
|
||||||
|
operator.DisablePsetEditing,
|
||||||
|
operator.EditPset,
|
||||||
|
operator.RemovePset,
|
||||||
|
operator.AddPset,
|
||||||
|
ui.BIM_PT_object_psets,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
pass
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import ifcopenshell
|
||||||
|
import blenderbim.bim.schema # TODO: refactor
|
||||||
|
|
||||||
|
|
||||||
|
class Usecase:
|
||||||
|
def __init__(self, file, settings=None):
|
||||||
|
self.file = file
|
||||||
|
self.settings = {"product": None, "Name": None}
|
||||||
|
for key, value in settings.items():
|
||||||
|
self.settings[key] = value
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if self.settings["product"].is_a("IfcObject"):
|
||||||
|
pset = self.file.create_entity(
|
||||||
|
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||||
|
)
|
||||||
|
self.file.create_entity(
|
||||||
|
"IfcRelDefinesByProperties",
|
||||||
|
**{
|
||||||
|
"GlobalId": ifcopenshell.guid.new(),
|
||||||
|
# TODO: owner history
|
||||||
|
"RelatedObjects": [self.settings["product"]],
|
||||||
|
"RelatingPropertyDefinition": pset,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif self.settings["product"].is_a("IfcTypeObject"):
|
||||||
|
pset = self.file.create_entity(
|
||||||
|
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||||
|
)
|
||||||
|
has_property_sets = list(self.settings["product"].HasPropertySets)
|
||||||
|
has_property_sets.append(pset)
|
||||||
|
self.settings["product"].HasPropertySets = has_property_sets
|
||||||
|
elif self.settings["product"].is_a("IfcMaterialDefinition"):
|
||||||
|
pset = self.file.create_entity(
|
||||||
|
"IfcPropertySet",
|
||||||
|
**{
|
||||||
|
"GlobalId": ifcopenshell.guid.new(),
|
||||||
|
"Name": self.settings["Name"],
|
||||||
|
"Material": self.settings["product"],
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import ifcopenshell
|
||||||
|
import blenderbim.bim.schema # TODO: refactor elsewhere
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
|
class Data:
|
||||||
|
products = {}
|
||||||
|
psets = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, product_id):
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
product = file.by_id(product_id)
|
||||||
|
cls.products[product_id] = set()
|
||||||
|
if product.is_a("IfcElementType"):
|
||||||
|
cls.add_type_product_psets(product, product_id)
|
||||||
|
else:
|
||||||
|
cls.add_product_psets(product, product_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_type_product_psets(cls, product, product_id):
|
||||||
|
if not hasattr(product, "HasPropertySets") or not product.HasPropertySets:
|
||||||
|
return # TODO
|
||||||
|
for definition in product.HasPropertySets:
|
||||||
|
if definition.is_a("IfcPropertySet"):
|
||||||
|
cls.add_pset(definition, product_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_product_psets(cls, product, product_id):
|
||||||
|
if not hasattr(product, "IsDefinedBy") or not product.IsDefinedBy:
|
||||||
|
return
|
||||||
|
for definition in product.IsDefinedBy:
|
||||||
|
if not definition.is_a("IfcRelDefinesByProperties"):
|
||||||
|
continue
|
||||||
|
if definition.RelatingPropertyDefinition.is_a("IfcPropertySet"):
|
||||||
|
cls.add_pset(definition.RelatingPropertyDefinition, product_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_pset(cls, pset, product_id):
|
||||||
|
new_pset = {
|
||||||
|
"Name": pset.Name,
|
||||||
|
"is_expanded": True,
|
||||||
|
"Properties": []
|
||||||
|
}
|
||||||
|
cls.products[product_id].add(int(pset.id()))
|
||||||
|
cls.psets[int(pset.id())] = new_pset
|
||||||
|
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(pset.Name)
|
||||||
|
if pset_template:
|
||||||
|
for prop_template in pset_template.HasPropertyTemplates:
|
||||||
|
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||||
|
continue # Other types not yet supported
|
||||||
|
enum_items = []
|
||||||
|
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||||
|
data_type = str(IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType))
|
||||||
|
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||||
|
data_type = "enum"
|
||||||
|
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||||
|
else:
|
||||||
|
continue # Other types not yet supported
|
||||||
|
if "<string>" in data_type:
|
||||||
|
data_type = "string"
|
||||||
|
elif "<real>" in data_type:
|
||||||
|
data_type = "float"
|
||||||
|
elif "<integer>" in data_type:
|
||||||
|
data_type = "integer"
|
||||||
|
elif "<boolean>" in data_type:
|
||||||
|
data_type = "boolean"
|
||||||
|
elif "<enumeration" in data_type:
|
||||||
|
data_type = "enum"
|
||||||
|
enum_items = attribute.type_of_attribute().declared_type().enumeration_items()
|
||||||
|
new_pset["Properties"].append({
|
||||||
|
"Name": prop_template.Name,
|
||||||
|
"value": None,
|
||||||
|
"type": data_type,
|
||||||
|
"enum_items": enum_items,
|
||||||
|
"is_null": True
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
if hasattr(pset, "HasProperties"):
|
||||||
|
props = pset.HasProperties
|
||||||
|
elif hasattr(pset, "Properties"): # For IfcMaterialProperties
|
||||||
|
props = pset.Properties
|
||||||
|
except:
|
||||||
|
return # I've seen ArchiCAD produce invalid IFCs with empty data
|
||||||
|
# Invalid IFC, but some vendors like Solidworks do this so we accomodate it
|
||||||
|
if not props:
|
||||||
|
return
|
||||||
|
for prop in props:
|
||||||
|
if prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
|
||||||
|
has_existing_prop = False
|
||||||
|
for existing_prop in new_pset["Properties"]:
|
||||||
|
if existing_prop["Name"] == prop.Name:
|
||||||
|
if prop.NominalValue is None:
|
||||||
|
existing_prop["value"] = None
|
||||||
|
elif existing_prop["type"] == "string":
|
||||||
|
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||||
|
elif existing_prop["type"] == "float":
|
||||||
|
existing_prop["value"] = float(prop.NominalValue.wrappedValue)
|
||||||
|
elif existing_prop["type"] == "integer":
|
||||||
|
existing_prop["value"] = int(prop.NominalValue.wrappedValue)
|
||||||
|
elif existing_prop["type"] == "boolean":
|
||||||
|
existing_prop["value"] = bool(prop.NominalValue.wrappedValue)
|
||||||
|
elif existing_prop["type"] == "enum":
|
||||||
|
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||||
|
existing_prop["is_null"] = prop.NominalValue.wrappedValue is None
|
||||||
|
has_existing_prop = True
|
||||||
|
break
|
||||||
|
if not has_existing_prop:
|
||||||
|
value = prop.NominalValue.wrappedValue
|
||||||
|
if isinstance(value, str):
|
||||||
|
data_type = "string"
|
||||||
|
elif isinstance(value, float):
|
||||||
|
data_type = "float"
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
data_type = "boolean"
|
||||||
|
elif isinstance(value, int):
|
||||||
|
data_type = "integer"
|
||||||
|
else:
|
||||||
|
data_type = "string"
|
||||||
|
value = str(value)
|
||||||
|
new_pset["Properties"].append({
|
||||||
|
"Name": prop.Name,
|
||||||
|
"value": value,
|
||||||
|
"type": data_type,
|
||||||
|
"enum_items": [],
|
||||||
|
"is_null": prop.NominalValue.wrappedValue is None
|
||||||
|
})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import blenderbim.bim.schema # TODO: refactor
|
||||||
|
|
||||||
|
|
||||||
|
class Usecase:
|
||||||
|
def __init__(self, file, settings=None):
|
||||||
|
self.file = file
|
||||||
|
self.settings = {"pset": None, "Name": None, "Properties": {}}
|
||||||
|
for key, value in settings.items():
|
||||||
|
self.settings[key] = value
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if hasattr(self.settings["pset"], "HasProperties"):
|
||||||
|
attr_name = "HasProperties"
|
||||||
|
elif hasattr(self.settings["pset"], "Properties"): # For IfcMaterialProperties
|
||||||
|
attr_name = "Properties"
|
||||||
|
props = getattr(self.settings["pset"], attr_name) or []
|
||||||
|
|
||||||
|
if self.settings["Name"]:
|
||||||
|
self.settings["pset"].Name = self.settings["Name"]
|
||||||
|
|
||||||
|
pset_template = blenderbim.bim.schema.ifc.psetqto.get_by_name(self.settings["pset"].Name)
|
||||||
|
|
||||||
|
for prop in props:
|
||||||
|
if prop.Name in self.settings["Properties"]:
|
||||||
|
value = self.settings["Properties"][prop.Name]
|
||||||
|
primary_measure_type = self.get_primary_measure_type(
|
||||||
|
pset_template, prop.Name, previous_value=prop.NominalValue
|
||||||
|
)
|
||||||
|
if value is not None:
|
||||||
|
prop.NominalValue = self.file.create_entity(primary_measure_type, value)
|
||||||
|
else:
|
||||||
|
prop.NominalValue = None
|
||||||
|
del self.settings["Properties"][prop.Name]
|
||||||
|
properties = []
|
||||||
|
for name, value in self.settings["Properties"].items():
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
primary_measure_type = self.get_primary_measure_type(pset_template, name)
|
||||||
|
properties.append(
|
||||||
|
self.file.create_entity(
|
||||||
|
"IfcPropertySingleValue",
|
||||||
|
**{"Name": name, "NominalValue": self.file.create_entity(primary_measure_type, value)},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
props = list(props)
|
||||||
|
props.extend(properties)
|
||||||
|
setattr(self.settings["pset"], attr_name, props)
|
||||||
|
|
||||||
|
def get_primary_measure_type(self, pset_template, name, previous_value=None):
|
||||||
|
if not pset_template:
|
||||||
|
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||||
|
for prop_template in pset_template.HasPropertyTemplates:
|
||||||
|
if prop_template.Name == name:
|
||||||
|
return prop_template.PrimaryMeasureType
|
||||||
|
return previous_value.is_a() if previous_value else "IfcLabel"
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import bpy
|
||||||
|
import json
|
||||||
|
import blenderbim.bim.module.pset.add_pset as add_pset
|
||||||
|
import blenderbim.bim.module.pset.edit_pset as edit_pset
|
||||||
|
import blenderbim.bim.module.pset.remove_pset as remove_pset
|
||||||
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
from blenderbim.bim.module.pset.data import Data
|
||||||
|
|
||||||
|
|
||||||
|
class TogglePsetExpansion(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.toggle_pset_expansion"
|
||||||
|
bl_label = "Toggle Pset Expansion"
|
||||||
|
pset_id: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
Data.psets[self.pset_id]["is_expanded"] = not Data.psets[self.pset_id]["is_expanded"]
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class EnablePsetEditing(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.enable_pset_editing"
|
||||||
|
bl_label = "Enable Pset Editing"
|
||||||
|
pset_id: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
|
||||||
|
while len(props.properties) > 0:
|
||||||
|
props.properties.remove(0)
|
||||||
|
|
||||||
|
props.active_pset_name = Data.psets[self.pset_id]["Name"]
|
||||||
|
for prop in Data.psets[self.pset_id]["Properties"]:
|
||||||
|
new = props.properties.add()
|
||||||
|
new.name = prop["Name"]
|
||||||
|
new.is_null = prop["is_null"]
|
||||||
|
if prop["type"] == "string":
|
||||||
|
new.string_value = prop["value"] or ""
|
||||||
|
elif prop["type"] == "integer":
|
||||||
|
new.int_value = prop["value"] or 0
|
||||||
|
elif prop["type"] == "float":
|
||||||
|
new.float_value = prop["value"] or 0.
|
||||||
|
elif prop["type"] == "boolean":
|
||||||
|
new.bool_value = prop["value"] or False
|
||||||
|
elif prop["type"] == "enum":
|
||||||
|
new.enum_items = json.dumps(prop["enum_items"])
|
||||||
|
if prop["value"]:
|
||||||
|
new.enum_value = prop["value"]
|
||||||
|
|
||||||
|
props.active_pset_id = self.pset_id
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class DisablePsetEditing(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.disable_pset_editing"
|
||||||
|
bl_label = "Disable Pset Editing"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
props.active_pset_id = 0
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class EditPset(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.edit_pset"
|
||||||
|
bl_label = "Edit Pset"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
properties = {}
|
||||||
|
for prop in Data.psets[props.active_pset_id]["Properties"]:
|
||||||
|
blender_prop = props.properties.get(prop["Name"])
|
||||||
|
if not blender_prop:
|
||||||
|
continue
|
||||||
|
if blender_prop.is_null:
|
||||||
|
properties[prop["Name"]] = None
|
||||||
|
elif prop["type"] == "string":
|
||||||
|
properties[prop["Name"]] = blender_prop.string_value
|
||||||
|
elif prop["type"] == "boolean":
|
||||||
|
properties[prop["Name"]] = blender_prop.bool_value
|
||||||
|
elif prop["type"] == "integer":
|
||||||
|
properties[prop["Name"]] = blender_prop.int_value
|
||||||
|
elif prop["type"] == "float":
|
||||||
|
properties[prop["Name"]] = blender_prop.float_value
|
||||||
|
elif prop["type"] == "enum":
|
||||||
|
properties[prop["Name"]] = blender_prop.enum_value
|
||||||
|
edit_pset.Usecase(self.file, {
|
||||||
|
"pset": self.file.by_id(props.active_pset_id),
|
||||||
|
"Name": props.active_pset_name,
|
||||||
|
"Properties": properties
|
||||||
|
}).execute()
|
||||||
|
Data.load(props.ifc_definition_id)
|
||||||
|
bpy.ops.bim.disable_pset_editing()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class RemovePset(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.remove_pset"
|
||||||
|
bl_label = "Remove Pset"
|
||||||
|
pset_id: bpy.props.IntProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
remove_pset.Usecase(self.file, {
|
||||||
|
"product": self.file.by_id(props.ifc_definition_id),
|
||||||
|
"pset": self.file.by_id(self.pset_id),
|
||||||
|
}).execute()
|
||||||
|
Data.load(props.ifc_definition_id)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class AddPset(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.add_pset"
|
||||||
|
bl_label = "Add Pset"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
props = obj.BIMObjectProperties
|
||||||
|
add_pset.Usecase(self.file, {
|
||||||
|
"product": self.file.by_id(props.ifc_definition_id),
|
||||||
|
"Name": props.pset_name,
|
||||||
|
}).execute()
|
||||||
|
Data.load(props.ifc_definition_id)
|
||||||
|
return {"FINISHED"}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import blenderbim.bim.schema # TODO: refactor
|
||||||
|
|
||||||
|
|
||||||
|
class Usecase:
|
||||||
|
def __init__(self, file, settings=None):
|
||||||
|
self.file = file
|
||||||
|
self.settings = {"product": None, "pset": None}
|
||||||
|
for key, value in settings.items():
|
||||||
|
self.settings[key] = value
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
to_purge = []
|
||||||
|
should_remove_pset = True
|
||||||
|
for inverse in self.file.get_inverse(self.settings["pset"]):
|
||||||
|
if inverse.is_a("IfcRelDefinesByProperties"):
|
||||||
|
if not inverse.RelatedObjects or len(inverse.RelatedObjects) == 1:
|
||||||
|
to_purge.append(inverse)
|
||||||
|
else:
|
||||||
|
related_objects = list(inverse.RelatedObjects)
|
||||||
|
related_objects.remove(self.settings["product"])
|
||||||
|
inverse.RelatedObjects = related_objects
|
||||||
|
should_remove_pset = False
|
||||||
|
if should_remove_pset:
|
||||||
|
self.file.remove(self.settings["pset"])
|
||||||
|
for element in to_purge:
|
||||||
|
self.file.remove(element)
|
||||||
|
# TODO: implement deep purging
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
from bpy.types import Panel
|
||||||
|
from blenderbim.bim.module.pset.data import Data
|
||||||
|
|
||||||
|
|
||||||
|
class BIM_PT_object_psets(Panel):
|
||||||
|
bl_label = "IFC Object Property Sets"
|
||||||
|
bl_idname = "BIM_PT_object_psets"
|
||||||
|
bl_space_type = "PROPERTIES"
|
||||||
|
bl_region_type = "WINDOW"
|
||||||
|
bl_context = "object"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties")
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
props = context.active_object.BIMObjectProperties
|
||||||
|
if not props.ifc_definition_id:
|
||||||
|
return
|
||||||
|
if props.ifc_definition_id not in Data.products:
|
||||||
|
Data.load(props.ifc_definition_id)
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(props, "pset_name", text="")
|
||||||
|
row.operator("bim.add_pset", icon="ADD", text="")
|
||||||
|
|
||||||
|
self.draw_psets_ui(props, Data.products[props.ifc_definition_id])
|
||||||
|
|
||||||
|
# TODO reimplement. See #1222.
|
||||||
|
#if props.relating_type and props.relating_type.BIMObjectProperties.psets:
|
||||||
|
# self.layout.label(text="Inherited Psets:")
|
||||||
|
# self.draw_psets_ui(props.relating_type.BIMObjectProperties, enabled=False)
|
||||||
|
|
||||||
|
def draw_psets_ui(self, props, pset_ids):
|
||||||
|
for pset_id in pset_ids:
|
||||||
|
pset = Data.psets[pset_id]
|
||||||
|
box = self.layout.box()
|
||||||
|
row = box.row(align=True)
|
||||||
|
icon = "TRIA_DOWN" if pset["is_expanded"] else "TRIA_RIGHT"
|
||||||
|
row.operator("bim.toggle_pset_expansion", icon=icon, text="", emboss=False).pset_id = pset_id
|
||||||
|
if not props.active_pset_id:
|
||||||
|
row.label(text=pset["Name"], icon="COPY_ID")
|
||||||
|
row.operator("bim.enable_pset_editing", icon="GREASEPENCIL", text="").pset_id = pset_id
|
||||||
|
row.operator("bim.remove_pset", icon="X", text="").pset_id = pset_id
|
||||||
|
elif props.active_pset_id != pset_id:
|
||||||
|
row.label(text=pset["Name"], icon="COPY_ID")
|
||||||
|
row.operator("bim.remove_pset", icon="X", text="").pset_id = pset_id
|
||||||
|
elif props.active_pset_id == pset_id:
|
||||||
|
row.prop(props, "active_pset_name", icon="COPY_ID", text="")
|
||||||
|
row.operator("bim.edit_pset", icon="CHECKMARK", text="")
|
||||||
|
row.operator("bim.disable_pset_editing", icon="X", text="")
|
||||||
|
if pset["is_expanded"]:
|
||||||
|
if props.active_pset_id == pset_id:
|
||||||
|
for prop in pset["Properties"]:
|
||||||
|
row = box.row(align=True)
|
||||||
|
blender_prop = props.properties.get(prop["Name"])
|
||||||
|
if prop["type"] == "string":
|
||||||
|
row.prop(blender_prop, "string_value", text=prop["Name"])
|
||||||
|
elif prop["type"] == "integer":
|
||||||
|
row.prop(blender_prop, "int_value", text=prop["Name"])
|
||||||
|
elif prop["type"] == "float":
|
||||||
|
row.prop(blender_prop, "float_value", text=prop["Name"])
|
||||||
|
elif prop["type"] == "boolean":
|
||||||
|
row.prop(blender_prop, "bool_value", text=prop["Name"])
|
||||||
|
elif prop["type"] == "enum":
|
||||||
|
row.prop(blender_prop, "enum_value", text=prop["Name"])
|
||||||
|
row.prop(blender_prop, "is_null", icon="RADIOBUT_OFF" if blender_prop.is_null else "RADIOBUT_ON", text="")
|
||||||
|
else:
|
||||||
|
for prop in pset["Properties"]:
|
||||||
|
if prop["value"] is None or prop["value"] == "":
|
||||||
|
continue
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.scale_y = 0.8
|
||||||
|
row.label(text=prop["Name"])
|
||||||
|
row.label(text=str(prop["value"]))
|
||||||
@@ -484,45 +484,6 @@ class AddQto(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class AddPset(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.add_pset"
|
|
||||||
bl_label = "Add Pset"
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
name = bpy.context.active_object.BIMObjectProperties.pset_name
|
|
||||||
pset_template = schema.ifc.psetqto.get_by_name(name)
|
|
||||||
if not pset_template:
|
|
||||||
return {"FINISHED"}
|
|
||||||
for obj in bpy.context.selected_objects:
|
|
||||||
if "/" not in obj.name or obj.BIMObjectProperties.psets.find(name) != -1:
|
|
||||||
continue
|
|
||||||
applicable_psets = schema.ifc.psetqto.get_applicable_names(obj.name.split("/")[0], pset_only=True)
|
|
||||||
if name not in applicable_psets:
|
|
||||||
continue
|
|
||||||
pset = obj.BIMObjectProperties.psets.add()
|
|
||||||
pset.name = name
|
|
||||||
for prop_name in (p.Name for p in pset_template.HasPropertyTemplates):
|
|
||||||
prop = pset.properties.add()
|
|
||||||
prop.name = prop_name
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class RemovePset(bpy.types.Operator):
|
|
||||||
bl_idname = "bim.remove_pset"
|
|
||||||
bl_label = "Remove Pset"
|
|
||||||
pset_index: bpy.props.IntProperty()
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
name = bpy.context.active_object.BIMObjectProperties.psets[self.pset_index].name
|
|
||||||
for obj in bpy.context.selected_objects:
|
|
||||||
if "/" not in obj.name:
|
|
||||||
continue
|
|
||||||
index = obj.BIMObjectProperties.psets.find(name)
|
|
||||||
if index != -1:
|
|
||||||
obj.BIMObjectProperties.psets.remove(index)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveQto(bpy.types.Operator):
|
class RemoveQto(bpy.types.Operator):
|
||||||
bl_idname = "bim.remove_qto"
|
bl_idname = "bim.remove_qto"
|
||||||
bl_label = "Remove Qto"
|
bl_label = "Remove Qto"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ psettemplatefiles_enum = []
|
|||||||
propertysettemplates_enum = []
|
propertysettemplates_enum = []
|
||||||
classification_enum = []
|
classification_enum = []
|
||||||
attributes_enum = []
|
attributes_enum = []
|
||||||
psetnames_enum = []
|
psetnames = {}
|
||||||
qtonames_enum = []
|
qtonames_enum = []
|
||||||
materialattributes_enum = []
|
materialattributes_enum = []
|
||||||
materialtypes_enum = []
|
materialtypes_enum = []
|
||||||
@@ -435,12 +435,14 @@ def refreshReferences(self, context):
|
|||||||
|
|
||||||
|
|
||||||
def getPsetNames(self, context):
|
def getPsetNames(self, context):
|
||||||
global psetnames_enum
|
global psetnames
|
||||||
psetnames_enum.clear()
|
if "/" in context.active_object.name:
|
||||||
if "/" in context.active_object.name and context.active_object.name.split("/")[0] in schema.ifc.elements:
|
ifc_class = context.active_object.name.split("/")[0]
|
||||||
pset_names = schema.ifc.psetqto.get_applicable_names(context.active_object.name.split("/")[0], pset_only=True)
|
if ifc_class not in psetnames:
|
||||||
psetnames_enum.extend([(p, p, "") for p in pset_names])
|
psets = schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||||
return psetnames_enum
|
psetnames[ifc_class] = [(p.Name, p.Name, "") for p in psets]
|
||||||
|
return psetnames[ifc_class]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def getMaterialPsetNames(self, context):
|
def getMaterialPsetNames(self, context):
|
||||||
@@ -1483,6 +1485,9 @@ class BIMObjectProperties(PropertyGroup):
|
|||||||
is_editing_type: BoolProperty(name="Is Editing Type")
|
is_editing_type: BoolProperty(name="Is Editing Type")
|
||||||
relating_type: PointerProperty(name="Type Product", type=bpy.types.Object)
|
relating_type: PointerProperty(name="Type Product", type=bpy.types.Object)
|
||||||
relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object)
|
relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object)
|
||||||
|
active_pset_id: IntProperty(name="Active Pset ID")
|
||||||
|
active_pset_name: StringProperty(name="Pset Name")
|
||||||
|
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||||
psets: CollectionProperty(name="Psets", type=PsetQto)
|
psets: CollectionProperty(name="Psets", type=PsetQto)
|
||||||
qtos: CollectionProperty(name="Qtos", type=PsetQto)
|
qtos: CollectionProperty(name="Qtos", type=PsetQto)
|
||||||
document_references: CollectionProperty(name="Document References", type=DocumentReference)
|
document_references: CollectionProperty(name="Document References", type=DocumentReference)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class IfcSchema:
|
|||||||
|
|
||||||
self.property_files = []
|
self.property_files = []
|
||||||
property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
|
property_paths = self.data_dir.joinpath("pset").glob("*.ifc")
|
||||||
|
# TODO: add IFC2X3 PsetQto template support
|
||||||
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4")
|
self.psetqto = ifcopenshell.util.pset.PsetQto("IFC4")
|
||||||
for path in property_paths:
|
for path in property_paths:
|
||||||
self.psetqto.templates.append(ifcopenshell.open(path))
|
self.psetqto.templates.append(ifcopenshell.open(path))
|
||||||
|
|||||||
@@ -143,73 +143,6 @@ class BIM_PT_object_material(Panel):
|
|||||||
row.prop(attribute, "string_value", text="")
|
row.prop(attribute, "string_value", text="")
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_object_psets(Panel):
|
|
||||||
bl_label = "IFC Object Property Sets"
|
|
||||||
bl_idname = "BIM_PT_object_psets"
|
|
||||||
bl_space_type = "PROPERTIES"
|
|
||||||
bl_region_type = "WINDOW"
|
|
||||||
bl_context = "object"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties")
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
if context.active_object is None:
|
|
||||||
return
|
|
||||||
layout = self.layout
|
|
||||||
props = context.active_object.BIMObjectProperties
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(props, "pset_name", text="")
|
|
||||||
row.operator("bim.add_pset")
|
|
||||||
|
|
||||||
self.draw_psets_ui(props)
|
|
||||||
|
|
||||||
if props.relating_type and props.relating_type.BIMObjectProperties.psets:
|
|
||||||
layout.label(text="Inherited Psets:")
|
|
||||||
self.draw_psets_ui(props.relating_type.BIMObjectProperties, enabled=False)
|
|
||||||
|
|
||||||
def draw_psets_ui(self, props, enabled=True):
|
|
||||||
for index, pset in enumerate(props.psets):
|
|
||||||
box = self.layout.box()
|
|
||||||
row = box.row(align=True)
|
|
||||||
row.prop(
|
|
||||||
pset,
|
|
||||||
"is_expanded",
|
|
||||||
icon="TRIA_DOWN" if pset.is_expanded else "TRIA_RIGHT",
|
|
||||||
icon_only=True,
|
|
||||||
emboss=False,
|
|
||||||
)
|
|
||||||
row2 = row.row(align=True)
|
|
||||||
row2.enabled = enabled
|
|
||||||
row2.prop(pset, "name", text="", icon="COPY_ID", emboss=pset.is_editable)
|
|
||||||
|
|
||||||
if enabled:
|
|
||||||
row.prop(pset, "is_editable", icon="CHECKMARK" if pset.is_editable else "GREASEPENCIL", icon_only=True)
|
|
||||||
row.operator("bim.remove_pset", icon="X", text="").pset_index = index
|
|
||||||
if not pset.is_expanded:
|
|
||||||
continue
|
|
||||||
if pset.is_editable:
|
|
||||||
for prop in pset.properties:
|
|
||||||
row = box.row(align=True)
|
|
||||||
row.enabled = enabled
|
|
||||||
row.prop(prop, "name", text="")
|
|
||||||
row.prop(prop, "string_value", text="")
|
|
||||||
if not enabled:
|
|
||||||
continue
|
|
||||||
op = row.operator("bim.copy_property_to_selection", icon="COPYDOWN", text="")
|
|
||||||
op.pset_name = pset.name
|
|
||||||
op.prop_name = prop.name
|
|
||||||
op.prop_value = prop.string_value
|
|
||||||
else:
|
|
||||||
col = box.column(align=True)
|
|
||||||
for prop in pset.properties:
|
|
||||||
if not prop.string_value:
|
|
||||||
continue
|
|
||||||
col.enabled = enabled
|
|
||||||
col.prop(prop, "string_value", text=prop.name)
|
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_object_qto(Panel):
|
class BIM_PT_object_qto(Panel):
|
||||||
bl_label = "IFC Object Quantity Sets"
|
bl_label = "IFC Object Quantity Sets"
|
||||||
bl_idname = "BIM_PT_object_qto"
|
bl_idname = "BIM_PT_object_qto"
|
||||||
|
|||||||
Reference in New Issue
Block a user