mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 10:51:13 +00:00
merage master v6.0.0
This commit is contained in:
@@ -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
@@ -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,))
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 "{}"')
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, "" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user