merage master v6.0.0

This commit is contained in:
admin
2021-04-12 10:26:10 +08:00
parent 72fdca9e82
commit 397b6ae5fe
52 changed files with 1415 additions and 195 deletions
+4
View File
@@ -466,6 +466,10 @@ ElSE()
else()
add_definitions(-Wno-maybe-uninitialized)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0))
# OpenCascade spews a lot of deprecated-copy warnings
add_definitions(-Wno-deprecated-copy)
endif()
# -fPIC is not relevant on Windows and creates pointless warnings
if (UNIX)
add_definitions(-fPIC)
+1 -1
View File
@@ -336,7 +336,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
`None`."""
if not os.path.exists(target_dir):
logger.info("cloning '%s' into '%s'" % (clone_url, target_dir))
run([git, "clone", clone_url, target_dir])
run([git, "clone", "--recursive", clone_url, target_dir])
else:
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
+3 -3
View File
@@ -52,10 +52,10 @@ endif
# Provides IfcOpenShell Python functionality
ifeq ($(PYVERSION), py37)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-517b819-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-37-v0.6.0-ff7219b-$(PLATFORM)64.zip
endif
ifeq ($(PYVERSION), py39)
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-517b819-$(PLATFORM)64.zip
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-39-v0.6.0-ff7219b-$(PLATFORM)64.zip
endif
cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -142,7 +142,7 @@ endif
cd dist/working && unzip v0.6.0.zip
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api dist/blenderbim/libs/site/packages/ifcopenshell/api
cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
# Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality
@@ -25,6 +25,7 @@ if bpy is not None:
"geometry": None,
"cobie": None,
"sequence": None,
"cost": None,
"group": None,
"structural": None,
"material": None,
+1 -1
View File
@@ -90,7 +90,7 @@ class IfcExporter:
for guid in to_delete:
product = self.file.by_id(guid)
IfcStore.unlink_element(product)
ifcopenshell.api.run("remove_product", self.file, **{"product": product})
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
def sync_edited_objects(self):
for obj_name in IfcStore.edited_objs.copy():
+5 -1
View File
@@ -414,7 +414,11 @@ class IfcImporter:
self.exclude_elements |= self.native_elements
def is_native(self, element):
if not element.Representation or not element.Representation.Representations or element.HasOpenings:
if (
not element.Representation
or not element.Representation.Representations
or getattr(element, "HasOpenings", None)
):
return
representations = self.get_transformed_body_representations(element.Representation.Representations)
@@ -205,7 +205,8 @@ class Helper:
for edge in bm.edges:
edge_vector = edge.verts[1].co - edge.verts[0].co
unshared_verts = set(edge.verts) - face_verts_set
if len(unshared_verts) == 1 and not (edge_vector.angle(profile_face.normal) - pi / 2 < 0.001):
angle_to_normal = edge_vector.angle(profile_face.normal)
if len(unshared_verts) == 1 and (angle_to_normal < 0.001 or angle_to_normal - pi < 0.001):
if unshared_verts.pop() == edge.verts[1]:
return [edge.verts[0].index, edge.verts[1].index]
return [edge.verts[1].index, edge.verts[0].index]
@@ -398,7 +398,7 @@ class UpdateMeshRepresentation(bpy.types.Operator):
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id())
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name)
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
@@ -5,8 +5,10 @@ classes = (
operator.EnableEditingGeoreferencing,
operator.DisableEditingGeoreferencing,
operator.EditGeoreferencing,
operator.SetNorthOffset,
operator.GetNorthOffset,
operator.SetIfcGridNorth,
operator.SetBlenderGridNorth,
operator.SetIfcTrueNorth,
operator.SetBlenderTrueNorth,
operator.RemoveGeoreferencing,
operator.AddGeoreferencing,
operator.ConvertLocalToGlobal,
@@ -17,27 +17,6 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
while len(props.map_conversion) > 0:
props.map_conversion.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.map_conversion.add()
new.name = attribute.name()
new.is_null = Data.map_conversion[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else Data.map_conversion[attribute.name()]
elif data_type == "float":
new.float_value = 0.0 if new.is_null else Data.map_conversion[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else Data.map_conversion[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else Data.map_conversion[attribute.name()]
while len(props.projected_crs) > 0:
props.projected_crs.remove(0)
@@ -69,6 +48,27 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
elif props.map_unit_type == "IfcConversionBasedUnit":
props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"]
while len(props.map_conversion) > 0:
props.map_conversion.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or data_type == "select":
continue
print(attribute.name(), data_type)
new = props.map_conversion.add()
new.name = attribute.name()
new.is_null = Data.map_conversion[attribute.name()] is None
new.is_optional = attribute.optional()
# Enforce a string data type to prevent data loss in singpe-precision Blender props
new.data_type = "string"
new.string_value = "" if new.is_null else str(Data.map_conversion[attribute.name()])
props.has_true_north = bool(Data.true_north)
if Data.true_north:
props.true_north_abscissa = str(Data.true_north[0])
props.true_north_ordinate = str(Data.true_north[1])
props.is_editing = True
return {"FINISHED"}
@@ -91,23 +91,6 @@ class EditGeoreferencing(bpy.types.Operator):
self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties
map_conversion = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
blender_attribute = props.map_conversion.get(attribute.name())
if blender_attribute.is_null:
map_conversion[attribute.name()] = None
elif blender_attribute.data_type == "string":
map_conversion[attribute.name()] = blender_attribute.string_value
elif blender_attribute.data_type == "float":
map_conversion[attribute.name()] = blender_attribute.float_value
elif blender_attribute.data_type == "integer":
map_conversion[attribute.name()] = blender_attribute.int_value
elif blender_attribute.data_type == "boolean":
map_conversion[attribute.name()] = blender_attribute.bool_value
projected_crs = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
@@ -129,38 +112,87 @@ class EditGeoreferencing(bpy.types.Operator):
if not props.is_map_unit_null:
map_unit = props.map_unit_si if props.map_unit_type == "IfcSIUnit" else props.map_unit_imperial
map_conversion = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcMapConversion").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity" or data_type == "select":
continue
blender_attribute = props.map_conversion.get(attribute.name())
if blender_attribute.is_null:
map_conversion[attribute.name()] = None
elif blender_attribute.data_type == "string":
# We store our floats as string to prevent single precision data loss
map_conversion[attribute.name()] = float(blender_attribute.string_value)
true_north = None
if props.has_true_north:
try:
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)]
except:
pass
ifcopenshell.api.run(
"georeference.edit_georeferencing",
self.file,
**{"map_conversion": map_conversion, "projected_crs": projected_crs, "map_unit": map_unit}
**{
"map_conversion": map_conversion,
"projected_crs": projected_crs,
"map_unit": map_unit,
"true_north": true_north,
}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"}
class SetNorthOffset(bpy.types.Operator):
bl_idname = "bim.set_north_offset"
bl_label = "Set North Offset"
class SetBlenderGridNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_grid_north"
bl_label = "Set Blender Grid North"
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.xy2angle(
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value,
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value,
ifcopenshell.util.geolocation.xaxis2angle(
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value),
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value),
)
)
return {"FINISHED"}
class GetNorthOffset(bpy.types.Operator):
bl_idname = "bim.get_north_offset"
bl_label = "Get North Offset"
class SetIfcGridNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_grid_north"
bl_label = "Set IFC Grid North"
def execute(self, context):
x_angle = -context.scene.sun_pos_properties.north_offset
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value = cos(x_angle)
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value = sin(x_angle)
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle))
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle))
return {"FINISHED"}
class SetBlenderTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_blender_true_north"
bl_label = "Set Blender True North"
def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.yaxis2angle(
float(context.scene.BIMGeoreferenceProperties.true_north_abscissa),
float(context.scene.BIMGeoreferenceProperties.true_north_ordinate),
)
)
return {"FINISHED"}
class SetIfcTrueNorth(bpy.types.Operator):
bl_idname = "bim.set_ifc_true_north"
bl_label = "Set IFC True North"
def execute(self, context):
y_angle = -context.scene.sun_pos_properties.north_offset + radians(90)
context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(cos(y_angle))
context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(sin(y_angle))
return {"FINISHED"}
@@ -46,3 +46,6 @@ class BIMGeoreferenceProperties(PropertyGroup):
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0")
blender_x_axis_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0")
has_true_north: BoolProperty(name="Has True North", default=True)
true_north_abscissa: StringProperty(name="True North Abscissa")
true_north_ordinate: StringProperty(name="True North Ordinate")
@@ -1,7 +1,9 @@
import ifcopenshell.util.geolocation
from bpy.types import Panel
from ifcopenshell.api.georeference.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing"
bl_idname = "BIM_PT_gis"
@@ -15,6 +17,8 @@ class BIM_PT_gis(Panel):
return IfcStore.get_file()
def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties
if not Data.is_loaded:
Data.load(IfcStore.get_file())
@@ -26,30 +30,10 @@ class BIM_PT_gis(Panel):
def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.edit_georeferencing", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_georeferencing", icon="X", text="")
for attribute in props.map_conversion:
if attribute.name == "XAxisAbscissa" and hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.get_north_offset", text="Set IFC North")
row.operator("bim.set_north_offset", text="Set Blender North")
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
for attribute in props.projected_crs:
row = self.layout.row(align=True)
if attribute.data_type == "string":
@@ -70,10 +54,43 @@ class BIM_PT_gis(Panel):
row.prop(props, "map_unit_imperial", text="")
row.prop(props, "is_map_unit_null", icon="RADIOBUT_OFF" if props.is_map_unit_null else "RADIOBUT_ON", text="")
row = self.layout.row()
row.label(text="Map Conversion", icon="GRID")
for attribute in props.map_conversion:
if attribute.name == "Scale" and hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.set_ifc_grid_north", text="Set IFC North")
row.operator("bim.set_blender_grid_north", text="Set Blender North")
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
row = self.layout.row()
row.prop(props, "has_true_north")
row = self.layout.row()
row.prop(props, "true_north_abscissa")
row = self.layout.row()
row.prop(props, "true_north_ordinate")
if hasattr(context.scene, "sun_pos_properties"):
row = self.layout.row(align=True)
row.operator("bim.set_ifc_true_north", text="Set IFC North")
row.operator("bim.set_blender_true_north", text="Set Blender North")
def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties
if not Data.map_conversion and IfcStore.get_file().schema != "IFC2X3":
if not Data.projected_crs and IfcStore.get_file().schema != "IFC2X3":
row = self.layout.row(align=True)
row.label(text="Not Georeferenced")
row.operator("bim.add_georeferencing", icon="ADD", text="")
@@ -100,26 +117,26 @@ class BIM_PT_gis(Panel):
row = self.layout.row(align=True)
row.label(text="XAxisOrdinate")
row.label(text=props.blender_x_axis_ordinate)
row.label(text="Derived Grid North")
row.label(
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
),
3,
)
)
)
elif IfcStore.get_file().schema == "IFC2X3":
row = self.layout.row()
row.label(text="Not Georeferenced")
if Data.map_conversion:
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in Data.map_conversion.items():
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or not value:
continue
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
if Data.projected_crs:
row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD")
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
row.operator("bim.remove_georeferencing", icon="X", text="")
for key, value in Data.projected_crs.items():
if key == "id" or key == "type" or not value:
@@ -132,6 +149,40 @@ class BIM_PT_gis(Panel):
row.label(text=key)
row.label(text=str(value))
if Data.map_conversion:
row = self.layout.row(align=True)
row.label(text="Map Conversion", icon="GRID")
for key, value in Data.map_conversion.items():
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or value is None:
continue
row = self.layout.row(align=True)
row.label(text=key)
row.label(text=str(value))
if key == "XAxisOrdinate":
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(
text=str(
round(
ifcopenshell.util.geolocation.xaxis2angle(
Data.map_conversion["XAxisAbscissa"], Data.map_conversion["XAxisOrdinate"]
),
3,
)
)
)
if Data.true_north:
row = self.layout.row()
row.label(text="True North", icon="LIGHT_SUN")
row = self.layout.row(align=True)
row.label(text="Vector")
row.label(text=str(Data.true_north[0:2])[1:-1])
row = self.layout.row(align=True)
row.label(text="Derived Angle")
row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*Data.true_north[0:2]), 3)))
class BIM_PT_gis_utilities(Panel):
bl_idname = "BIM_PT_gis_utilities"
@@ -47,7 +47,7 @@ class EnableEditingLayer(bpy.types.Operator):
for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
if data_type == "entity" or data_type == "select":
continue
new = props.layer_attributes.add()
new.name = attribute.name()
@@ -8,6 +8,9 @@ classes = (
operator.UnassignMaterial,
operator.AddConstituent,
operator.RemoveConstituent,
operator.AddProfile,
operator.RemoveProfile,
operator.AssignParameterizedProfile,
operator.AddLayer,
operator.RemoveLayer,
operator.ReorderMaterialSetItem,
@@ -1,8 +1,36 @@
import bpy
import json
import ifcopenshell.api
import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
class AssignParameterizedProfile(bpy.types.Operator):
bl_idname = "bim.assign_parameterized_profile"
bl_label = "Assign Parameterized Profile"
ifc_class: bpy.props.StringProperty()
material_profile: bpy.props.IntProperty()
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
profile = ifcopenshell.api.run(
"profile.add_parameterized_profile",
self.file,
**{"ifc_class": self.ifc_class},
)
ifcopenshell.api.run(
"material.assign_profile",
self.file,
**{"material_profile": self.file.by_id(self.material_profile), "profile": profile}
)
Data.load_profiles()
ProfileData.load(self.file)
bpy.ops.bim.enable_editing_material_set_item(obj=obj.name, material_set_item=self.material_profile)
return {"FINISHED"}
class AddMaterial(bpy.types.Operator):
@@ -115,6 +143,43 @@ class RemoveConstituent(bpy.types.Operator):
return {"FINISHED"}
class AddProfile(bpy.types.Operator):
bl_idname = "bim.add_profile"
bl_label = "Add Profile"
obj: bpy.props.StringProperty()
profile_set: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.add_profile",
self.file,
**{
"profile_set": self.file.by_id(self.profile_set),
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material)),
},
)
Data.load_profiles()
return {"FINISHED"}
class RemoveProfile(bpy.types.Operator):
bl_idname = "bim.remove_profile"
bl_label = "Remove Profile"
obj: bpy.props.StringProperty()
profile: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"material.remove_profile", self.file, **{"profile": self.file.by_id(self.profile)}
)
Data.load_profiles()
return {"FINISHED"}
class AddLayer(bpy.types.Operator):
bl_idname = "bim.add_layer"
bl_label = "Add Layer"
@@ -333,8 +398,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
def execute(self, context):
self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = self.material_set_item
self.props = obj.BIMObjectMaterialProperties
self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set_item = self.file.by_id(self.material_set_item)
@@ -347,17 +412,24 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
else:
material_set_item_data = {}
props.material_set_item_material = str(material_set_item_data["Material"])
self.props.material_set_item_material = str(material_set_item_data["Material"])
while len(props.material_set_item_attributes) > 0:
props.material_set_item_attributes.remove(0)
self.load_set_item_attributes(material_set_item, material_set_item_data)
if material_set_item.is_a("IfcMaterialProfile"):
self.load_profile_attributes(material_set_item, material_set_item_data)
return {"FINISHED"}
def load_set_item_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_attributes) > 0:
self.props.material_set_item_attributes.remove(0)
for attribute in IfcStore.get_schema().declaration_by_name(material_set_item.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in material_set_item_data:
new = props.material_set_item_attributes.add()
new = self.props.material_set_item_attributes.add()
new.name = attribute.name()
new.is_null = material_set_item_data[attribute.name()] is None
new.data_type = data_type
@@ -369,7 +441,45 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()]
return {"FINISHED"}
def load_profile_attributes(self, material_set_item, material_set_item_data):
while len(self.props.material_set_item_profile_attributes) > 0:
self.props.material_set_item_profile_attributes.remove(0)
if not material_set_item_data["Profile"]:
return
profile = self.file.by_id(material_set_item_data["Profile"])
profile_data = ProfileData.profiles[material_set_item_data["Profile"]]
for attribute in IfcStore.get_schema().declaration_by_name(profile.is_a()).all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
if attribute.name() in profile_data:
new = self.props.material_set_item_profile_attributes.add()
new.name = attribute.name()
new.is_null = profile_data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else profile_data[attribute.name()]
elif data_type == "float":
new.float_value = 0.0 if new.is_null else profile_data[attribute.name()]
elif data_type == "integer":
new.int_value = 0 if new.is_null else profile_data[attribute.name()]
elif data_type == "boolean":
new.bool_value = False if new.is_null else profile_data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if profile_data[attribute.name()]:
new.enum_value = profile_data[attribute.name()]
# Force null to be false if the attribute is mandatory because when we first assign a profile, all of
# its fields are null (which is illegal).
# TODO: find a better solution.
if not new.is_optional:
new.is_null = False
class DisableEditingMaterialSetItem(bpy.types.Operator):
@@ -431,16 +541,31 @@ class EditMaterialSetItem(bpy.types.Operator):
)
Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet":
profile_attributes = {}
for attribute in props.material_set_item_profile_attributes:
if attribute.data_type == "string":
value = attribute.string_value
elif attribute.data_type == "float":
value = attribute.float_value
elif attribute.data_type == "integer":
value = attribute.int_value
elif attribute.data_type == "boolean":
value = attribute.bool_value
elif attribute.data_type == "enum":
value = attribute.enum_value
profile_attributes[attribute.name] = None if attribute.is_null else value
ifcopenshell.api.run(
"material.edit_profile",
self.file,
**{
"profile": self.file.by_id(self.material_set_item),
"attributes": attributes,
"profile_attributes": profile_attributes,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
},
)
Data.load_profiles()
ProfileData.load(self.file)
else:
pass
@@ -1,5 +1,5 @@
import bpy
import blenderbim.bim.schema # refactor
import blenderbim.bim.schema # refactor
from ifcopenshell.api.material.data import Data
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute
@@ -17,6 +17,29 @@ from bpy.props import (
materials_enum = []
materialtypes_enum = []
profileclasses_enum = []
parameterizedprofileclasses_enum = []
def getProfileClasses(self, context):
global profileclasses_enum
if len(profileclasses_enum) == 0 and IfcStore.get_schema():
profileclasses_enum.clear()
profileclasses_enum = [
(t.name(), t.name(), "") for t in IfcStore.get_schema().declaration_by_name("IfcProfileDef").subtypes()
]
return profileclasses_enum
def getParameterizedProfileClasses(self, context):
global parameterizedprofileclasses_enum
if len(parameterizedprofileclasses_enum) == 0 and IfcStore.get_schema():
parameterizedprofileclasses_enum.clear()
parameterizedprofileclasses_enum = [
(t.name(), t.name(), "")
for t in IfcStore.get_schema().declaration_by_name("IfcParameterizedProfileDef").subtypes()
]
return parameterizedprofileclasses_enum
def getMaterials(self, context):
@@ -52,4 +75,9 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID")
material_set_item_attributes: CollectionProperty(name="Material Set Item Attributes", type=Attribute)
material_set_item_profile_attributes: CollectionProperty(name="Material Set Item Profile Attributes", type=Attribute)
material_set_item_material: EnumProperty(items=getMaterials, name="Material")
profile_classes: EnumProperty(items=getProfileClasses, name="Profile Classes")
parameterized_profile_classes: EnumProperty(
items=getParameterizedProfileClasses, name="Parameterized Profile Classes"
)
@@ -1,5 +1,6 @@
from bpy.types import Panel
from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore
@@ -46,6 +47,8 @@ class BIM_PT_object_material(Panel):
Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id)
if not ProfileData.is_loaded:
ProfileData.load(self.file)
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials:
@@ -172,6 +175,39 @@ class BIM_PT_object_material(Panel):
row.prop(attribute, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
if self.set_item_name == "profile":
self.draw_assign_profile_ui(box, item)
self.draw_editable_profile_ui(box, item)
def draw_assign_profile_ui(self, layout, item):
row = layout.row(align=True)
row.prop(self.props, "profile_classes", text="")
if self.props.profile_classes == "IfcParameterizedProfileDef":
row.prop(self.props, "parameterized_profile_classes", text="")
op = row.operator("bim.assign_parameterized_profile", icon="GREASEPENCIL" if item["Profile"] else "ADD", text="")
op.ifc_class = self.props.parameterized_profile_classes
op.material_profile = item["id"]
else:
# TODO: support non parametric profiles by showing a list of named profiles to select from, or an
# eyedropper to pick profile geometry from the scene
row.operator("bim.disable_editing_material_set_item", icon="X", text="")
def draw_editable_profile_ui(self, layout, item):
for attribute in self.props.material_set_item_profile_attributes:
row = layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "integer":
row.prop(attribute, "int_value", text=attribute.name)
elif attribute.data_type == "float":
row.prop(attribute, "float_value", text=attribute.name)
elif attribute.data_type == "boolean":
row.prop(attribute, "bool_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList":
item = Data.materials[set_item_id]
@@ -84,6 +84,8 @@ class PieAddOpening(bpy.types.Operator):
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
opening_name = obj.name
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
opening_name = obj.children[0].name
else:
opj_name = obj.name
bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
@@ -3,6 +3,7 @@ from . import ui, prop, operator
classes = (
operator.CreateProject,
operator.CreateProjectLibrary,
operator.ValidateIfcFile,
prop.BIMProjectProperties,
ui.BIM_PT_project,
@@ -53,6 +53,30 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"}
class CreateProjectLibrary(bpy.types.Operator):
bl_idname = "bim.create_project_library"
bl_label = "Create Project Library"
def execute(self, context):
self.file = IfcStore.get_file()
if self.file:
return {"FINISHED"}
IfcStore.file = ifcopenshell.api.run(
"project.create_file", **{"version": bpy.context.scene.BIMProperties.export_schema}
)
self.file = IfcStore.get_file()
if self.file.schema == "IFC2X3":
bpy.ops.bim.add_person()
bpy.ops.bim.add_organisation()
project_library = bpy.data.objects.new("My Project Library", None)
bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary")
bpy.ops.bim.assign_unit()
return {"FINISHED"}
class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file"
bl_label = "Validate IFC File"
@@ -63,3 +63,6 @@ class BIM_PT_project(Panel):
row.prop(props, "volume_unit", text="Volume Unit")
row = self.layout.row()
row.operator("bim.create_project")
if props.export_schema != "IFC2X3":
row = self.layout.row()
row.operator("bim.create_project_library")
@@ -2,21 +2,55 @@ import bpy
from . import ui, prop, operator
classes = (
operator.LoadWorkPlans,
operator.DisableWorkPlanEditingUI,
operator.AddWorkPlan,
operator.EditWorkPlan,
operator.RemoveWorkPlan,
operator.EnableEditingWorkPlan,
operator.DisableEditingWorkPlan,
operator.LoadWorkSchedules,
operator.DisableWorkScheduleEditingUI,
operator.AddWorkSchedule,
operator.EditWorkSchedule,
operator.RemoveWorkSchedule,
operator.EnableEditingWorkSchedule,
operator.DisableEditingWorkSchedule,
operator.LoadTasks,
operator.DisableTaskEditingUI,
operator.AddWorkPlan,
operator.RemoveWorkPlan,
operator.LoadWorkCalendars,
operator.DisableWorkCalendarEditingUI,
operator.AddWorkCalendar,
operator.EditWorkCalendar,
operator.RemoveWorkCalendar,
operator.EnableEditingWorkCalendar,
operator.DisableEditingWorkCalendar,
prop.WorkPlan,
prop.BIMWorkPlanProperties,
prop.WorkSchedule,
prop.BIMWorkScheduleProperties,
prop.WorkCalendar,
prop.BIMWorkCalendarProperties,
prop.Task,
prop.BIMTaskProperties,
ui.BIM_PT_work_plans,
ui.BIM_UL_work_plans,
ui.BIM_PT_work_schedules,
ui.BIM_UL_work_schedules,
ui.BIM_PT_work_calendars,
ui.BIM_UL_work_calendars,
ui.BIM_PT_tasks,
ui.BIM_UL_tasks,
)
def register():
bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties)
bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties)
bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties)
bpy.types.Scene.BIMWorkCalendarProperties = bpy.props.PointerProperty(type=prop.BIMWorkCalendarProperties)
def unregister():
del bpy.types.Scene.BIMTaskProperties
del bpy.types.Scene.BIMWorkPlanProperties
del bpy.types.Scene.BIMWorkScheduleProperties
del bpy.types.Scene.BIMWorkCalendarProperties
@@ -1,9 +1,36 @@
import bpy
import json
import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data
class LoadWorkPlans(bpy.types.Operator):
bl_idname = "bim.load_work_plans"
bl_label = "Load Work Plans"
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
while len(props.work_plans) > 0:
props.work_plans.remove(0)
for ifc_definition_id, work_plan in Data.work_plans.items():
new = props.work_plans.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_plan["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_plan()
return {"FINISHED"}
class DisableWorkPlanEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_plan_editing_ui"
bl_label = "Disable WorkPlan Editing UI"
def execute(self, context):
context.scene.BIMWorkPlanProperties.is_editing = False
return {"FINISHED"}
class AddWorkPlan(bpy.types.Operator):
bl_idname = "bim.add_work_plan"
bl_label = "Add Work Plan"
@@ -11,6 +38,33 @@ class AddWorkPlan(bpy.types.Operator):
def execute(self, context):
ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
return {"FINISHED"}
class EditWorkPlan(bpy.types.Operator):
bl_idname = "bim.edit_work_plan"
bl_label = "Edit Work Plan"
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
attributes = {}
for attribute in props.work_plan_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_work_plan",
self.file,
**{"work_plan": self.file.by_id(props.active_work_plan_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
return {"FINISHED"}
@@ -20,10 +74,290 @@ class RemoveWorkPlan(bpy.types.Operator):
work_plan: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run("sequence.remove_work_plan", self.file, **{"work_plan": self.file.by_id(self.work_plan)})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_plans()
return {"FINISHED"}
class EnableEditingWorkPlan(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_plan"
bl_label = "Enable Editing Work Plan"
work_plan: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkPlanProperties
while len(props.work_plan_attributes) > 0:
props.work_plan_attributes.remove(0)
data = Data.work_plans[self.work_plan]
for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkPlan").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.work_plan_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if attribute.name() in ["CreationDate", "StartTime", "FinishTime"]:
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_work_plan_id = self.work_plan
return {"FINISHED"}
class DisableEditingWorkPlan(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_plan"
bl_label = "Disable Editing Work Plan"
def execute(self, context):
context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
return {"FINISHED"}
class LoadWorkSchedules(bpy.types.Operator):
bl_idname = "bim.load_work_schedules"
bl_label = "Load Work Schedules"
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
while len(props.work_schedules) > 0:
props.work_schedules.remove(0)
for ifc_definition_id, work_schedule in Data.work_schedules.items():
new = props.work_schedules.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_schedule["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_schedule()
return {"FINISHED"}
class DisableWorkScheduleEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_schedule_editing_ui"
bl_label = "Disable WorkSchedule Editing UI"
def execute(self, context):
context.scene.BIMWorkScheduleProperties.is_editing = False
return {"FINISHED"}
class AddWorkSchedule(bpy.types.Operator):
bl_idname = "bim.add_work_schedule"
bl_label = "Add Work Schedule"
def execute(self, context):
ifcopenshell.api.run("sequence.add_work_schedule", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_schedules()
return {"FINISHED"}
class EditWorkSchedule(bpy.types.Operator):
bl_idname = "bim.edit_work_schedule"
bl_label = "Edit Work Schedule"
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
attributes = {}
for attribute in props.work_schedule_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_plan", IfcStore.get_file(), work_plan=IfcStore.get_file().by_id(self.work_plan)
"sequence.edit_work_schedule",
self.file,
**{"work_schedule": self.file.by_id(props.active_work_schedule_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_schedules()
return {"FINISHED"}
class RemoveWorkSchedule(bpy.types.Operator):
bl_idname = "bim.remove_work_schedule"
bl_label = "Remove Work Schedule"
work_schedule: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_schedule", self.file, **{"work_schedule": self.file.by_id(self.work_schedule)}
)
Data.load(self.file)
bpy.ops.bim.load_work_schedules()
return {"FINISHED"}
class EnableEditingWorkSchedule(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_schedule"
bl_label = "Enable Editing Work Schedule"
work_schedule: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkScheduleProperties
while len(props.work_schedule_attributes) > 0:
props.work_schedule_attributes.remove(0)
data = Data.work_schedules[self.work_schedule]
for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkSchedule").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.work_schedule_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if attribute.name() in ["CreationDate", "StartTime", "FinishTime"]:
new.string_value = "" if new.is_null else data[attribute.name()].isoformat()
elif data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_work_schedule_id = self.work_schedule
return {"FINISHED"}
class DisableEditingWorkSchedule(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_schedule"
bl_label = "Disable Editing Work Schedule"
def execute(self, context):
context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
return {"FINISHED"}
class LoadWorkCalendars(bpy.types.Operator):
bl_idname = "bim.load_work_calendars"
bl_label = "Load Work Calendars"
def execute(self, context):
props = context.scene.BIMWorkCalendarProperties
while len(props.work_calendars) > 0:
props.work_calendars.remove(0)
for ifc_definition_id, work_calendar in Data.work_calendars.items():
new = props.work_calendars.add()
new.ifc_definition_id = ifc_definition_id
new.name = work_calendar["Name"] or "Unnamed"
props.is_editing = True
bpy.ops.bim.disable_editing_work_calendar()
return {"FINISHED"}
class DisableWorkCalendarEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_work_calendar_editing_ui"
bl_label = "Disable WorkCalendar Editing UI"
def execute(self, context):
context.scene.BIMWorkCalendarProperties.is_editing = False
return {"FINISHED"}
class AddWorkCalendar(bpy.types.Operator):
bl_idname = "bim.add_work_calendar"
bl_label = "Add Work Calendar"
def execute(self, context):
ifcopenshell.api.run("sequence.add_work_calendar", IfcStore.get_file())
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_calendars()
return {"FINISHED"}
class EditWorkCalendar(bpy.types.Operator):
bl_idname = "bim.edit_work_calendar"
bl_label = "Edit Work Calendar"
def execute(self, context):
props = context.scene.BIMWorkCalendarProperties
attributes = {}
for attribute in props.work_calendar_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
if attribute.data_type == "string":
attributes[attribute.name] = attribute.string_value
elif attribute.data_type == "enum":
attributes[attribute.name] = attribute.enum_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.edit_work_calendar",
self.file,
**{"work_calendar": self.file.by_id(props.active_work_calendar_id), "attributes": attributes}
)
Data.load(IfcStore.get_file())
bpy.ops.bim.load_work_calendars()
return {"FINISHED"}
class RemoveWorkCalendar(bpy.types.Operator):
bl_idname = "bim.remove_work_calendar"
bl_label = "Remove Work Plan"
work_calendar: bpy.props.IntProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"sequence.remove_work_calendar", self.file, **{"work_calendar": self.file.by_id(self.work_calendar)}
)
Data.load(self.file)
bpy.ops.bim.load_work_calendars()
return {"FINISHED"}
class EnableEditingWorkCalendar(bpy.types.Operator):
bl_idname = "bim.enable_editing_work_calendar"
bl_label = "Enable Editing Work Plan"
work_calendar: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMWorkCalendarProperties
while len(props.work_calendar_attributes) > 0:
props.work_calendar_attributes.remove(0)
data = Data.work_calendars[self.work_calendar]
for attribute in IfcStore.get_schema().declaration_by_name("IfcWorkCalendar").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity":
continue
new = props.work_calendar_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.data_type = data_type
if data_type == "string":
new.string_value = "" if new.is_null else data[attribute.name()]
elif data_type == "enum":
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_work_calendar_id = self.work_calendar
return {"FINISHED"}
class DisableEditingWorkCalendar(bpy.types.Operator):
bl_idname = "bim.disable_editing_work_calendar"
bl_label = "Disable Editing Work Calendar"
def execute(self, context):
context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0
return {"FINISHED"}
@@ -23,3 +23,42 @@ class BIMTaskProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
tasks: CollectionProperty(name="Tasks", type=Task)
active_task_index: IntProperty(name="Active Task Index")
class WorkPlan(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMWorkPlanProperties(PropertyGroup):
work_plan_attributes: CollectionProperty(name="Work Plan Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
work_plans: CollectionProperty(name="Work Plans", type=WorkPlan)
active_work_plan_index: IntProperty(name="Active Work Plan Index")
active_work_plan_id: IntProperty(name="Active Work Plan Id")
class WorkSchedule(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMWorkScheduleProperties(PropertyGroup):
work_schedule_attributes: CollectionProperty(name="Work Schedule Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
work_schedules: CollectionProperty(name="Work Schedules", type=WorkSchedule)
active_work_schedule_index: IntProperty(name="Active Work Schedules Index")
active_work_schedule_id: IntProperty(name="Active Work Schedules Id")
class WorkCalendar(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMWorkCalendarProperties(PropertyGroup):
work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False)
work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar)
active_work_calendar_index: IntProperty(name="Active Work Calendar Index")
active_work_calendar_id: IntProperty(name="Active Work Calendar Id")
@@ -18,15 +18,181 @@ class BIM_PT_work_plans(Panel):
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkPlanProperties
row = self.layout.row(align=True)
row.label(text="{} Work Plans Found".format(len(Data.work_plans)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_plan", text="", icon="ADD")
row.operator("bim.disable_work_plan_editing_ui", text="", icon="CHECKMARK")
else:
row.operator("bim.load_work_plans", text="", icon="GREASEPENCIL")
row = self.layout.row()
row.operator("bim.add_work_plan", icon="ADD")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_work_plans",
"",
self.props,
"work_plans",
self.props,
"active_work_plan_index",
)
for work_plan_id, work_plan in Data.work_plans.items():
if self.props.active_work_plan_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.work_plan_attributes:
row = self.layout.row(align=True)
row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT")
row.operator("bim.add_work_plan", text="", icon="GREASEPENCIL")
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_work_plans(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkPlanProperties.active_work_plan_id == item.ifc_definition_id:
row.operator("bim.edit_work_plan", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_plan", text="", icon="X")
elif context.scene.BIMWorkPlanProperties.active_work_plan_id:
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_plan", text="", icon="GREASEPENCIL")
op.work_plan = item.ifc_definition_id
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = item.ifc_definition_id
class BIM_PT_work_schedules(Panel):
bl_label = "IFC Work Schedules"
bl_idname = "BIM_PT_work_schedules"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkScheduleProperties
row = self.layout.row(align=True)
row.label(text="{} Work Schedules Found".format(len(Data.work_schedules)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_schedule", text="", icon="ADD")
row.operator("bim.disable_work_schedule_editing_ui", text="", icon="CHECKMARK")
else:
row.operator("bim.load_work_schedules", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_work_schedules",
"",
self.props,
"work_schedules",
self.props,
"active_work_schedule_index",
)
if self.props.active_work_schedule_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.work_schedule_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_work_schedules(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkScheduleProperties.active_work_schedule_id == item.ifc_definition_id:
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_schedule", text="", icon="X")
elif context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL")
op.work_schedule = item.ifc_definition_id
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = item.ifc_definition_id
class BIM_PT_work_calendars(Panel):
bl_label = "IFC Work Calendars"
bl_idname = "BIM_PT_work_calendars"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load(IfcStore.get_file())
self.props = context.scene.BIMWorkCalendarProperties
row = self.layout.row(align=True)
row.label(text="{} Work Calendar Found".format(len(Data.work_calendars)), icon="TEXT")
if self.props.is_editing:
row.operator("bim.add_work_calendar", text="", icon="ADD")
row.operator("bim.disable_work_calendar_editing_ui", text="", icon="CHECKMARK")
else:
row.operator("bim.load_work_calendars", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_work_calendars",
"",
self.props,
"work_calendars",
self.props,
"active_work_calendar_index",
)
if self.props.active_work_calendar_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.work_calendar_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_UL_work_calendars(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMWorkCalendarProperties.active_work_calendar_id == item.ifc_definition_id:
row.operator("bim.edit_work_calendar", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_work_calendar", text="", icon="X")
elif context.scene.BIMWorkCalendarProperties.active_work_calendar_id:
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_work_calendar", text="", icon="GREASEPENCIL")
op.work_calendar = item.ifc_definition_id
row.operator("bim.remove_work_calendar", text="", icon="X").work_calendar = item.ifc_definition_id
class BIM_PT_tasks(Panel):
@@ -27,6 +27,8 @@ class BIM_PT_voids(Panel):
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
op.opening = obj.name
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
op.opening = obj.children[0].name
else:
op.obj = obj.name
+4 -1
View File
@@ -1,3 +1,6 @@
import ifcopenshell
import ezdxf
class Dxf2Ifc:
def execute(self):
self.create_ifc_file()
@@ -14,7 +17,7 @@ class Dxf2Ifc:
[
self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop(
[self.file.createIfcCartesianPoint((v.dxf.location)) for v in face[0:3]]
[self.file.createIfcCartesianPoint((face[index].dxf.location)) for index in range(len(face) -1)]
),
True,
)
@@ -1,6 +1,8 @@
import math
import numpy as np
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.geolocation
from behave import step
from bimtester import util
@@ -171,7 +173,7 @@ def step_impl(context, number):
return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number)
abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False)
ordinate = check_ifc4_geolocation("IfcMapConversion", "XAxisOrdinate", should_assert=False)
actual_value = round(ifcopenshell.util.geolocation.xy2angle(abscissa, ordinate), 3)
actual_value = round(ifcopenshell.util.geolocation.xaxis2angle(abscissa, ordinate), 3)
value = round(number, 3)
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value)
@@ -186,6 +188,26 @@ def step_impl(context, number):
check_ifc4_geolocation("IfcMapConversion", "Scale", number)
@step(u'The model must be rotated clockwise by "{number}" for true north to point up')
def step_impl(context, number):
number = util.assert_number(number)
project = IfcStore.file.by_type("IfcProject")[0]
for c in project.RepresentationContexts:
if c.TrueNorth:
actual_value = round(
ifcopenshell.util.geolocation.yaxis2angle(
c.TrueNorth.DirectionRatios[0], c.TrueNorth.DirectionRatios[1]
),
3,
)
value = round(number, 3)
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(
value, actual_value
)
return
assert False, _("True north is not defined in the file")
@step(u'The site "{guid}" has a longitude of "{number}"')
def step_impl(context, guid, number):
number = util.assert_number(number)
@@ -212,3 +234,14 @@ def step_impl(context, guid, number):
site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite")
util.assert_attribute(site, "RefElevation", number)
@step(u'The site "{guid}" must be coincident with the project origin')
def step_impl(context, guid):
site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite")
if not site.ObjectPlacement:
assert False, _("The site has no object placement")
site_placement = ifcopenshell.util.placement.get_local_placement(site.ObjectPlacement)[:,3][0:3]
origin = np.array([0, 0, 0])
assert np.allclose(origin, site_placement), _('The site location is at "{}" instead of "{}"')
+8 -3
View File
@@ -507,10 +507,15 @@ public:
virtual bool convert_placement(IfcUtil::IfcBaseClass* item, gp_Trsf& trsf) {
if (item->as<IfcSchema::IfcObjectPlacement>()) {
return convert(item->as<IfcSchema::IfcObjectPlacement>(), trsf);
} else {
return false;
try {
return convert(item->as<IfcSchema::IfcObjectPlacement>(), trsf);
} catch (std::exception& e) {
Logger::Error(e, item);
} catch (...) {
Logger::Error("Failed processing placement", item);
}
}
return false;
}
};
@@ -36,9 +36,9 @@ class Usecase:
self.file.remove(decomposes)
if is_decomposed_by:
related_objects = list(is_decomposed_by.RelatedObjects)
related_objects.append(self.settings["product"])
is_decomposed_by.RelatedObjects = related_objects
related_objects = set(is_decomposed_by.RelatedObjects)
related_objects.add(self.settings["product"])
is_decomposed_by.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by})
else:
is_decomposed_by = self.file.create_entity(
@@ -50,3 +50,4 @@ class Usecase:
"RelatingObject": self.settings["relating_object"],
}
)
return is_decomposed_by
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -21,7 +21,12 @@ class Usecase:
context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin)
else:
context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin)
project = self.file.by_type("IfcProject")[0]
if self.file.schema == "IFC2X3":
project = self.file.by_type("IfcProject")[0]
else:
project = self.file.by_type("IfcContext")[0]
if project.RepresentationContexts:
contexts = list(project.RepresentationContexts)
else:
@@ -2,12 +2,14 @@ class Data:
is_loaded = False
map_conversion = {}
projected_crs = {}
true_north = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.map_conversion = {}
cls.projected_crs = {}
cls.true_north = None
@classmethod
def load(cls, file):
@@ -15,6 +17,7 @@ class Data:
return
cls.map_conversion = {}
cls.projected_crs = {}
cls.true_north = {}
if file.schema == "IFC2X3":
return
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
@@ -28,4 +31,9 @@ class Data:
if cls.projected_crs["MapUnit"]:
cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info()
break
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if not context.TrueNorth:
continue
cls.true_north = context.TrueNorth.DirectionRatios
break
cls.is_loaded = True
@@ -7,6 +7,7 @@ class Usecase:
self.settings = {
"map_conversion": {},
"projected_crs": {},
"true_north": [],
"map_unit": "",
}
for key, value in settings.items():
@@ -21,6 +22,7 @@ class Usecase:
setattr(projected_crs, name, value)
self.remove_existing_map_unit(projected_crs)
self.set_map_unit(projected_crs)
self.set_true_north()
def remove_existing_map_unit(self, projected_crs):
if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1:
@@ -50,3 +52,20 @@ class Usecase:
self.settings["map_unit"],
self.file.createIfcMeasureWithUnit(value_component, si_unit),
)
def set_true_north(self):
if self.settings["true_north"] == []:
return
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
if context.TrueNorth:
if len(self.file.get_inverse(context.TrueNorth)) != 1:
context.TrueNorth = self.file.create_entity("IfcDirection")
else:
context.TrueNorth = self.file.create_entity("IfcDirection")
direction = context.TrueNorth
if self.settings["true_north"] is None:
context.TrueNorth = self.settings["true_north"]
elif context.CoordinateSpaceDimension == 2:
direction.DirectionRatios = self.settings["true_north"][0:2]
else:
direction.DirectionRatios = self.settings["true_north"][0:2] + [0.0]
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -4,6 +4,7 @@ class Usecase():
self.settings = {
"profile": None,
"attributes": {},
"profile_attributes": {},
"material": None
}
for key, value in settings.items():
@@ -13,3 +14,5 @@ class Usecase():
for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value)
self.settings["profile"].Material = self.settings["material"]
for name, value in self.settings["profile_attributes"].items():
setattr(self.settings["profile"].Profile, name, value)
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase:
def __init__(self, file, **settings):
self.file = file
@@ -9,4 +9,7 @@ class Usecase:
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcOrganization", **self.settings)
if self.file.schema == "IFC2X3":
self.settings["Id"] = self.settings["Identification"]
del self.settings["Identification"]
return self.file.create_entity("IfcOrganization", **self.settings)
@@ -10,4 +10,7 @@ class Usecase:
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcPerson", **self.settings)
if self.file.schema == "IFC2X3":
self.settings["Id"] = self.settings["Identification"]
del self.settings["Identification"]
return self.file.create_entity("IfcPerson", **self.settings)
@@ -27,7 +27,7 @@ class Usecase:
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 = list(self.settings["product"].HasPropertySets or [])
has_property_sets.append(pset)
self.settings["product"].HasPropertySets = has_property_sets
elif self.settings["product"].is_a("IfcMaterialDefinition"):
@@ -18,9 +18,9 @@ class Usecase:
elif self.settings["product"].is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []]
for representation in representations:
ifcopenshell.api.run("owner.unassign_representation",
ifcopenshell.api.run("geometry.unassign_representation",
self.file, **{"product": self.settings["product"], "representation": representation}
)
ifcopenshell.api.run("owner.remove_representation", self.file, **{"representation": representation})
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
# TODO: remove object placement and other relationships
self.file.remove(self.settings["product"])
@@ -25,18 +25,7 @@ class Usecase:
work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcDateTime")
context = self.file.by_type("IfcContext")[0]
if context.Declares:
rel_declares = context.Declares[0]
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel_declares)
else:
rel_declares = self.file.create_entity("IfcRelDeclares", **{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatingContext": context
})
related_definitions = list(rel_declares.RelatedDefinitions or [])
related_definitions.append(work_plan)
rel_declares.RelatedDefinitions = related_definitions
ifcopenshell.api.run(
"project.assign_declaration", self.file, definition=work_plan, relating_context=context
)
return work_plan
@@ -4,19 +4,32 @@ import ifcopenshell.util.date
class Data:
is_loaded = False
work_plans = {}
work_schedules = {}
tasks = {}
@classmethod
def purge(cls):
cls.is_loaded = False
cls.work_plans = {}
cls.work_schedules = {}
cls.work_calendars = {}
cls.tasks = {}
@classmethod
def load(cls, file):
cls._file = file
if not cls._file:
return
cls.load_work_plans()
cls.load_work_schedules()
cls.load_work_calendars()
cls.load_tasks()
cls.is_loaded = True
@classmethod
def load_work_plans(cls):
cls.work_plans = {}
cls.tasks = {}
for work_plan in file.by_type("IfcWorkPlan"):
for work_plan in cls._file.by_type("IfcWorkPlan"):
data = work_plan.get_info()
del data["OwnerHistory"]
if data["Creators"]:
@@ -26,6 +39,32 @@ class Data:
if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
cls.work_plans[work_plan.id()] = data
for task in file.by_type("IfcTask"):
@classmethod
def load_work_schedules(cls):
cls.work_schedules = {}
for work_schedule in cls._file.by_type("IfcWorkSchedule"):
data = work_schedule.get_info()
del data["OwnerHistory"]
if data["Creators"]:
data["Creators"] = [p.id() for p in data["Creators"]]
data["CreationDate"] = ifcopenshell.util.date.ifc2datetime(data["CreationDate"])
data["StartTime"] = ifcopenshell.util.date.ifc2datetime(data["StartTime"])
if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
cls.work_schedules[work_schedule.id()] = data
@classmethod
def load_work_calendars(cls):
for work_calendar in cls._file.by_type("IfcWorkCalendar"):
data = work_calendar.get_info()
del data["OwnerHistory"]
del data["WorkingTimes"]
del data["ExceptionTimes"]
cls.work_calendars[work_calendar.id()] = data
@classmethod
def load_tasks(cls):
cls.tasks = {}
for task in cls._file.by_type("IfcTask"):
cls.tasks[task.id()] = {"Name": task.Name, "Identification": task.Identification or ""}
cls.is_loaded=True
@@ -7,8 +7,10 @@ class Usecase:
def execute(self):
# TODO: do a deep purge
if self.settings["work_plan"].HasContext:
rel_declares = self.settings["work_plan"].HasContext[0]
if len(rel_declares.RelatedDefinitions) == 1:
self.file.remove(rel_declares)
ifcopenshell.api.run(
"project.unassign_declaration",
self.file,
definition=self.settings["work_plan"],
relating_context=self.file.by_type("IfcContext")[0],
)
self.file.remove(self.settings["work_plan"])
@@ -1,5 +1,6 @@
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.placement
class Usecase:
@@ -43,3 +44,11 @@ class Usecase:
"RelatingStructure": self.settings["relating_structure"],
}
)
if getattr(self.settings["product"], "ObjectPlacement", None):
ifcopenshell.api.run(
"geometry.edit_object_placement",
self.file,
product=self.settings["product"],
matrix=ifcopenshell.util.placement.get_local_placement(self.settings["product"].ObjectPlacement),
)
@@ -33,7 +33,10 @@ class Usecase():
# TODO: handle unit rewriting, which is complicated
else:
unit_assignment = self.file.createIfcUnitAssignment([u["ifc"] for u in self.settings.values()])
self.file.by_type("IfcProject")[0].UnitsInContext = unit_assignment
if self.file.schema == "IFC2X3":
self.file.by_type("IfcProject")[0].UnitsInContext = unit_assignment
else:
self.file.by_type("IfcContext")[0].UnitsInContext = unit_assignment
return unit_assignment
def create_metric_unit(self, unit_type, data):
+77 -21
View File
@@ -64,7 +64,11 @@ class facet(metaclass=meta_facet):
yield k, getattr(self, k)
def __str__(self):
return self.message % dict(list(self))
di = dict(list(self))
for k, v in di.items():
if isinstance(v, str) and not len(v):
di[k] = "not specified"
return self.message % di
class entity(facet):
@@ -88,19 +92,19 @@ class classification(facet):
"""
parameters = ["system", "value"]
message = "a classification reference to '%(value)s' from '%(system)s'"
message = "a classification reference '%(value)s' from '%(system)s'"
def __call__(self, inst, logger):
refs = []
for association in inst.HasAssociations:
if association.is_a("IfcRelAssociatesClassification"):
cref = association.RelatingClassification
refs.append((cref.ReferencedSource, cref.Name))
refs.append((cref.ReferencedSource.Name, cref.ItemReference))
return facet_evaluation(
(self.system, self.value) in refs,
# @todo
"",
"[classification_eval_todo]",
)
@@ -109,17 +113,19 @@ class property(facet):
The IDS property facet implenented using `ifcopenshell.util.element`
"""
parameters = ["property", "propertyset", "value"]
message = "a property '%(property)s' in '%(propertyset)s' with value '%(value)s'"
parameters = ["name", "propertyset", "value"]
# import pdb;pdb.set_trace()
message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'"
def __call__(self, inst, logger):
props = ifcopenshell.util.element.get_psets(inst)
pset = props.get(self.propertyset)
val = pset.get(self.property) if pset else None
val = pset.get(self.name) if pset else None
logger.debug("Testing %s == %s", val, self.value)
di = {
"property": self.property,
"name": self.name,
"propertyset": self.propertyset,
"value": val,
}
@@ -128,13 +134,38 @@ class property(facet):
msg = self.message % di
else:
if pset:
msg = "a set '%(propertyset)s', but no property '%(property)'" % di
msg = "a set '%(propertyset)s', but no property '%(name)'" % di
else:
msg = "no set '%(propertyset)s'" % di
return facet_evaluation(val == self.value, msg)
class material(facet):
"""
The IDS material facet
"""
parameters = ["name", "value"]
message = "a material '%(name)s with value '%(value)s'"
def __call__(self, inst, logger):
material_relations = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")]
names = []
for rel in material_relations:
if rel.RelatingMaterial.is_a() == "IfcMaterialLayerSetUsage":
layers = rel.RelatingMaterial.ForLayerSet.MaterialLayers
names = [layer.Material.Name for layer in layers]
elif rel.RelatingMaterial.is_a() == "IfcMaterial":
names.append(rel.RelatingMaterial.Name)
return facet_evaluation(
0,
# @todo
"[material_eval_todo]",
)
class boolean_logic:
"""
Boolean conjunction over a collection of functions
@@ -166,17 +197,39 @@ class restriction:
"""
def __init__(self, node):
self.options = [
n.getAttribute("value")
for n in node.childNodes
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration")
]
self.restriction_on = node.getAttribute("base")
self.options = []
self.type = []
for n in node.childNodes:
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"):
self.options.append(n.getAttribute("value"))
self.type = "enumeration"
elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")):
self.options.append(n.getAttribute("value"))
self.type = "bounds"
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"):
self.options.append(n.getAttribute("value"))
self.type = "length"
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"):
self.options.append(n.getAttribute("value"))
self.type = "pattern"
# "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
def __eq__(self, other):
return other in self.options
def __repr__(self):
return " or ".join(self.options)
if self.type == "enumeration":
return " or ".join(self.options)
elif self.type == "bounds":
self.options.sort()
return "of type %s, having a value between %s and %s" % (self.restriction_on, self.options[0], self.options[1])
elif self.type == "length":
return "of type %s with a length of %s" % (self.restriction_on, self.options[0])
elif self.type == "pattern":
return "of type %s respecting pattern %s" % (self.restriction_on, self.options[0])
class specification:
@@ -202,10 +255,11 @@ class specification:
def __call__(self, inst, logger):
if self.applicabiliy(inst, logger):
valid = self.requirements(inst, logger)
if valid:
logger.info(str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant")
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is compliant"})
else:
logger.error(str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant")
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n%s has" % inst + " " + str(valid) + " so is not compliant"})
def __str__(self):
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__
@@ -229,15 +283,17 @@ class ids:
for spec in self.specifications:
for elem in ifc_file.by_type("IfcObject"):
spec(elem, logger)
if __name__ == "__main__":
import sys
import sys, os
import logging
import ifcopenshell
filename = os.path.join(os.getcwd(), "ids.txt")
logger = logging.getLogger("IDS")
logging.basicConfig(level=logging.INFO, format="%(message)s")
logging.basicConfig(filename=filename, level=logging.INFO, format="%(message)s")
logging.FileHandler(filename, mode='w')
ids_file = ids(sys.argv[1])
ifc_file = ifcopenshell.open(sys.argv[2])
@@ -94,6 +94,16 @@ def global2local(matrix, eastings, northings, orthogonal_height, x_axis_abscissa
)
# Used for converting the X and Y vectors of the X Axis in IFC geolocation
def xy2angle(x, y):
# Used for converting the X and Y vectors of the X Axis in IFC grid north geolocation
def xaxis2angle(x, y):
return math.degrees(math.atan2(y, x))
# Used for converting the X and Y vectors of the Y Axis in IFC true north geolocation
def yaxis2angle(x, y):
angle = math.degrees(math.atan2(y, x)) - 90
if angle < -180:
angle += 360
elif angle > 180:
angle -= 360
return angle
+153 -5
View File
@@ -181,6 +181,7 @@ void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::option
path.add(center.Y());
// @todo isn't there a ")" missing here?
// @todo also X, Y are not added to {x,y}coords vector
// @todo also z_rotation is in radians, should be in degrees
// Bounding box:
// More important to have all geometry in bounding box than to be minimal
@@ -1060,6 +1061,11 @@ void SvgSerializer::write(const geometry_data& data) {
z_rotation -= 180;
}
std::string text_offset = "8";
if (scale_) {
text_offset = "1";
}
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
@@ -1073,7 +1079,7 @@ void SvgSerializer::write(const geometry_data& data) {
xcoords.push_back(path.add(center.X()));
path.add(" ");
ycoords.push_back(path.add(center.Y()));
path.add(") translate(0 -8)\">");
path.add(") translate(0 -" + text_offset + ")\">");
std::vector<std::string> labels{};
@@ -1302,15 +1308,28 @@ void SvgSerializer::write(const geometry_data& data) {
10 * ((pa.X() - pb.X()) * (pa.X() - pb.X())) +
1 * ((pa.Y() - pb.Y()) * (pa.Y() - pb.Y()))
);
if (d > furthest_points_distance) {
gp_Pnt p3d((pa.XYZ() + pb.XYZ()) / 2.);
gp_Pnt2d p2d(p3d.X(), p3d.Y());
if (fcls.Perform(p2d) == TopAbs_IN) {
if (d > furthest_points_distance) {
// Sample some points on the line and assure it's inside.
bool all_inside = true;
for (int i = 5; i < 95; ++i) {
gp_Pnt p3d((pa.XYZ() + (pb.XYZ() - pa.XYZ()) * i / 100.));
gp_Pnt2d p2d(p3d.X(), p3d.Y());
if (fcls.Perform(p2d) != TopAbs_IN) {
all_inside = false;
}
}
if (all_inside) {
gp_Pnt p3d((pa.XYZ() + pb.XYZ()) * 0.5);
gp_Pnt2d p2d(p3d.X(), p3d.Y());
furthest_points = { &pa, &pb };
furthest_points_distance = d;
center_point = p3d;
}
}
}
}
@@ -1563,7 +1582,136 @@ void SvgSerializer::resetScale() {
ymax = -std::numeric_limits<double>::infinity();
}
void SvgSerializer::addTextAnnotations(const drawing_key& k) {
auto& meta = drawing_metadata[k];
boost::optional<std::pair<double, double>> range;
if (k.first && section_data_) {
for (auto& sd : *section_data_) {
if (sd.which() == 0) {
const auto& plan = boost::get<horizontal_plan>(sd);
if (k.first == plan.storey) {
range = std::make_pair(plan.elevation, plan.next_elevation);
}
}
}
}
auto annotations = file->instances_by_type("IfcAnnotation");
if (annotations) {
for (auto& ann_ : *annotations) {
auto ann = (IfcUtil::IfcBaseEntity*) ann_;
auto ot = ann->get("ObjectType");
auto nm = ann->get("Name");
auto ds = ann->get("Description");
auto pl = ann->get("ObjectPlacement");
if (!ot->isNull() && !nm->isNull() && !ds->isNull() && !pl->isNull()) {
auto object_type = (std::string) *ot;
auto name = (std::string) *nm;
auto desc = (std::string) *ds;
if (object_type == "Text") {
IfcGeom::Kernel kernel(file);
gp_Trsf trsf;
if (kernel.convert_placement(*pl, trsf)) {
auto v = trsf.TranslationPart();
if (k.first) {
v.ChangeCoord(1) *= -1.;
trsf.SetTranslationPart(v);
}
if (!range || (v.Z() >= range->first && v.Z() < range->second)) {
if (meta.pln_3d.Position().Direction().Dot(gp_Dir(trsf.HVectorialPart().Column(3))) > 0.99) {
auto svg_name = nameElement(ann);
path_object* po;
if (k.first) {
po = &start_path(meta.pln_3d, k.first, svg_name);
}
else {
po = &start_path(meta.pln_3d, k.second, svg_name);
}
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
boost::optional<double> font_size;
std::vector<std::string> tokens;
boost::split(tokens, name, boost::is_any_of("_"));
if (tokens.size() == 2) {
try {
font_size = boost::lexical_cast<double>(tokens.back());
}
catch (...) {}
}
// @todo column or row?
double z_rotation = gp_Dir(trsf.HVectorialPart().Column(1)).AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
z_rotation *= 180. / M_PI;
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" <text text-anchor=\"left\" x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" y=\"");
ycoords.push_back(path.add(v.Y()));
path.add("\" transform=\"rotate(");
path.add(z_rotation);
path.add(" ");
xcoords.push_back(path.add(v.X()));
path.add(" ");
ycoords.push_back(path.add(v.Y()));
path.add(")\"");
if (font_size) {
path.add(" font-size=\"");
path.add(*font_size);
path.add("\"");
}
path.add(">");
std::vector<std::string> labels{ desc };
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
const auto& l = *lit;
double dy = labels.begin() == lit
? 0.0 // align bottom
: 1.0; // <- dy is relative to the previous text element, so
// always 1 for successive spans.
path.add("<tspan x=\"");
xcoords.push_back(path.add(v.X()));
path.add("\" dy=\"");
path.add(boost::lexical_cast<std::string>(dy));
path.add("em\">");
path.add(l);
path.add("</tspan>");
}
path.add("</text>");
po->second.push_back(path);
}
}
}
}
}
}
}
}
void SvgSerializer::finalize() {
for (auto& p : drawing_metadata) {
addTextAnnotations(p.first);
}
for (auto& p : storey_hlr) {
draw_hlr(drawing_metadata[{p.first, ""}].pln_3d, { p.first, "" });
}
+1
View File
@@ -220,6 +220,7 @@ public:
void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; }
void setStoreyHeightLineLength(double d) { storey_height_line_length_ = d; }
void setSpaceNameTransform(const std::string& v) { space_name_transform_ = v; }
void addTextAnnotations(const drawing_key& k);
std::array<std::array<double, 3>, 3> resize();
void resetScale();