diff --git a/README.md b/README.md index 69e777d592..87eba9c96a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For more information, see * [http://ifcopenshell.org](http://ifcopenshell.org) * [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org) -[![Build Status](https://api.travis-ci.org/IfcOpenShell/IfcOpenShell.png)](https://api.travis-ci.org/IfcOpenShell/IfcOpenShell) +[![Build Status](https://travis-ci.org/IfcOpenShell/IfcOpenShell.svg?branch=v0.6.0)](https://travis-ci.org/IfcOpenShell/IfcOpenShell) Prerequisites ------------- diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index ef9e00d2ff..b696551ffb 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -71,6 +71,9 @@ if bpy is not None: operator.AddMaterialLayer, operator.RemoveMaterialLayer, operator.MoveMaterialLayer, + operator.AddMaterialConstituent, + operator.RemoveMaterialConstituent, + operator.MoveMaterialConstituent, operator.AddConstraint, operator.RemoveConstraint, operator.AssignConstraint, @@ -169,6 +172,7 @@ if bpy is not None: operator.PropagateTextData, operator.PushRepresentation, operator.ConvertLocalToGlobal, + operator.ConvertGlobalToLocal, operator.GuessQuantity, operator.ExecuteBIMTester, operator.BIMTesterPurge, @@ -212,6 +216,8 @@ if bpy is not None: operator.UpdateIfcRepresentation, prop.StrProperty, prop.MaterialLayer, + prop.MaterialConstituent, + prop.MaterialSet, prop.Variable, prop.Role, prop.Address, diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index f43d4cfef6..98a6d8d94a 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1,5 +1,6 @@ import bpy import csv +import bmesh import json import time import datetime @@ -426,19 +427,14 @@ class IfcParser: for constraint in obj.BIMObjectProperties.constraints: self.rel_associates_constraint_object.setdefault(constraint.name, []).append(product) - for slot in obj.material_slots: - if slot.material is None or slot.link == "OBJECT": - continue - if obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet": - self.rel_associates_material_layer_set.setdefault(self.product_index, []).append(slot.material.name) - elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet": - self.rel_associates_material_constituent_set.setdefault(self.product_index, []).append( - slot.material.name - ) - elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet": - self.rel_associates_material_profile_set.setdefault(self.product_index, []).append(slot.material.name) - else: - self.rel_associates_material.setdefault(slot.material.name, []).append(product) + if obj.BIMObjectProperties.material_type == "IfcMaterial" and obj.BIMObjectProperties.material: + self.rel_associates_material.setdefault(obj.BIMObjectProperties.material.name, []).append(product) + elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet": + self.rel_associates_material_constituent_set[self.product_index] = obj.BIMObjectProperties.material_set + elif obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet": + self.rel_associates_material_layer_set[self.product_index] = obj.BIMObjectProperties.material_set + elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet": + pass # TODO return product @@ -1157,7 +1153,7 @@ class IfcParser: continue results[slot.material.name] = { "ifc": None, - "part_ifc": None, + "part_ifc": None, # TODO: check if we deprecate this "raw": slot.material, "material_type": obj.BIMObjectProperties.material_type, "attributes": self.get_material_attributes(slot.material), @@ -2951,6 +2947,14 @@ class IfcExporter: mesh = representation["raw"] if not representation["is_parametric"]: mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + if self.ifc_export_settings.should_force_triangulation: + mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + bm = bmesh.new() + bm.from_mesh(mesh) + bmesh.ops.triangulate(bm, faces=bm.faces) + bm.to_mesh(mesh) + bm.free() + del bm if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_force_faceted_brep: return self.create_faceted_brep(representation, mesh) return self.create_polygonal_face_set(representation, mesh) @@ -3137,17 +3141,27 @@ class IfcExporter: def relate_objects_to_material_sets(self, set_type): if not self.ifc_export_settings.has_representations: return - for product_index, related_materials in getattr( - self.ifc_parser, f"rel_associates_material_{set_type}_set" - ).items(): - material_set = self.file.create_entity( - f"IfcMaterial{set_type.capitalize()}Set", - **{ - f"Material{set_type.capitalize()}s": [ - self.ifc_parser.materials[m]["part_ifc"] for m in related_materials - ] - }, - ) + for product_index, material_set in getattr(self.ifc_parser, f"rel_associates_material_{set_type}_set").items(): + if set_type == "constituent": + materials = self.create_material_constituents(material_set.material_constituents) + elif set_type == "layer": + materials = self.create_material_layers(material_set.material_layers) + elif set_type == "profile": + materials = [] # TODO + if not materials: + continue + + attributes = { + f"Material{set_type.capitalize()}s": materials, + "Description": material_set.description or None, + } + + if set_type == "layer": + attributes["LayerSetName"] = material_set.name or None + else: + attributes["Name"] = material_set.name or None + + material_set = self.file.create_entity(f"IfcMaterial{set_type.capitalize()}Set", **attributes) self.file.createIfcRelAssociatesMaterial( ifcopenshell.guid.new(), self.owner_history, @@ -3157,6 +3171,50 @@ class IfcExporter: material_set, ) + def create_material_layers(self, layers): + results = [] + for layer in layers: + if layer.category == "None": + category = None + elif layer.category == "Custom": + category = layer.custom_category or None + else: + category = layer.category + is_ventilated = layer.is_ventilated == "TRUE" if layer.is_ventilated != "UNKNOWN" else None + results.append( + self.file.create_entity( + "IfcMaterialLayer", + **{ + "Material": self.ifc_parser.materials[layer.material.name]["ifc"] or None, + "LayerThickness": layer.layer_thickness, + "IsVentilated": is_ventilated, + "Name": layer.name or None, + "Description": layer.description or None, + "Category": category, + "Priority": layer.priority, + }, + ) + ) + return results + + def create_material_constituents(self, constituents): + results = [] + # TODO: the correlation for IfcShapeAspect is not yet implemented + for constituent in constituents: + results.append( + self.file.create_entity( + "IfcMaterialConstituent", + **{ + "Name": constituent.name or None, + "Description": constituent.description or None, + "Material": self.ifc_parser.materials[constituent.material.name]["ifc"], + "Fraction": constituent.fraction or None, + "Category": constituent.category or None, + }, + ) + ) + return results + def relate_spaces_to_boundary_elements(self): for (relating_space_index, relationships,) in self.ifc_parser.rel_space_boundaries.items(): for relationship in relationships: @@ -3342,6 +3400,7 @@ class IfcExportSettings: settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment settings.should_guess_quantities = scene_bim.export_should_guess_quantities settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep + settings.should_force_triangulation = scene_bim.export_should_force_triangulation settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.context_tree = [] for ifc_context in ["model", "plan"]: diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index e1a8fe02c4..abd3fbbb88 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -3598,25 +3598,48 @@ class ConvertLocalToGlobal(bpy.types.Operator): bl_label = "Convert Local To Global" def execute(self, context): - x, y, z = bpy.context.scene.cursor.location - if bpy.context.scene.MapConversion.scale: scale = float(bpy.context.scene.MapConversion.scale) else: scale = 1.0 - - rotation = atan2( - float(bpy.context.scene.MapConversion.x_axis_ordinate), + results = ifcopenshell.util.geolocation.xyz2enh( + bpy.context.scene.cursor.location[0], + bpy.context.scene.cursor.location[1], + bpy.context.scene.cursor.location[2], + float(bpy.context.scene.MapConversion.eastings), + float(bpy.context.scene.MapConversion.northings), + float(bpy.context.scene.MapConversion.orthogonal_height), float(bpy.context.scene.MapConversion.x_axis_abscissa), + float(bpy.context.scene.MapConversion.x_axis_ordinate), + scale, ) - a = scale * cos(rotation) - b = scale * sin(rotation) + print("Coordinates:", results) + bpy.context.scene.cursor.location = results + return {"FINISHED"} - eastings = (a * x) - (b * y) + float(bpy.context.scene.MapConversion.eastings) - northings = (b * x) + (a * y) + float(bpy.context.scene.MapConversion.northings) - height = z + float(bpy.context.scene.MapConversion.orthogonal_height) - bpy.context.scene.cursor.location = (eastings, northings, height) +class ConvertGlobalToLocal(bpy.types.Operator): + bl_idname = "bim.convert_global_to_local" + bl_label = "Convert Global To Local" + + def execute(self, context): + if bpy.context.scene.MapConversion.scale: + scale = float(bpy.context.scene.MapConversion.scale) + else: + scale = 1.0 + results = ifcopenshell.util.geolocation.enh2xyz( + float(bpy.context.scene.BIMProperties.eastings), + float(bpy.context.scene.BIMProperties.northings), + float(bpy.context.scene.BIMProperties.orthogonal_height), + float(bpy.context.scene.MapConversion.eastings), + float(bpy.context.scene.MapConversion.northings), + float(bpy.context.scene.MapConversion.orthogonal_height), + float(bpy.context.scene.MapConversion.x_axis_abscissa), + float(bpy.context.scene.MapConversion.x_axis_ordinate), + scale, + ) + print("Coordinates:", results) + bpy.context.scene.cursor.location = results return {"FINISHED"} @@ -4103,7 +4126,7 @@ class AddMaterialLayer(bpy.types.Operator): bl_label = "Add Material Layer" def execute(self, context): - new = bpy.context.active_object.BIMObjectProperties.material_layers.add() + new = bpy.context.active_object.BIMObjectProperties.material_set.material_layers.add() new.material = bpy.data.materials[0] new.name = "Material Layer" return {"FINISHED"} @@ -4115,7 +4138,7 @@ class RemoveMaterialLayer(bpy.types.Operator): index: bpy.props.IntProperty() def execute(self, context): - bpy.context.active_object.BIMObjectProperties.material_layers.remove(self.index) + bpy.context.active_object.BIMObjectProperties.material_set.material_layers.remove(self.index) return {"FINISHED"} @@ -4125,7 +4148,7 @@ class MoveMaterialLayer(bpy.types.Operator): direction: bpy.props.StringProperty() def execute(self, context): - props = bpy.context.active_object.BIMObjectProperties + props = bpy.context.active_object.BIMObjectProperties.material_set index = props.active_material_layer_index if self.direction == "UP" and index - 1 >= 0: props.material_layers.move(index, index - 1) @@ -4136,6 +4159,44 @@ class MoveMaterialLayer(bpy.types.Operator): return {"FINISHED"} +class AddMaterialConstituent(bpy.types.Operator): + bl_idname = "bim.add_material_constituent" + bl_label = "Add Material Constituent" + + def execute(self, context): + new = bpy.context.active_object.BIMObjectProperties.material_set.material_constituents.add() + new.material = bpy.data.materials[0] + new.name = "Material Constituent" + return {"FINISHED"} + + +class RemoveMaterialConstituent(bpy.types.Operator): + bl_idname = "bim.remove_material_constituent" + bl_label = "Remove Material Constituent" + index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.active_object.BIMObjectProperties.material_set.material_constituents.remove(self.index) + return {"FINISHED"} + + +class MoveMaterialConstituent(bpy.types.Operator): + bl_idname = "bim.move_material_constituent" + bl_label = "Move Material Constituent" + direction: bpy.props.StringProperty() + + def execute(self, context): + props = bpy.context.active_object.BIMObjectProperties.material_set + index = props.active_material_constituent_index + if self.direction == "UP" and index - 1 >= 0: + props.material_constituents.move(index, index - 1) + props.active_material_constituent_index = index - 1 + elif self.direction == "DOWN" and index + 1 < len(props.material_constituents): + props.material_constituents.move(index, index + 1) + props.active_material_constituent_index = index + 1 + return {"FINISHED"} + + class SelectScheduleFile(bpy.types.Operator): bl_idname = "bim.select_schedule_file" bl_label = "Select Documentation IFC File" @@ -4199,10 +4260,10 @@ class SetNorthOffset(bpy.types.Operator): bl_label = "Set North Offset" def execute(self, context): - context.scene.sun_pos_properties.north_offset = radians( + context.scene.sun_pos_properties.north_offset = -radians( ifcopenshell.util.geolocation.xy2angle( - float(bpy.context.scene.MapConversion.x_axis_ordinate), float(bpy.context.scene.MapConversion.x_axis_abscissa), + float(bpy.context.scene.MapConversion.x_axis_ordinate), ) ) return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 1c204019b7..7c4096a1e6 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -583,6 +583,7 @@ class MaterialLayer(PropertyGroup): description: StringProperty(name="Description") category: EnumProperty( items=[ + ("None", "None", ""), ("LoadBearing", "Load Bearing", ""), ("Insulation", "Insulation", ""), ("Finish", "Finish", ""), @@ -595,6 +596,23 @@ class MaterialLayer(PropertyGroup): priority: IntProperty(name="Priority") +class MaterialConstituent(PropertyGroup): + name: StringProperty(name="Name") + description: StringProperty(name="Description") + material: PointerProperty(name="Material", type=bpy.types.Material) + fraction: FloatProperty(name="Fraction") + category: StringProperty(name="Category") + + +class MaterialSet(PropertyGroup): + name: StringProperty(name="Name") + description: StringProperty(name="Description") + active_material_layer_index: IntProperty(name="Active Material Layer Index") + material_layers: CollectionProperty(name="Material Layers", type=MaterialLayer) + active_material_constituent_index: IntProperty(name="Active Material Constituent Index") + material_constituents: CollectionProperty(name="Material Constituents", type=MaterialConstituent) + + class Drawing(PropertyGroup): name: StringProperty(name="Name", update=updateDrawingName) camera: PointerProperty(name="Camera", type=bpy.types.Object) @@ -1411,6 +1429,7 @@ class BIMProperties(PropertyGroup): name="Export with Presentation Style Assignment", default=False ) export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False) + export_should_force_triangulation: BoolProperty(name="Export with Triangulation", default=False) import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False) import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False) import_should_reset_absolute_coordinates: BoolProperty(name="Import Resetting Absolute Coordinates", default=False) @@ -1503,6 +1522,9 @@ class BIMProperties(PropertyGroup): active_clash_set_index: IntProperty(name="Active Clash Set Index") constraints: CollectionProperty(name="Constraints", type=Constraint) active_constraint_index: IntProperty(name="Active Constraint Index") + eastings: StringProperty(name="Eastings") + northings: StringProperty(name="Northings") + orthogonal_height: StringProperty(name="Orthogonal Height") ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes") ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC") ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC") @@ -1665,8 +1687,7 @@ class BIMObjectProperties(PropertyGroup): classifications: CollectionProperty(name="Classifications", type=ClassificationReference) material_type: EnumProperty(items=getMaterialTypes, name="Material Type") material: PointerProperty(name="Material", type=bpy.types.Material) - material_layers: CollectionProperty(name="Material Layers", type=MaterialLayer) - active_material_layer_index: IntProperty(name="Active Material Layer Index") + material_set: PointerProperty(name="Material Set", type=MaterialSet) pset_name: EnumProperty(items=getPsetNames, name="Pset Name") qto_name: EnumProperty(items=getQtoNames, name="Qto Name") has_boundary_condition: BoolProperty(name="Has Boundary Condition") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index c3b42c756a..be7f813026 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -126,19 +126,28 @@ class BIM_PT_object_material(Panel): if props.material_type == "IfcMaterial": row = layout.row() row.prop(props, "material") - elif props.material_type == "IfcMaterialLayerSet": + else: + set_props = props.material_set row = layout.row() + row.prop(set_props, "name", text="Name") + row = layout.row() + row.prop(set_props, "description", text="Description") + row = layout.row() + + if props.material_type == "IfcMaterialLayerSet": row.template_list( - "MATERIAL_UL_matslots", "", props, "material_layers", props, "active_material_layer_index" + "MATERIAL_UL_matslots", "", set_props, "material_layers", set_props, "active_material_layer_index" ) col = row.column(align=True) col.operator("bim.add_material_layer", icon="ADD", text="") - col.operator("bim.remove_material_layer", icon="REMOVE", text="").index = props.active_material_layer_index + col.operator( + "bim.remove_material_layer", icon="REMOVE", text="" + ).index = set_props.active_material_layer_index col.operator("bim.move_material_layer", icon="TRIA_UP", text="").direction = "UP" col.operator("bim.move_material_layer", icon="TRIA_DOWN", text="").direction = "DOWN" - if props.active_material_layer_index < len(props.material_layers): - material = props.material_layers[props.active_material_layer_index] + if set_props.active_material_layer_index < len(set_props.material_layers): + material = set_props.material_layers[set_props.active_material_layer_index] row = layout.row() row.prop(material, "material") row = layout.row() @@ -153,7 +162,38 @@ class BIM_PT_object_material(Panel): row = layout.row() row.prop(material, "layer_thickness") row = layout.row() + row.prop(material, "is_ventilated") + row = layout.row() row.prop(material, "priority") + elif props.material_type == "IfcMaterialConstituentSet": + row.template_list( + "MATERIAL_UL_matslots", + "", + set_props, + "material_constituents", + set_props, + "active_material_constituent_index", + ) + col = row.column(align=True) + col.operator("bim.add_material_constituent", icon="ADD", text="") + col.operator( + "bim.remove_material_constituent", icon="REMOVE", text="" + ).index = set_props.active_material_constituent_index + col.operator("bim.move_material_constituent", icon="TRIA_UP", text="").direction = "UP" + col.operator("bim.move_material_constituent", icon="TRIA_DOWN", text="").direction = "DOWN" + + if set_props.active_material_constituent_index < len(set_props.material_constituents): + material = set_props.material_constituents[set_props.active_material_constituent_index] + row = layout.row() + row.prop(material, "material") + row = layout.row() + row.prop(material, "name") + row = layout.row() + row.prop(material, "description") + row = layout.row() + row.prop(material, "fraction") + row = layout.row() + row.prop(material, "category") class BIM_PT_object_psets(Panel): @@ -881,6 +921,13 @@ class BIM_PT_gis(Panel): row = layout.row(align=True) row.operator("bim.convert_local_to_global") + layout.row().prop(scene.BIMProperties, "eastings") + layout.row().prop(scene.BIMProperties, "northings") + layout.row().prop(scene.BIMProperties, "orthogonal_height") + + row = layout.row(align=True) + row.operator("bim.convert_global_to_local") + if hasattr(bpy.context.scene, "sun_pos_properties"): row = layout.row(align=True) row.operator("bim.get_north_offset") @@ -1921,6 +1968,16 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, "import_should_auto_set_workarounds") + layout.label(text="RIB iTWO Workarounds:") + + row = layout.row() + row.prop(bim_properties, "export_should_force_faceted_brep") + + layout.label(text="Navisworks Workarounds:") + + row = layout.row() + row.prop(bim_properties, "export_should_force_triangulation") + layout.label(text="Tekla Workarounds:") row = layout.row() diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 8bf5fd1489..ecc8798ba3 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -1736,9 +1736,11 @@ IfcSchema::IfcRelVoidsElement::list::ptr IfcGeom::Kernel::find_openings(IfcSchem // Filter openings in Reference view, solely marked as Reference. IfcSchema::IfcRelVoidsElement::list::ptr openings(new IfcSchema::IfcRelVoidsElement::list); std::for_each(rs.begin(), rs.end(), [&openings](IfcSchema::IfcRelVoidsElement* rel) { - auto reps = rel->RelatedOpeningElement()->Representation()->Representations(); - if (!(reps->size() == 1 && (*reps->begin())->RepresentationIdentifier() == "Reference")) { - openings->push(rel); + if (rel->RelatedOpeningElement()->hasObjectPlacement() && rel->RelatedOpeningElement()->hasRepresentation()) { + auto reps = rel->RelatedOpeningElement()->Representation()->Representations(); + if (!(reps->size() == 1 && (*reps->begin())->RepresentationIdentifier() == "Reference")) { + openings->push(rel); + } } }); diff --git a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py index 7e1dcd8b96..4bc3e9c2c1 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/geolocation.py +++ b/src/ifcopenshell-python/ifcopenshell/util/geolocation.py @@ -33,6 +33,18 @@ def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_ return (eastings, northings, height) +def enh2xyz(e, n, h, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None): + if scale is None: + scale = 1.0 + rotation = math.atan2(x_axis_ordinate, x_axis_abscissa) + a = scale * math.cos(rotation) + b = scale * math.sin(rotation) + x = ((b * n) - (b * northings) - (a * eastings) + (a * e)) / ((a * a) + (b * b)) + y = ((a * n) - (a * northings) + (b * eastings) - (b * e)) / ((a * a) + (b * b)) + z = h - orthogonal_height + return (x, y, z) + + # Used for converting the X and Y vectors of the X Axis in IFC geolocation def xy2angle(x, y): return math.degrees(math.atan2(y, x)) diff --git a/src/ifcsverchok/nodes/ifc/by_type.py b/src/ifcsverchok/nodes/ifc/by_type.py index c6602975ca..26c570e0f0 100644 --- a/src/ifcsverchok/nodes/ifc/by_type.py +++ b/src/ifcsverchok/nodes/ifc/by_type.py @@ -1,16 +1,18 @@ import bpy import ifcopenshell import ifcsverchok.helper -from bpy.props import StringProperty +from bpy.props import StringProperty, EnumProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode +from blenderbim.bim import schema class SvIfcByType(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): bl_idname = "SvIfcByType" bl_label = "IFC By Type" + ifc_element_types = [(t, t, t) for t in schema.IfcSchema().IfcElementType.keys()] file: StringProperty(name="file", update=updateNode) - type: StringProperty(name="type", update=updateNode) + type: EnumProperty(name="type", items=ifc_element_types) def sv_init(self, context): self.inputs.new("SvStringsSocket", "file").prop_name = "file"