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() else()
add_definitions(-Wno-maybe-uninitialized) add_definitions(-Wno-maybe-uninitialized)
endif() 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 # -fPIC is not relevant on Windows and creates pointless warnings
if (UNIX) if (UNIX)
add_definitions(-fPIC) add_definitions(-fPIC)
+1 -1
View File
@@ -336,7 +336,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
`None`.""" `None`."""
if not os.path.exists(target_dir): if not os.path.exists(target_dir):
logger.info("cloning '%s' into '%s'" % (clone_url, 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: else:
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,)) logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
+3 -3
View File
@@ -52,10 +52,10 @@ endif
# Provides IfcOpenShell Python functionality # Provides IfcOpenShell Python functionality
ifeq ($(PYVERSION), py37) 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 endif
ifeq ($(PYVERSION), py39) 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 endif
cd dist/working && unzip ifcblender* cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/ 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 cd dist/working && unzip v0.6.0.zip
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities # 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/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 # Provides bcf functionality
cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/ cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/
# Provides IFCClash functionality # Provides IFCClash functionality
@@ -25,6 +25,7 @@ if bpy is not None:
"geometry": None, "geometry": None,
"cobie": None, "cobie": None,
"sequence": None, "sequence": None,
"cost": None,
"group": None, "group": None,
"structural": None, "structural": None,
"material": None, "material": None,
+1 -1
View File
@@ -90,7 +90,7 @@ class IfcExporter:
for guid in to_delete: for guid in to_delete:
product = self.file.by_id(guid) product = self.file.by_id(guid)
IfcStore.unlink_element(product) 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): def sync_edited_objects(self):
for obj_name in IfcStore.edited_objs.copy(): for obj_name in IfcStore.edited_objs.copy():
+5 -1
View File
@@ -414,7 +414,11 @@ class IfcImporter:
self.exclude_elements |= self.native_elements self.exclude_elements |= self.native_elements
def is_native(self, element): 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 return
representations = self.get_transformed_body_representations(element.Representation.Representations) representations = self.get_transformed_body_representations(element.Representation.Representations)
@@ -205,7 +205,8 @@ class Helper:
for edge in bm.edges: for edge in bm.edges:
edge_vector = edge.verts[1].co - edge.verts[0].co edge_vector = edge.verts[1].co - edge.verts[0].co
unshared_verts = set(edge.verts) - face_verts_set 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]: if unshared_verts.pop() == edge.verts[1]:
return [edge.verts[0].index, edge.verts[1].index] return [edge.verts[0].index, edge.verts[1].index]
return [edge.verts[1].index, edge.verts[0].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.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{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) Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
@@ -5,8 +5,10 @@ classes = (
operator.EnableEditingGeoreferencing, operator.EnableEditingGeoreferencing,
operator.DisableEditingGeoreferencing, operator.DisableEditingGeoreferencing,
operator.EditGeoreferencing, operator.EditGeoreferencing,
operator.SetNorthOffset, operator.SetIfcGridNorth,
operator.GetNorthOffset, operator.SetBlenderGridNorth,
operator.SetIfcTrueNorth,
operator.SetBlenderTrueNorth,
operator.RemoveGeoreferencing, operator.RemoveGeoreferencing,
operator.AddGeoreferencing, operator.AddGeoreferencing,
operator.ConvertLocalToGlobal, operator.ConvertLocalToGlobal,
@@ -17,27 +17,6 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties 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: while len(props.projected_crs) > 0:
props.projected_crs.remove(0) props.projected_crs.remove(0)
@@ -69,6 +48,27 @@ class EnableEditingGeoreferencing(bpy.types.Operator):
elif props.map_unit_type == "IfcConversionBasedUnit": elif props.map_unit_type == "IfcConversionBasedUnit":
props.map_unit_imperial = Data.projected_crs["MapUnit"]["Name"] 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 props.is_editing = True
return {"FINISHED"} return {"FINISHED"}
@@ -91,23 +91,6 @@ class EditGeoreferencing(bpy.types.Operator):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
props = context.scene.BIMGeoreferenceProperties 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 = {} projected_crs = {}
for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes(): for attribute in IfcStore.get_schema().declaration_by_name("IfcProjectedCRS").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) 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: 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_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( ifcopenshell.api.run(
"georeference.edit_georeferencing", "georeference.edit_georeferencing",
self.file, 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()) Data.load(IfcStore.get_file())
bpy.ops.bim.disable_editing_georeferencing() bpy.ops.bim.disable_editing_georeferencing()
return {"FINISHED"} return {"FINISHED"}
class SetNorthOffset(bpy.types.Operator): class SetBlenderGridNorth(bpy.types.Operator):
bl_idname = "bim.set_north_offset" bl_idname = "bim.set_blender_grid_north"
bl_label = "Set North Offset" bl_label = "Set Blender Grid North"
def execute(self, context): def execute(self, context):
context.scene.sun_pos_properties.north_offset = -radians( context.scene.sun_pos_properties.north_offset = -radians(
ifcopenshell.util.geolocation.xy2angle( ifcopenshell.util.geolocation.xaxis2angle(
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").float_value, float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value),
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value, float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value),
) )
) )
return {"FINISHED"} return {"FINISHED"}
class GetNorthOffset(bpy.types.Operator): class SetIfcGridNorth(bpy.types.Operator):
bl_idname = "bim.get_north_offset" bl_idname = "bim.set_ifc_grid_north"
bl_label = "Get North Offset" bl_label = "Set IFC Grid North"
def execute(self, context): def execute(self, context):
x_angle = -context.scene.sun_pos_properties.north_offset 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("XAxisAbscissa").string_value = str(cos(x_angle))
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").float_value = sin(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"} return {"FINISHED"}
@@ -46,3 +46,6 @@ class BIMGeoreferenceProperties(PropertyGroup):
blender_orthogonal_height: StringProperty(name="Blender Orthogonal Height", default="0") 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_abscissa: StringProperty(name="Blender X Axis Abscissa", default="1")
blender_x_axis_ordinate: StringProperty(name="Blender X Axis Ordinate", default="0") 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 bpy.types import Panel
from ifcopenshell.api.georeference.data import Data from ifcopenshell.api.georeference.data import Data
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
class BIM_PT_gis(Panel): class BIM_PT_gis(Panel):
bl_label = "IFC Georeferencing" bl_label = "IFC Georeferencing"
bl_idname = "BIM_PT_gis" bl_idname = "BIM_PT_gis"
@@ -15,6 +17,8 @@ class BIM_PT_gis(Panel):
return IfcStore.get_file() return IfcStore.get_file()
def draw(self, context): def draw(self, context):
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
if not Data.is_loaded: if not Data.is_loaded:
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
@@ -26,30 +30,10 @@ class BIM_PT_gis(Panel):
def draw_editable_ui(self, context): def draw_editable_ui(self, context):
props = context.scene.BIMGeoreferenceProperties props = context.scene.BIMGeoreferenceProperties
row = self.layout.row(align=True) 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.edit_georeferencing", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_georeferencing", icon="X", 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: for attribute in props.projected_crs:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if attribute.data_type == "string": if attribute.data_type == "string":
@@ -70,10 +54,43 @@ class BIM_PT_gis(Panel):
row.prop(props, "map_unit_imperial", text="") 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.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): def draw_ui(self, context):
props = context.scene.BIMGeoreferenceProperties 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 = self.layout.row(align=True)
row.label(text="Not Georeferenced") row.label(text="Not Georeferenced")
row.operator("bim.add_georeferencing", icon="ADD", text="") row.operator("bim.add_georeferencing", icon="ADD", text="")
@@ -100,26 +117,26 @@ class BIM_PT_gis(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="XAxisOrdinate") row.label(text="XAxisOrdinate")
row.label(text=props.blender_x_axis_ordinate) 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": elif IfcStore.get_file().schema == "IFC2X3":
row = self.layout.row() row = self.layout.row()
row.label(text="Not Georeferenced") 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: if Data.projected_crs:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Projected CRS", icon="WORLD") 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(): for key, value in Data.projected_crs.items():
if key == "id" or key == "type" or not value: 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=key)
row.label(text=str(value)) 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): class BIM_PT_gis_utilities(Panel):
bl_idname = "BIM_PT_gis_utilities" 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(): for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes():
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute) data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity": if data_type == "entity" or data_type == "select":
continue continue
new = props.layer_attributes.add() new = props.layer_attributes.add()
new.name = attribute.name() new.name = attribute.name()
@@ -8,6 +8,9 @@ classes = (
operator.UnassignMaterial, operator.UnassignMaterial,
operator.AddConstituent, operator.AddConstituent,
operator.RemoveConstituent, operator.RemoveConstituent,
operator.AddProfile,
operator.RemoveProfile,
operator.AssignParameterizedProfile,
operator.AddLayer, operator.AddLayer,
operator.RemoveLayer, operator.RemoveLayer,
operator.ReorderMaterialSetItem, operator.ReorderMaterialSetItem,
@@ -1,8 +1,36 @@
import bpy import bpy
import json
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.attribute import ifcopenshell.util.attribute
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.material.data import Data 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): class AddMaterial(bpy.types.Operator):
@@ -115,6 +143,43 @@ class RemoveConstituent(bpy.types.Operator):
return {"FINISHED"} 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): class AddLayer(bpy.types.Operator):
bl_idname = "bim.add_layer" bl_idname = "bim.add_layer"
bl_label = "Add Layer" bl_label = "Add Layer"
@@ -333,8 +398,8 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
props = obj.BIMObjectMaterialProperties self.props = obj.BIMObjectMaterialProperties
props.active_material_set_item_id = self.material_set_item self.props.active_material_set_item_id = self.material_set_item
product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id] product_data = Data.products[obj.BIMObjectProperties.ifc_definition_id]
material_set_item = self.file.by_id(self.material_set_item) material_set_item = self.file.by_id(self.material_set_item)
@@ -347,17 +412,24 @@ class EnableEditingMaterialSetItem(bpy.types.Operator):
else: else:
material_set_item_data = {} 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: self.load_set_item_attributes(material_set_item, material_set_item_data)
props.material_set_item_attributes.remove(0) 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(): 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) data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
if data_type == "entity": if data_type == "entity":
continue continue
if attribute.name() in material_set_item_data: 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.name = attribute.name()
new.is_null = material_set_item_data[attribute.name()] is None new.is_null = material_set_item_data[attribute.name()] is None
new.data_type = data_type 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()] new.int_value = 0 if new.is_null else material_set_item_data[attribute.name()]
elif data_type == "boolean": elif data_type == "boolean":
new.bool_value = False if new.is_null else material_set_item_data[attribute.name()] 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): class DisableEditingMaterialSetItem(bpy.types.Operator):
@@ -431,16 +541,31 @@ class EditMaterialSetItem(bpy.types.Operator):
) )
Data.load_layers() Data.load_layers()
elif product_data["type"] == "IfcMaterialProfileSet": 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( ifcopenshell.api.run(
"material.edit_profile", "material.edit_profile",
self.file, self.file,
**{ **{
"profile": self.file.by_id(self.material_set_item), "profile": self.file.by_id(self.material_set_item),
"attributes": attributes, "attributes": attributes,
"profile_attributes": profile_attributes,
"material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)), "material": self.file.by_id(int(obj.BIMObjectMaterialProperties.material_set_item_material)),
}, },
) )
Data.load_profiles() Data.load_profiles()
ProfileData.load(self.file)
else: else:
pass pass
@@ -1,5 +1,5 @@
import bpy import bpy
import blenderbim.bim.schema # refactor import blenderbim.bim.schema # refactor
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
@@ -17,6 +17,29 @@ from bpy.props import (
materials_enum = [] materials_enum = []
materialtypes_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): def getMaterials(self, context):
@@ -52,4 +75,9 @@ class BIMObjectMaterialProperties(PropertyGroup):
material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute) material_set_attributes: CollectionProperty(name="Material Set Attributes", type=Attribute)
active_material_set_item_id: IntProperty(name="Active Material Set ID") 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_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") 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 bpy.types import Panel
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -46,6 +47,8 @@ class BIM_PT_object_material(Panel):
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
if self.oprops.ifc_definition_id not in Data.products: if self.oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), self.oprops.ifc_definition_id) 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] self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials: 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, "bool_value", text=attribute.name)
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") 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): def draw_read_only_set_item_ui(self, set_item_id, index, is_first=False, is_last=False):
if self.product_data["type"] == "IfcMaterialList": if self.product_data["type"] == "IfcMaterialList":
item = Data.materials[set_item_id] item = Data.materials[set_item_id]
@@ -84,6 +84,8 @@ class PieAddOpening(bpy.types.Operator):
for obj in context.selected_objects: for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id: if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
opening_name = obj.name 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: else:
opj_name = obj.name opj_name = obj.name
bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name) bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
@@ -3,6 +3,7 @@ from . import ui, prop, operator
classes = ( classes = (
operator.CreateProject, operator.CreateProject,
operator.CreateProjectLibrary,
operator.ValidateIfcFile, operator.ValidateIfcFile,
prop.BIMProjectProperties, prop.BIMProjectProperties,
ui.BIM_PT_project, ui.BIM_PT_project,
@@ -53,6 +53,30 @@ class CreateProject(bpy.types.Operator):
return {"FINISHED"} 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): class ValidateIfcFile(bpy.types.Operator):
bl_idname = "bim.validate_ifc_file" bl_idname = "bim.validate_ifc_file"
bl_label = "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.prop(props, "volume_unit", text="Volume Unit")
row = self.layout.row() row = self.layout.row()
row.operator("bim.create_project") 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 from . import ui, prop, operator
classes = ( 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.LoadTasks,
operator.DisableTaskEditingUI, operator.DisableTaskEditingUI,
operator.AddWorkPlan, operator.LoadWorkCalendars,
operator.RemoveWorkPlan, 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.Task,
prop.BIMTaskProperties, prop.BIMTaskProperties,
ui.BIM_PT_work_plans, 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_PT_tasks,
ui.BIM_UL_tasks, ui.BIM_UL_tasks,
) )
def register(): def register():
bpy.types.Scene.BIMTaskProperties = bpy.props.PointerProperty(type=prop.BIMTaskProperties) 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(): def unregister():
del bpy.types.Scene.BIMTaskProperties 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 bpy
import json
import ifcopenshell.api import ifcopenshell.api
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.sequence.data import Data 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): class AddWorkPlan(bpy.types.Operator):
bl_idname = "bim.add_work_plan" bl_idname = "bim.add_work_plan"
bl_label = "Add Work Plan" bl_label = "Add Work Plan"
@@ -11,6 +38,33 @@ class AddWorkPlan(bpy.types.Operator):
def execute(self, context): def execute(self, context):
ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file()) ifcopenshell.api.run("sequence.add_work_plan", IfcStore.get_file())
Data.load(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"} return {"FINISHED"}
@@ -20,10 +74,290 @@ class RemoveWorkPlan(bpy.types.Operator):
work_plan: bpy.props.IntProperty() work_plan: bpy.props.IntProperty()
def execute(self, context): 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( 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()) 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"} return {"FINISHED"}
@@ -23,3 +23,42 @@ class BIMTaskProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
tasks: CollectionProperty(name="Tasks", type=Task) tasks: CollectionProperty(name="Tasks", type=Task)
active_task_index: IntProperty(name="Active Task Index") 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): def draw(self, context):
if not Data.is_loaded: if not Data.is_loaded:
Data.load(IfcStore.get_file()) 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() if self.props.is_editing:
row.operator("bim.add_work_plan", icon="ADD") 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 = self.layout.row(align=True)
row.label(text=work_plan["Name"] or "Unnamed", icon="TEXT") if attribute.data_type == "string":
row.operator("bim.add_work_plan", text="", icon="GREASEPENCIL") row.prop(attribute, "string_value", text=attribute.name)
row.operator("bim.remove_work_plan", text="", icon="X").work_plan = work_plan_id 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): class BIM_PT_tasks(Panel):
@@ -27,6 +27,8 @@ class BIM_PT_voids(Panel):
for obj in context.selected_objects: for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id: if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
op.opening = obj.name 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: else:
op.obj = obj.name op.obj = obj.name
+4 -1
View File
@@ -1,3 +1,6 @@
import ifcopenshell
import ezdxf
class Dxf2Ifc: class Dxf2Ifc:
def execute(self): def execute(self):
self.create_ifc_file() self.create_ifc_file()
@@ -14,7 +17,7 @@ class Dxf2Ifc:
[ [
self.file.createIfcFaceOuterBound( self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop( 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, True,
) )
@@ -1,6 +1,8 @@
import math import math
import numpy as np
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
from behave import step from behave import step
from bimtester import util from bimtester import util
@@ -171,7 +173,7 @@ def step_impl(context, number):
return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number) return check_ifc2x3_geolocation("EPset_MapConversion", "Height", number)
abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False) abscissa = check_ifc4_geolocation("IfcMapConversion", "XAxisAbscissa", should_assert=False)
ordinate = check_ifc4_geolocation("IfcMapConversion", "XAxisOrdinate", 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) value = round(number, 3)
assert actual_value == value, _('We expected a value of "{}" but instead got "{}"').format(value, actual_value) 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) 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}"') @step(u'The site "{guid}" has a longitude of "{number}"')
def step_impl(context, guid, number): def step_impl(context, guid, number):
number = util.assert_number(number) number = util.assert_number(number)
@@ -212,3 +234,14 @@ def step_impl(context, guid, number):
site = util.assert_guid(IfcStore.file, guid) site = util.assert_guid(IfcStore.file, guid)
util.assert_type(site, "IfcSite") util.assert_type(site, "IfcSite")
util.assert_attribute(site, "RefElevation", number) 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) { virtual bool convert_placement(IfcUtil::IfcBaseClass* item, gp_Trsf& trsf) {
if (item->as<IfcSchema::IfcObjectPlacement>()) { if (item->as<IfcSchema::IfcObjectPlacement>()) {
return convert(item->as<IfcSchema::IfcObjectPlacement>(), trsf); try {
} else { return convert(item->as<IfcSchema::IfcObjectPlacement>(), trsf);
return false; } 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) self.file.remove(decomposes)
if is_decomposed_by: if is_decomposed_by:
related_objects = list(is_decomposed_by.RelatedObjects) related_objects = set(is_decomposed_by.RelatedObjects)
related_objects.append(self.settings["product"]) related_objects.add(self.settings["product"])
is_decomposed_by.RelatedObjects = related_objects is_decomposed_by.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by}) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by})
else: else:
is_decomposed_by = self.file.create_entity( is_decomposed_by = self.file.create_entity(
@@ -50,3 +50,4 @@ class Usecase:
"RelatingObject": self.settings["relating_object"], "RelatingObject": self.settings["relating_object"],
} }
) )
return is_decomposed_by
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -21,7 +21,12 @@ class Usecase:
context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin) context = self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin)
else: else:
context = self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin) 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: if project.RepresentationContexts:
contexts = list(project.RepresentationContexts) contexts = list(project.RepresentationContexts)
else: else:
@@ -2,12 +2,14 @@ class Data:
is_loaded = False is_loaded = False
map_conversion = {} map_conversion = {}
projected_crs = {} projected_crs = {}
true_north = {}
@classmethod @classmethod
def purge(cls): def purge(cls):
cls.is_loaded = False cls.is_loaded = False
cls.map_conversion = {} cls.map_conversion = {}
cls.projected_crs = {} cls.projected_crs = {}
cls.true_north = None
@classmethod @classmethod
def load(cls, file): def load(cls, file):
@@ -15,6 +17,7 @@ class Data:
return return
cls.map_conversion = {} cls.map_conversion = {}
cls.projected_crs = {} cls.projected_crs = {}
cls.true_north = {}
if file.schema == "IFC2X3": if file.schema == "IFC2X3":
return return
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
@@ -28,4 +31,9 @@ class Data:
if cls.projected_crs["MapUnit"]: if cls.projected_crs["MapUnit"]:
cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info() cls.projected_crs["MapUnit"] = map_conversion.TargetCRS.MapUnit.get_info()
break 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 cls.is_loaded = True
@@ -7,6 +7,7 @@ class Usecase:
self.settings = { self.settings = {
"map_conversion": {}, "map_conversion": {},
"projected_crs": {}, "projected_crs": {},
"true_north": [],
"map_unit": "", "map_unit": "",
} }
for key, value in settings.items(): for key, value in settings.items():
@@ -21,6 +22,7 @@ class Usecase:
setattr(projected_crs, name, value) setattr(projected_crs, name, value)
self.remove_existing_map_unit(projected_crs) self.remove_existing_map_unit(projected_crs)
self.set_map_unit(projected_crs) self.set_map_unit(projected_crs)
self.set_true_north()
def remove_existing_map_unit(self, projected_crs): def remove_existing_map_unit(self, projected_crs):
if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1: 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.settings["map_unit"],
self.file.createIfcMeasureWithUnit(value_component, si_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: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -4,6 +4,7 @@ class Usecase():
self.settings = { self.settings = {
"profile": None, "profile": None,
"attributes": {}, "attributes": {},
"profile_attributes": {},
"material": None "material": None
} }
for key, value in settings.items(): for key, value in settings.items():
@@ -13,3 +14,5 @@ class Usecase():
for name, value in self.settings["attributes"].items(): for name, value in self.settings["attributes"].items():
setattr(self.settings["profile"], name, value) setattr(self.settings["profile"], name, value)
self.settings["profile"].Material = self.settings["material"] 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: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -1,6 +1,3 @@
import ifcopenshell
class Usecase: class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
self.file = file self.file = file
@@ -9,4 +9,7 @@ class Usecase:
self.settings[key] = value self.settings[key] = value
def execute(self): 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 self.settings[key] = value
def execute(self): 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( pset = self.file.create_entity(
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]} "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) has_property_sets.append(pset)
self.settings["product"].HasPropertySets = has_property_sets self.settings["product"].HasPropertySets = has_property_sets
elif self.settings["product"].is_a("IfcMaterialDefinition"): elif self.settings["product"].is_a("IfcMaterialDefinition"):
@@ -18,9 +18,9 @@ class Usecase:
elif self.settings["product"].is_a("IfcTypeProduct"): elif self.settings["product"].is_a("IfcTypeProduct"):
representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []] representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []]
for representation in representations: for representation in representations:
ifcopenshell.api.run("owner.unassign_representation", ifcopenshell.api.run("geometry.unassign_representation",
self.file, **{"product": self.settings["product"], "representation": 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 # TODO: remove object placement and other relationships
self.file.remove(self.settings["product"]) self.file.remove(self.settings["product"])
@@ -25,18 +25,7 @@ class Usecase:
work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcDateTime") work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(self.settings["start_time"], "IfcDateTime")
context = self.file.by_type("IfcContext")[0] context = self.file.by_type("IfcContext")[0]
if context.Declares: ifcopenshell.api.run(
rel_declares = context.Declares[0] "project.assign_declaration", self.file, definition=work_plan, relating_context=context
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
return work_plan return work_plan
@@ -4,19 +4,32 @@ import ifcopenshell.util.date
class Data: class Data:
is_loaded = False is_loaded = False
work_plans = {} work_plans = {}
work_schedules = {}
tasks = {} tasks = {}
@classmethod @classmethod
def purge(cls): def purge(cls):
cls.is_loaded = False cls.is_loaded = False
cls.work_plans = {} cls.work_plans = {}
cls.work_schedules = {}
cls.work_calendars = {}
cls.tasks = {} cls.tasks = {}
@classmethod @classmethod
def load(cls, file): 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.work_plans = {}
cls.tasks = {} for work_plan in cls._file.by_type("IfcWorkPlan"):
for work_plan in file.by_type("IfcWorkPlan"):
data = work_plan.get_info() data = work_plan.get_info()
del data["OwnerHistory"] del data["OwnerHistory"]
if data["Creators"]: if data["Creators"]:
@@ -26,6 +39,32 @@ class Data:
if data["FinishTime"]: if data["FinishTime"]:
data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"]) data["FinishTime"] = ifcopenshell.util.date.ifc2datetime(data["FinishTime"])
cls.work_plans[work_plan.id()] = data 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.tasks[task.id()] = {"Name": task.Name, "Identification": task.Identification or ""}
cls.is_loaded=True
@@ -7,8 +7,10 @@ class Usecase:
def execute(self): def execute(self):
# TODO: do a deep purge # TODO: do a deep purge
if self.settings["work_plan"].HasContext: ifcopenshell.api.run(
rel_declares = self.settings["work_plan"].HasContext[0] "project.unassign_declaration",
if len(rel_declares.RelatedDefinitions) == 1: self.file,
self.file.remove(rel_declares) definition=self.settings["work_plan"],
relating_context=self.file.by_type("IfcContext")[0],
)
self.file.remove(self.settings["work_plan"]) self.file.remove(self.settings["work_plan"])
@@ -1,5 +1,6 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.placement
class Usecase: class Usecase:
@@ -43,3 +44,11 @@ class Usecase:
"RelatingStructure": self.settings["relating_structure"], "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 # TODO: handle unit rewriting, which is complicated
else: else:
unit_assignment = self.file.createIfcUnitAssignment([u["ifc"] for u in self.settings.values()]) 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 return unit_assignment
def create_metric_unit(self, unit_type, data): 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) yield k, getattr(self, k)
def __str__(self): 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): class entity(facet):
@@ -88,19 +92,19 @@ class classification(facet):
""" """
parameters = ["system", "value"] 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): def __call__(self, inst, logger):
refs = [] refs = []
for association in inst.HasAssociations: for association in inst.HasAssociations:
if association.is_a("IfcRelAssociatesClassification"): if association.is_a("IfcRelAssociatesClassification"):
cref = association.RelatingClassification cref = association.RelatingClassification
refs.append((cref.ReferencedSource, cref.Name)) refs.append((cref.ReferencedSource.Name, cref.ItemReference))
return facet_evaluation( return facet_evaluation(
(self.system, self.value) in refs, (self.system, self.value) in refs,
# @todo # @todo
"", "[classification_eval_todo]",
) )
@@ -109,17 +113,19 @@ class property(facet):
The IDS property facet implenented using `ifcopenshell.util.element` The IDS property facet implenented using `ifcopenshell.util.element`
""" """
parameters = ["property", "propertyset", "value"] parameters = ["name", "propertyset", "value"]
message = "a property '%(property)s' in '%(propertyset)s' with value '%(value)s'"
# import pdb;pdb.set_trace()
message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'"
def __call__(self, inst, logger): def __call__(self, inst, logger):
props = ifcopenshell.util.element.get_psets(inst) props = ifcopenshell.util.element.get_psets(inst)
pset = props.get(self.propertyset) 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) logger.debug("Testing %s == %s", val, self.value)
di = { di = {
"property": self.property, "name": self.name,
"propertyset": self.propertyset, "propertyset": self.propertyset,
"value": val, "value": val,
} }
@@ -128,13 +134,38 @@ class property(facet):
msg = self.message % di msg = self.message % di
else: else:
if pset: if pset:
msg = "a set '%(propertyset)s', but no property '%(property)'" % di msg = "a set '%(propertyset)s', but no property '%(name)'" % di
else: else:
msg = "no set '%(propertyset)s'" % di msg = "no set '%(propertyset)s'" % di
return facet_evaluation(val == self.value, msg) 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: class boolean_logic:
""" """
Boolean conjunction over a collection of functions Boolean conjunction over a collection of functions
@@ -166,17 +197,39 @@ class restriction:
""" """
def __init__(self, node): 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): def __eq__(self, other):
return other in self.options return other in self.options
def __repr__(self): 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: class specification:
@@ -202,10 +255,11 @@ class specification:
def __call__(self, inst, logger): def __call__(self, inst, logger):
if self.applicabiliy(inst, logger): if self.applicabiliy(inst, logger):
valid = self.requirements(inst, logger) valid = self.requirements(inst, logger)
if valid: 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: 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): def __str__(self):
return "Given an instance with %(applicabiliy)s\nWe expect %(requirements)s" % self.__dict__ 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 spec in self.specifications:
for elem in ifc_file.by_type("IfcObject"): for elem in ifc_file.by_type("IfcObject"):
spec(elem, logger) spec(elem, logger)
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys, os
import logging import logging
import ifcopenshell import ifcopenshell
filename = os.path.join(os.getcwd(), "ids.txt")
logger = logging.getLogger("IDS") 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]) ids_file = ids(sys.argv[1])
ifc_file = ifcopenshell.open(sys.argv[2]) 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 # Used for converting the X and Y vectors of the X Axis in IFC grid north geolocation
def xy2angle(x, y): def xaxis2angle(x, y):
return math.degrees(math.atan2(y, x)) 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()); path.add(center.Y());
// @todo isn't there a ")" missing here? // @todo isn't there a ")" missing here?
// @todo also X, Y are not added to {x,y}coords vector // @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: // Bounding box:
// More important to have all geometry in bounding box than to be minimal // 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; z_rotation -= 180;
} }
std::string text_offset = "8";
if (scale_) {
text_offset = "1";
}
util::string_buffer path; util::string_buffer path;
// dominant-baseline="central" is not well supported in IE. // dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans // 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())); xcoords.push_back(path.add(center.X()));
path.add(" "); path.add(" ");
ycoords.push_back(path.add(center.Y())); ycoords.push_back(path.add(center.Y()));
path.add(") translate(0 -8)\">"); path.add(") translate(0 -" + text_offset + ")\">");
std::vector<std::string> labels{}; 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())) + 10 * ((pa.X() - pb.X()) * (pa.X() - pb.X())) +
1 * ((pa.Y() - pb.Y()) * (pa.Y() - pb.Y())) 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 = { &pa, &pb };
furthest_points_distance = d; furthest_points_distance = d;
center_point = p3d; center_point = p3d;
} }
} }
} }
} }
@@ -1563,7 +1582,136 @@ void SvgSerializer::resetScale() {
ymax = -std::numeric_limits<double>::infinity(); 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() { void SvgSerializer::finalize() {
for (auto& p : drawing_metadata) {
addTextAnnotations(p.first);
}
for (auto& p : storey_hlr) { for (auto& p : storey_hlr) {
draw_hlr(drawing_metadata[{p.first, ""}].pln_3d, { p.first, "" }); 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 setDrawDoorArcs(bool b) { draw_door_arcs_ = b; }
void setStoreyHeightLineLength(double d) { storey_height_line_length_ = d; } void setStoreyHeightLineLength(double d) { storey_height_line_length_ = d; }
void setSpaceNameTransform(const std::string& v) { space_name_transform_ = v; } void setSpaceNameTransform(const std::string& v) { space_name_transform_ = v; }
void addTextAnnotations(const drawing_key& k);
std::array<std::array<double, 3>, 3> resize(); std::array<std::array<double, 3>, 3> resize();
void resetScale(); void resetScale();