New usecase for adding conversion based units

This commit is contained in:
Dion Moult
2021-10-12 18:31:18 +11:00
parent e455f87305
commit fc2e12bff6
20 changed files with 974 additions and 207 deletions
+3 -1
View File
@@ -27,6 +27,8 @@ import addon_utils
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.placement import ifcopenshell.util.placement
import blenderbim.tool as tool
import blenderbim.core.style
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -105,7 +107,7 @@ class IfcExporter:
continue continue
try: try:
if isinstance(obj, bpy.types.Material): if isinstance(obj, bpy.types.Material):
bpy.ops.bim.update_style_colours(material=obj.name) blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
else: else:
bpy.ops.bim.update_representation(obj=obj.name) bpy.ops.bim.update_representation(obj=obj.name)
except ReferenceError: except ReferenceError:
@@ -97,5 +97,5 @@ class AddAggregate(bpy.types.Operator):
aggregate = bpy.data.objects.new("Assembly", None) aggregate = bpy.data.objects.new("Assembly", None)
aggregate_collection.objects.link(aggregate) aggregate_collection.objects.link(aggregate)
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly") bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=aggregate.name) core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
return {"FINISHED"} return {"FINISHED"}
@@ -25,6 +25,7 @@ import ifcopenshell.util.representation
import logging import logging
import ifcopenshell.api import ifcopenshell.api
import blenderbim.core.geometry as core import blenderbim.core.geometry as core
import blenderbim.core.style
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
@@ -108,7 +109,7 @@ class AddRepresentation(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
[ [
bpy.ops.bim.add_style(material=s.material.name) blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
for s in obj.material_slots for s in obj.material_slots
if s.material and not s.material.BIMMaterialProperties.ifc_style_id if s.material and not s.material.BIMMaterialProperties.ifc_style_id
] ]
@@ -69,7 +69,7 @@ class CreateProject(bpy.types.Operator):
building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None) building_storey = bpy.data.objects.new(self.get_name("IfcBuildingStorey", "My Storey"), None)
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject") bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
bpy.ops.bim.assign_unit() blenderbim.core.unit.assign_scene_units(tool.Ifc, tool.Unit)
model = blenderbim.core.context.add_context( model = blenderbim.core.context.add_context(
tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0 tool.Ifc, context_type="Model", context_identifier="", target_view="", parent=0
@@ -64,4 +64,4 @@ class SpatialData:
@classmethod @classmethod
def is_directly_contained(cls): def is_directly_contained(cls):
return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure")) return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure", False))
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AssignSceneUnits,
operator.AssignUnit, operator.AssignUnit,
operator.UnassignUnit, operator.UnassignUnit,
operator.LoadUnits, operator.LoadUnits,
@@ -0,0 +1,38 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.tool as tool
def refresh():
UnitsData.is_loaded = False
class UnitsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"total_units": cls.get_total_units()}
cls.is_loaded = True
@classmethod
def get_total_units(cls):
ifc = tool.Ifc.get()
return len(ifc.by_type("IfcDerivedUnit") + ifc.by_type("IfcNamedUnit") + ifc.by_type("IfcMonetaryUnit"))
@@ -21,274 +21,131 @@ import json
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.unit import ifcopenshell.util.unit
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.unit as core
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.unit.data import Data from ifcopenshell.api.unit.data import Data
class AssignUnit(bpy.types.Operator): class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data
return {"FINISHED"}
class AssignSceneUnits(bpy.types.Operator, Operator):
bl_idname = "bim.assign_scene_units"
bl_label = "Assign Scene Units"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.assign_scene_units(tool.Ifc, tool.Unit)
class AssignUnit(bpy.types.Operator, Operator):
bl_idname = "bim.assign_unit" bl_idname = "bim.assign_unit"
bl_label = "Assign Unit" bl_label = "Assign Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty() unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() core.assign_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
if self.unit:
ifcopenshell.api.run("unit.assign_unit", self.file, units=[self.file.by_id(self.unit)])
else:
ifcopenshell.api.run("unit.assign_unit", self.file, **self.get_units(context))
Data.load(self.file)
if self.unit:
bpy.ops.bim.load_units()
return {"FINISHED"}
def get_units(self, context):
scene = context.scene
units = {
"length": {
"ifc": None,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
"area": {
"ifc": None,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
"volume": {
"ifc": None,
"is_metric": scene.unit_settings.system != "IMPERIAL",
"raw": scene.unit_settings.length_unit,
},
}
for data in units.values():
if data["raw"] == "ADAPTIVE":
if data["is_metric"]:
data["raw"] = "METERS"
else:
data["raw"] = "FEET"
return units
class UnassignUnit(bpy.types.Operator): class UnassignUnit(bpy.types.Operator, Operator):
bl_idname = "bim.unassign_unit" bl_idname = "bim.unassign_unit"
bl_label = "Unassign Unit" bl_label = "Unassign Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty() unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() core.unassign_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
ifcopenshell.api.run("unit.unassign_unit", IfcStore.get_file(), units=[self.file.by_id(self.unit)])
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class LoadUnits(bpy.types.Operator): class LoadUnits(bpy.types.Operator, Operator):
bl_idname = "bim.load_units" bl_idname = "bim.load_units"
bl_label = "Load Units" bl_label = "Load Units"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.load_units(tool.Unit)
props.units.clear()
for ifc_definition_id, unit in Data.units.items():
name = unit.get("Name", "")
if unit["type"] == "IfcMonetaryUnit":
name = unit["Currency"]
if unit["type"] == "IfcSIUnit" and unit["Prefix"]:
if "_" in name:
name_components = name.split("_")
name = f"{name_components[0]} {unit['Prefix']}{name_components[1]}"
else:
name = f"{unit['Prefix']}{name}"
icon = "MOD_MESHDEFORM"
if unit["type"] == "IfcSIUnit":
icon = "SNAP_GRID"
elif unit["type"] == "IfcMonetaryUnit":
icon = "COPY_ID"
unit_type = unit.get("UserDefinedType", None)
if not unit_type:
unit_type = unit.get("UnitType", None)
if unit["type"] == "IfcMonetaryUnit":
unit_type = "CURRENCY"
new = props.units.add()
new.ifc_definition_id = ifc_definition_id
new.name = name
new.unit_type = unit_type
new.is_assigned = ifc_definition_id in Data.unit_assignment
new.icon = icon
props.is_editing = True
bpy.ops.bim.disable_editing_unit()
return {"FINISHED"}
class DisableUnitEditingUI(bpy.types.Operator): class DisableUnitEditingUI(bpy.types.Operator, Operator):
bl_idname = "bim.disable_unit_editing_ui" bl_idname = "bim.disable_unit_editing_ui"
bl_label = "Disable Unit Editing UI" bl_label = "Disable Unit Editing UI"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
context.scene.BIMUnitProperties.is_editing = False core.disable_unit_editing_ui(tool.Unit)
return {"FINISHED"}
class RemoveUnit(bpy.types.Operator): class RemoveUnit(bpy.types.Operator, Operator):
bl_idname = "bim.remove_unit" bl_idname = "bim.remove_unit"
bl_label = "Remove Unit" bl_label = "Remove Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty() unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.remove_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
self.file = IfcStore.get_file()
ifcopenshell.api.run("unit.remove_unit", self.file, **{"unit": self.file.by_id(self.unit)})
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class AddMonetaryUnit(bpy.types.Operator): class AddMonetaryUnit(bpy.types.Operator, Operator):
bl_idname = "bim.add_monetary_unit" bl_idname = "bim.add_monetary_unit"
bl_label = "Add Monetary Unit" bl_label = "Add Monetary Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.add_monetary_unit(tool.Ifc, tool.Unit)
self.file = IfcStore.get_file()
ifcopenshell.api.run("unit.add_monetary_unit", self.file)
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class AddSIUnit(bpy.types.Operator): class AddSIUnit(bpy.types.Operator, Operator):
bl_idname = "bim.add_si_unit" bl_idname = "bim.add_si_unit"
bl_label = "Add SI Unit" bl_label = "Add SI Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit_type: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.add_si_unit(tool.Ifc, tool.Unit, unit_type=self.unit_type)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"unit.add_si_unit",
self.file,
unit_type=props.named_unit_types,
name=ifcopenshell.util.unit.si_type_names[props.named_unit_types],
)
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class AddContextDependentUnit(bpy.types.Operator): class AddContextDependentUnit(bpy.types.Operator, Operator):
bl_idname = "bim.add_context_dependent_unit" bl_idname = "bim.add_context_dependent_unit"
bl_label = "Add Context Dependent Unit" bl_label = "Add Context Dependent Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def execute(self, context): unit_type: bpy.props.StringProperty()
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.add_context_dependent_unit(tool.Ifc, tool.Unit, unit_type=self.unit_type, name=self.name)
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"unit.add_context_dependent_unit", self.file, unit_type=props.named_unit_types, name="THINGAMAJIG"
)
Data.load(self.file)
bpy.ops.bim.load_units()
return {"FINISHED"}
class EnableEditingUnit(bpy.types.Operator): class EnableEditingUnit(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_unit" bl_idname = "bim.enable_editing_unit"
bl_label = "Enable Editing Unit" bl_label = "Enable Editing Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty() unit: bpy.props.IntProperty()
def execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.enable_editing_unit(tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
props.unit_attributes.clear()
data = Data.units[self.unit]
blenderbim.bim.helper.import_attributes(
data["type"],
props.unit_attributes,
data,
lambda name, prop, data: self.import_attributes(name, prop, data, context),
)
props.active_unit_id = self.unit
return {"FINISHED"}
def import_attributes(self, name, prop, data, context):
if name == "Dimensions" and data["type"] != "IfcSIUnit":
new = context.scene.BIMUnitProperties.unit_attributes.add()
new.name = name
new.is_null = data[name] is None
new.is_optional = False
new.data_type = "string"
new.string_value = json.dumps([e for e in IfcStore.get_file().by_id(data["id"]).Dimensions])
return True
class DisableEditingUnit(bpy.types.Operator): class DisableEditingUnit(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_unit" bl_idname = "bim.disable_editing_unit"
bl_label = "Disable Editing Unit" bl_label = "Disable Editing Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
context.scene.BIMUnitProperties.active_unit_id = 0 core.disable_editing_unit(tool.Unit)
return {"FINISHED"}
class EditUnit(bpy.types.Operator): class EditUnit(bpy.types.Operator, Operator):
bl_idname = "bim.edit_unit" bl_idname = "bim.edit_unit"
bl_label = "Edit Unit" bl_label = "Edit Unit"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
unit: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
props = context.scene.BIMUnitProperties core.edit_unit(tool.Ifc, tool.Unit, unit=tool.Ifc.get().by_id(self.unit))
attributes = blenderbim.bim.helper.export_attributes(props.unit_attributes, self.export_attributes)
self.file = IfcStore.get_file()
unit = self.file.by_id(props.active_unit_id)
if unit.is_a("IfcMonetaryUnit"):
ifcopenshell.api.run("unit.edit_monetary_unit", self.file, **{"unit": unit, "attributes": attributes})
elif unit.is_a("IfcDerivedUnit"):
ifcopenshell.api.run("unit.edit_derived_unit", self.file, **{"unit": unit, "attributes": attributes})
elif unit.is_a("IfcNamedUnit"):
ifcopenshell.api.run("unit.edit_named_unit", self.file, **{"unit": unit, "attributes": attributes})
Data.load(IfcStore.get_file())
bpy.ops.bim.load_units()
return {"FINISHED"}
def export_attributes(self, attributes, prop):
if prop.name == "Dimensions":
try:
attributes[prop.name] = json.loads(prop.get_value())
except:
attributes[prop.name] = (0, 0, 0, 0, 0, 0, 0)
return True
@@ -69,7 +69,7 @@ class Unit(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
unit_type: StringProperty(name="Unit Type") unit_type: StringProperty(name="Unit Type")
is_assigned: BoolProperty(name="Is Assigned") is_assigned: BoolProperty(name="Is Assigned")
icon: StringProperty(name="Icon") ifc_class: StringProperty(name="IFC Class")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -19,7 +19,7 @@
import blenderbim.bim.helper import blenderbim.bim.helper
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.unit.data import Data from blenderbim.bim.module.unit.data import UnitsData
class BIM_PT_units(Panel): class BIM_PT_units(Panel):
@@ -36,13 +36,13 @@ class BIM_PT_units(Panel):
return file return file
def draw(self, context): def draw(self, context):
self.file = IfcStore.get_file() if not UnitsData.is_loaded:
if not Data.is_loaded: UnitsData.load()
Data.load(self.file)
self.props = context.scene.BIMUnitProperties self.props = context.scene.BIMUnitProperties
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Units Found".format(len(Data.units)), icon="SNAP_GRID") row.label(text="{} Units Found".format(UnitsData.data["total_units"]), icon="SNAP_GRID")
if self.props.is_editing: if self.props.is_editing:
row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL") row.operator("bim.disable_unit_editing_ui", text="", icon="CANCEL")
else: else:
@@ -60,10 +60,13 @@ class BIM_PT_units(Panel):
pass # TODO pass # TODO
elif self.props.unit_classes == "IfcSIUnit": elif self.props.unit_classes == "IfcSIUnit":
row.prop(self.props, "named_unit_types", text="") row.prop(self.props, "named_unit_types", text="")
row.operator("bim.add_si_unit", text="", icon="ADD") op = row.operator("bim.add_si_unit", text="", icon="ADD")
op.unit_type = self.props.named_unit_types
elif self.props.unit_classes == "IfcContextDependentUnit": elif self.props.unit_classes == "IfcContextDependentUnit":
row.prop(self.props, "named_unit_types", text="") row.prop(self.props, "named_unit_types", text="")
row.operator("bim.add_context_dependent_unit", text="", icon="ADD") op = row.operator("bim.add_context_dependent_unit", text="", icon="ADD")
op.name = "THINGAMAJIG"
op.unit_type = self.props.named_unit_types
self.layout.template_list( self.layout.template_list(
"BIM_UL_units", "BIM_UL_units",
@@ -85,8 +88,14 @@ class BIM_UL_units(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMUnitProperties props = context.scene.BIMUnitProperties
if item: if item:
icon = "MOD_MESHDEFORM"
if item.ifc_class == "IfcSIUnit":
icon = "SNAP_GRID"
elif item.ifc_class == "IfcMonetaryUnit":
icon = "COPY_ID"
row = layout.row(align=True) row = layout.row(align=True)
row.label(text=item.unit_type or "No Type", icon=item.icon) row.label(text=item.unit_type or "No Type", icon=icon)
row.label(text=item.name or "Unnamed") row.label(text=item.name or "Unnamed")
if item.is_assigned: if item.is_assigned:
+16
View File
@@ -130,6 +130,22 @@ class Surveyor:
def get_absolute_matrix(cls, obj): pass def get_absolute_matrix(cls, obj): pass
@interface
class Unit:
def clear_active_unit(cls): pass
def disable_editing_units(cls): pass
def enable_editing_units(cls): pass
def export_unit_attributes(cls): pass
def get_scene_unit_name(cls, unit_type): pass
def get_scene_unit_si_prefix(cls): pass
def get_si_name_from_unit_type(cls, unit_type): pass
def get_unit_class(cls, unit): pass
def import_unit_attributes(cls, unit): pass
def import_units(cls): pass
def is_scene_unit_metric(cls): pass
def set_active_unit(cls, unit): pass
@interface @interface
class Voider: class Voider:
def can_void(cls, opening, element): pass def can_void(cls, opening, element): pass
+99
View File
@@ -0,0 +1,99 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
def assign_scene_units(ifc, unit):
length_name = unit.get_scene_unit_name("length")
area_name = unit.get_scene_unit_name("area")
volume_name = unit.get_scene_unit_name("volume")
if unit.is_scene_unit_metric():
prefix = unit.get_scene_unit_si_prefix()
lengthunit = ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", name=length_name, prefix=prefix)
areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", name=area_name, prefix=prefix)
volumeunit = ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", name=volume_name, prefix=prefix)
else:
lengthunit = ifc.run("unit.add_conversion_based_unit", name=length_name)
areaunit = ifc.run("unit.add_conversion_based_unit", name=area_name)
volumeunit = ifc.run("unit.add_conversion_based_unit", name=volume_name)
ifc.run("unit.assign_unit", units=[lengthunit, areaunit, volumeunit])
def assign_unit(ifc, unit_tool, unit=None):
ifc.run("unit.assign_unit", units=[unit])
unit_tool.import_units()
def unassign_unit(ifc, unit_tool, unit=None):
ifc.run("unit.unassign_unit", units=[unit])
unit_tool.import_units()
def load_units(unit):
unit.import_units()
unit.enable_editing_units()
def disable_unit_editing_ui(unit):
unit.disable_editing_units()
def remove_unit(ifc, unit_tool, unit=None):
ifc.run("unit.remove_unit", unit=unit)
unit_tool.import_units()
def add_monetary_unit(ifc, unit):
result = ifc.run("unit.add_monetary_unit")
unit.import_units()
return result
def add_si_unit(ifc, unit, unit_type=None):
result = ifc.run("unit.add_si_unit", unit_type=unit_type, name=unit.get_si_name_from_unit_type(unit_type))
unit.import_units()
return result
def add_context_dependent_unit(ifc, unit, unit_type=None, name=None):
result = ifc.run("unit.add_context_dependent_unit", unit_type=unit_type, name=name)
unit.import_units()
return result
def enable_editing_unit(unit_tool, unit=None):
unit_tool.set_active_unit(unit)
unit_tool.import_unit_attributes(unit)
def disable_editing_unit(unit):
unit.clear_active_unit()
def edit_unit(ifc, unit_tool, unit=None):
attributes = unit_tool.export_unit_attributes()
unit_class = unit_tool.get_unit_class(unit)
if unit_class == "IfcMonetaryUnit":
ifc.run("unit.edit_monetary_unit", unit=unit, attributes=attributes)
elif unit_class == "IfcDerivedUnit":
ifc.run("unit.edit_derived_unit", unit=unit, attributes=attributes)
elif unit_class == "IfcNamedUnit":
ifc.run("unit.edit_named_unit", unit=unit, attributes=attributes)
unit_tool.import_units()
unit_tool.clear_active_unit()
@@ -25,3 +25,4 @@ from blenderbim.tool.ifc import Ifc
from blenderbim.tool.owner import Owner from blenderbim.tool.owner import Owner
from blenderbim.tool.style import Style from blenderbim.tool.style import Style
from blenderbim.tool.surveyor import Surveyor from blenderbim.tool.surveyor import Surveyor
from blenderbim.tool.unit import Unit
+157
View File
@@ -0,0 +1,157 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import json
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
class Unit(blenderbim.core.tool.Unit):
@classmethod
def clear_active_unit(cls):
bpy.context.scene.BIMUnitProperties.active_unit_id = 0
@classmethod
def disable_editing_units(cls):
bpy.context.scene.BIMUnitProperties.is_editing = False
@classmethod
def enable_editing_units(cls):
bpy.context.scene.BIMUnitProperties.is_editing = True
@classmethod
def export_unit_attributes(cls):
def callback(attributes, prop):
if prop.name == "Dimensions":
try:
attributes[prop.name] = json.loads(prop.get_value())
except:
attributes[prop.name] = (0, 0, 0, 0, 0, 0, 0)
return True
props = bpy.context.scene.BIMUnitProperties
return blenderbim.bim.helper.export_attributes(props.unit_attributes, callback=callback)
@classmethod
def get_scene_unit_name(cls, unit_type):
props = bpy.context.scene.unit_settings
is_metric = props.system == "METRIC" or props.system == "NONE"
if is_metric:
name = "METRE"
elif props.length_unit == "MILES":
name = "mile"
elif props.length_unit == "FEET" or props.length_unit == "ADAPTIVE":
name = "foot"
elif props.length_unit == "INCHES":
name = "inch"
elif props.length_unit == "THOU":
name = "thou"
if unit_type == "length":
return name
elif unit_type == "area":
if is_metric:
return f"SQUARE_{name}"
return f"square {name}"
elif unit_type == "volume":
if is_metric:
return f"CUBIC_{name}"
return f"cubic {name}"
@classmethod
def get_scene_unit_si_prefix(cls):
props = bpy.context.scene.unit_settings
if props.length_unit == "ADAPTIVE" or props.length_unit == "METERS":
return
return props.length_unit.replace("METERS", "")
@classmethod
def get_si_name_from_unit_type(cls, unit_type):
return ifcopenshell.util.unit.si_type_names.get(unit_type, None)
@classmethod
def get_unit_class(cls, unit):
return unit.is_a()
@classmethod
def import_unit_attributes(cls, unit):
def callback(name, prop, data):
if name == "Dimensions" and data["type"] != "IfcSIUnit":
new = bpy.context.scene.BIMUnitProperties.unit_attributes.add()
new.name = name
new.is_null = data[name] is None
new.is_optional = False
new.data_type = "string"
new.string_value = json.dumps([e for e in tool.Ifc.get().by_id(data["id"]).Dimensions])
return True
props = bpy.context.scene.BIMUnitProperties
props.unit_attributes.clear()
blenderbim.bim.helper.import_attributes2(unit, props.unit_attributes, callback=callback)
@classmethod
def import_units(cls):
props = bpy.context.scene.BIMUnitProperties
props.units.clear()
units = []
for unit_class in ["IfcDerivedUnit", "IfcMonetaryUnit", "IfcNamedUnit"]:
units += tool.Ifc.get().by_type(unit_class)
assigned_units = tool.Ifc.get().by_type("IfcUnitAssignment")
if assigned_units:
assigned_units = assigned_units[0].Units
for unit in units:
name = ""
if unit.is_a("IfcMonetaryUnit"):
name = unit.Currency
elif not unit.is_a("IfcDerivedUnit"):
name = unit.Name or ""
if unit.is_a("IfcSIUnit") and unit.Prefix:
if "_" in name:
name_components = name.split("_")
name = f"{name_components[0]} {unit.Prefix}{name_components[1]}"
else:
name = f"{unit.Prefix}{name}"
if unit.is_a("IfcMonetaryUnit"):
unit_type = "CURRENCY"
else:
unit_type = getattr(unit, "UserDefinedType", None)
if not unit_type:
unit_type = getattr(unit, "UnitType", None)
new = props.units.add()
new.ifc_definition_id = unit.id()
new.name = name
new.unit_type = unit_type
new.is_assigned = unit in assigned_units
new.ifc_class = unit.is_a()
@classmethod
def is_scene_unit_metric(cls):
return bpy.context.scene.unit_settings.system in ["METRIC", "NONE"]
@classmethod
def set_active_unit(cls, unit):
bpy.context.scene.BIMUnitProperties.active_unit_id = unit.id()
+3 -1
View File
@@ -4,6 +4,8 @@ markers =
context context
geometry geometry
owner owner
void
pset_template pset_template
spatial spatial
style
unit
void
@@ -0,0 +1,71 @@
@unit
Feature: Unit
Scenario: Assign scene units
Given an empty IFC project
When I press "bim.assign_scene_units"
Then nothing happens
Scenario: Assign unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
When I press "bim.assign_unit(unit={unit})"
Then nothing happens
Scenario: Unassign unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
When I press "bim.unassign_unit(unit={unit})"
Then nothing happens
Scenario: Load units
Given an empty IFC project
When I press "bim.load_units"
Then nothing happens
Scenario: Disable unit editing UI
Given an empty IFC project
And I press "bim.load_units"
When I press "bim.disable_unit_editing_ui"
Then nothing happens
Scenario: Remove unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
When I press "bim.remove_unit(unit={unit})"
Then nothing happens
Scenario: Add monetary unit
Given an empty IFC project
When I press "bim.add_monetary_unit"
Then nothing happens
Scenario: Add SI unit
Given an empty IFC project
When I press "bim.add_si_unit(unit_type='LENGTHUNIT')"
Then nothing happens
Scenario: Add context dependent unit
Given an empty IFC project
When I press "bim.add_context_dependent_unit(name='THINGAMAJIGS', unit_type='LENGTHUNIT')"
Then nothing happens
Scenario: Enable editing unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
When I press "bim.enable_editing_unit(unit={unit})"
Then nothing happens
Scenario: Disable editing unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
And I press "bim.enable_editing_unit(unit={unit})"
When I press "bim.disable_editing_unit"
Then nothing happens
Scenario: Edit unit
Given an empty IFC project
And the variable "unit" is "{ifc}.by_type('IfcSIUnit')[0].id()"
And I press "bim.enable_editing_unit(unit={unit})"
When I press "bim.edit_unit(unit={unit})"
Then nothing happens
+7
View File
@@ -98,6 +98,13 @@ def container():
prophet.verify() prophet.verify()
@pytest.fixture
def unit():
prophet = Prophecy(blenderbim.core.tool.Unit)
yield prophet
prophet.verify()
class Prophecy: class Prophecy:
def __init__(self, cls): def __init__(self, cls):
self.subject = cls self.subject = cls
+154
View File
@@ -0,0 +1,154 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.core.unit as subject
from test.core.bootstrap import ifc, unit
class TestAssignSceneUnits:
def test_creating_and_assigning_metric_units(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(True)
unit.get_scene_unit_name("length").should_be_called().will_return("name")
unit.get_scene_unit_si_prefix().should_be_called().will_return("prefix")
ifc.run(
"unit.add_si_unit", unit_type="LENGTHUNIT", name="name", prefix="prefix"
).should_be_called().will_return("lengthunit")
unit.get_scene_unit_name("area").should_be_called().will_return("name")
ifc.run("unit.add_si_unit", unit_type="AREAUNIT", name="name", prefix="prefix").should_be_called().will_return(
"areaunit"
)
unit.get_scene_unit_name("volume").should_be_called().will_return("name")
ifc.run(
"unit.add_si_unit", unit_type="VOLUMEUNIT", name="name", prefix="prefix"
).should_be_called().will_return("volumeunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit"]).should_be_called()
subject.assign_scene_units(ifc, unit)
def test_creating_and_assigning_imperial_units(self, ifc, unit):
unit.is_scene_unit_metric().should_be_called().will_return(False)
unit.get_scene_unit_name("length").should_be_called().will_return("lengthname")
ifc.run("unit.add_conversion_based_unit", name="lengthname").should_be_called().will_return("lengthunit")
unit.get_scene_unit_name("area").should_be_called().will_return("areaname")
ifc.run("unit.add_conversion_based_unit", name="areaname").should_be_called().will_return("areaunit")
unit.get_scene_unit_name("volume").should_be_called().will_return("volumename")
ifc.run("unit.add_conversion_based_unit", name="volumename").should_be_called().will_return("volumeunit")
ifc.run("unit.assign_unit", units=["lengthunit", "areaunit", "volumeunit"]).should_be_called()
subject.assign_scene_units(ifc, unit)
class TestAssignUnit:
def test_run(self, ifc, unit):
ifc.run("unit.assign_unit", units=["unit"]).should_be_called()
unit.import_units().should_be_called()
subject.assign_unit(ifc, unit, unit="unit")
class TestUnassignUnit:
def test_run(self, ifc, unit):
ifc.run("unit.unassign_unit", units=["unit"]).should_be_called()
unit.import_units().should_be_called()
subject.unassign_unit(ifc, unit, unit="unit")
class TestLoadUnits:
def test_run(self, unit):
unit.import_units().should_be_called()
unit.enable_editing_units().should_be_called()
subject.load_units(unit)
class TestDisableUnitEditingUI:
def test_run(self, unit):
unit.disable_editing_units().should_be_called()
subject.disable_unit_editing_ui(unit)
class TestRemoveUnit:
def test_run(self, ifc, unit):
ifc.run("unit.remove_unit", unit="unit").should_be_called()
unit.import_units().should_be_called()
subject.remove_unit(ifc, unit, unit="unit")
class TestAddMonetaryUnit:
def test_run(self, ifc, unit):
ifc.run("unit.add_monetary_unit").should_be_called().will_return("unit")
unit.import_units().should_be_called()
assert subject.add_monetary_unit(ifc, unit) == "unit"
class TestAddSIUnit:
def test_run(self, ifc, unit):
unit.get_si_name_from_unit_type("unit_type").should_be_called().will_return("name")
ifc.run("unit.add_si_unit", unit_type="unit_type", name="name").should_be_called().will_return("unit")
unit.import_units().should_be_called()
assert subject.add_si_unit(ifc, unit, unit_type="unit_type") == "unit"
class TestAddContextDependentUnit:
def test_run(self, ifc, unit):
ifc.run("unit.add_context_dependent_unit", unit_type="unit_type", name="name").should_be_called().will_return(
"unit"
)
unit.import_units().should_be_called()
assert subject.add_context_dependent_unit(ifc, unit, unit_type="unit_type", name="name") == "unit"
class TestEnableEditingUnit:
def test_run(self, unit):
unit.set_active_unit("unit").should_be_called()
unit.import_unit_attributes("unit").should_be_called()
subject.enable_editing_unit(unit, unit="unit")
class TestDisableEditingUnit:
def test_run(self, unit):
unit.clear_active_unit().should_be_called()
subject.disable_editing_unit(unit)
class TestEditUnit:
def test_editing_monetary_units(self, ifc, unit):
unit.export_unit_attributes().should_be_called().will_return("attributes")
unit.get_unit_class("unit").should_be_called().will_return("IfcMonetaryUnit")
ifc.run("unit.edit_monetary_unit", unit="unit", attributes="attributes").should_be_called()
unit.import_units().should_be_called()
unit.clear_active_unit().should_be_called()
subject.edit_unit(ifc, unit, unit="unit")
def test_editing_derived_units(self, ifc, unit):
unit.export_unit_attributes().should_be_called().will_return("attributes")
unit.get_unit_class("unit").should_be_called().will_return("IfcDerivedUnit")
ifc.run("unit.edit_derived_unit", unit="unit", attributes="attributes").should_be_called()
unit.import_units().should_be_called()
unit.clear_active_unit().should_be_called()
subject.edit_unit(ifc, unit, unit="unit")
def test_editing_named_units(self, ifc, unit):
unit.export_unit_attributes().should_be_called().will_return("attributes")
unit.get_unit_class("unit").should_be_called().will_return("IfcNamedUnit")
ifc.run("unit.edit_named_unit", unit="unit", attributes="attributes").should_be_called()
unit.import_units().should_be_called()
unit.clear_active_unit().should_be_called()
subject.edit_unit(ifc, unit, unit="unit")
+315
View File
@@ -0,0 +1,315 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
from blenderbim.tool.unit import Unit as subject
class TestImplementsTool(NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Unit)
class TestClearActiveUnit(NewFile):
def test_run(self):
bpy.context.scene.BIMUnitProperties.active_unit_id = 1
subject.clear_active_unit()
assert bpy.context.scene.BIMUnitProperties.active_unit_id == 0
class TestDisableEditingUnits(NewFile):
def test_run(self):
bpy.context.scene.BIMUnitProperties.is_editing = True
subject.disable_editing_units()
assert bpy.context.scene.BIMUnitProperties.is_editing == False
class TestEnableEditingUnits(NewFile):
def test_run(self):
bpy.context.scene.BIMUnitProperties.is_editing = False
subject.enable_editing_units()
assert bpy.context.scene.BIMUnitProperties.is_editing == True
class TestExportUnitAttributes(NewFile):
def test_exporting_derived_units(self):
TestImportUnitAttributes().test_importing_derived_units()
assert subject.export_unit_attributes() == {
"UnitType": "ANGULARVELOCITYUNIT",
"UserDefinedType": "UserDefinedType",
}
def test_exporting_monetary_units(self):
TestImportUnitAttributes().test_importing_monetary_units()
assert subject.export_unit_attributes() == {"Currency": "Currency"}
def test_exporting_monetary_units_ifc2x3(self):
TestImportUnitAttributes().test_importing_monetary_units_ifc2x3()
assert subject.export_unit_attributes() == {"Currency": "USD"}
def test_exporting_context_dependent_units(self):
TestImportUnitAttributes().test_importing_context_dependent_units()
assert subject.export_unit_attributes() == {
"UnitType": "ABSORBEDDOSEUNIT",
"Name": "Name",
"Dimensions": [1, 2, 3, 4, 5, 6, 7],
}
def test_exporting_conversion_based_units(self):
TestImportUnitAttributes().test_importing_conversion_based_units()
assert subject.export_unit_attributes() == {
"UnitType": "ABSORBEDDOSEUNIT",
"Name": "Name",
"Dimensions": [1, 2, 3, 4, 5, 6, 7],
}
def test_exporting_conversion_based_with_offset_units(self):
TestImportUnitAttributes().test_importing_conversion_based_with_offset_units()
assert subject.export_unit_attributes() == {
"UnitType": "ABSORBEDDOSEUNIT",
"Name": "Name",
"Dimensions": [1, 2, 3, 4, 5, 6, 7],
"ConversionOffset": 1,
}
def test_exporting_si_units(self):
TestImportUnitAttributes().test_importing_si_units()
assert subject.export_unit_attributes() == {"UnitType": "ABSORBEDDOSEUNIT", "Prefix": "EXA", "Name": "AMPERE"}
class TestGetSceneUnitName(NewFile):
def test_getting_a_metric_name(self):
bpy.context.scene.unit_settings.system = "METRIC"
assert subject.get_scene_unit_name("length") == "METRE"
assert subject.get_scene_unit_name("area") == "SQUARE_METRE"
assert subject.get_scene_unit_name("volume") == "CUBIC_METRE"
def test_getting_an_imperial_name(self):
bpy.context.scene.unit_settings.system = "IMPERIAL"
bpy.context.scene.unit_settings.length_unit = "MILES"
assert subject.get_scene_unit_name("length") == "mile"
assert subject.get_scene_unit_name("area") == "square mile"
assert subject.get_scene_unit_name("volume") == "cubic mile"
bpy.context.scene.unit_settings.length_unit = "FEET"
assert subject.get_scene_unit_name("length") == "foot"
assert subject.get_scene_unit_name("area") == "square foot"
assert subject.get_scene_unit_name("volume") == "cubic foot"
bpy.context.scene.unit_settings.length_unit = "INCHES"
assert subject.get_scene_unit_name("length") == "inch"
assert subject.get_scene_unit_name("area") == "square inch"
assert subject.get_scene_unit_name("volume") == "cubic inch"
bpy.context.scene.unit_settings.length_unit = "THOU"
assert subject.get_scene_unit_name("length") == "thou"
assert subject.get_scene_unit_name("area") == "square thou"
assert subject.get_scene_unit_name("volume") == "cubic thou"
bpy.context.scene.unit_settings.length_unit = "ADAPTIVE"
assert subject.get_scene_unit_name("length") == "foot"
assert subject.get_scene_unit_name("area") == "square foot"
assert subject.get_scene_unit_name("volume") == "cubic foot"
def test_getting_a_name_with_no_unit_system(self):
bpy.context.scene.unit_settings.system = "NONE"
assert subject.get_scene_unit_name("length") == "METRE"
class TestGetSceneUnitSIPrefix:
def test_run(self):
bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "METERS"
assert subject.get_scene_unit_si_prefix() is None
bpy.context.scene.unit_settings.length_unit = "MICROMETERS"
assert subject.get_scene_unit_si_prefix() == "MICRO"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
assert subject.get_scene_unit_si_prefix() == "MILLI"
bpy.context.scene.unit_settings.length_unit = "CENTIMETERS"
assert subject.get_scene_unit_si_prefix() == "CENTI"
bpy.context.scene.unit_settings.length_unit = "KILOMETERS"
assert subject.get_scene_unit_si_prefix() == "KILO"
bpy.context.scene.unit_settings.length_unit = "ADAPTIVE"
assert subject.get_scene_unit_si_prefix() is None
class TestGetSINameFromUnitType:
def test_run(self):
for key, value in ifcopenshell.util.unit.si_type_names.items():
assert subject.get_si_name_from_unit_type(key) == value
class TestGetUnitClass:
def test_run(self):
ifc = ifcopenshell.file()
assert subject.get_unit_class(ifc.createIfcSIUnit()) == "IfcSIUnit"
assert subject.get_unit_class(ifc.createIfcNamedUnit()) == "IfcNamedUnit"
class TestImportUnitAttributes(NewFile):
def test_importing_derived_units(self):
ifc = ifcopenshell.file()
unit = ifc.createIfcDerivedUnit()
unit.UnitType = "ANGULARVELOCITYUNIT"
unit.UserDefinedType = "UserDefinedType"
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("UnitType").enum_value == "ANGULARVELOCITYUNIT"
assert props.unit_attributes.get("UserDefinedType").string_value == "UserDefinedType"
def test_importing_monetary_units(self):
ifc = ifcopenshell.file()
unit = ifc.createIfcMonetaryUnit()
unit.Currency = "Currency"
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("Currency").string_value == "Currency"
def test_importing_monetary_units_ifc2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
unit = ifc.createIfcMonetaryUnit()
unit.Currency = "USD"
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("Currency").enum_value == "USD"
def test_importing_context_dependent_units(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
unit = ifc.createIfcContextDependentUnit()
unit.UnitType = "ABSORBEDDOSEUNIT"
unit.Name = "Name"
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
assert props.unit_attributes.get("Name").string_value == "Name"
assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
def test_importing_conversion_based_units(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
unit = ifc.createIfcConversionBasedUnit()
unit.UnitType = "ABSORBEDDOSEUNIT"
unit.Name = "Name"
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
assert props.unit_attributes.get("Name").string_value == "Name"
assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
def test_importing_conversion_based_with_offset_units(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
unit = ifc.createIfcConversionBasedUnitWithOffset()
unit.UnitType = "ABSORBEDDOSEUNIT"
unit.Name = "Name"
unit.Dimensions = ifc.createIfcDimensionalExponents(1, 2, 3, 4, 5, 6, 7)
unit.ConversionOffset = 1
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
assert props.unit_attributes.get("Name").string_value == "Name"
assert props.unit_attributes.get("Dimensions").string_value == "[1, 2, 3, 4, 5, 6, 7]"
assert props.unit_attributes.get("ConversionOffset").float_value == 1
def test_importing_si_units(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
unit = ifc.createIfcSIUnit()
unit.UnitType = "ABSORBEDDOSEUNIT"
unit.Prefix = "EXA"
unit.Name = "AMPERE"
subject.import_unit_attributes(unit)
props = bpy.context.scene.BIMUnitProperties
assert props.unit_attributes.get("UnitType").enum_value == "ABSORBEDDOSEUNIT"
assert props.unit_attributes.get("Prefix").enum_value == "EXA"
assert props.unit_attributes.get("Name").enum_value == "AMPERE"
assert props.unit_attributes.get("Dimensions") is None
class TestImportUnits(NewFile):
def test_importing_multiple_units(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
unit1 = ifc.createIfcDerivedUnit(UnitType="ANGULARVELOCITYUNIT")
unit2 = ifc.createIfcMonetaryUnit(Currency="Currency")
unit3 = ifc.createIfcContextDependentUnit(Name="Name", UnitType="ABSORBEDDOSEUNIT")
unit4 = ifc.createIfcConversionBasedUnit(Name="Name", UnitType="ABSORBEDDOSEUNIT")
unit5 = ifc.createIfcSIUnit(Name="AMPERE", Prefix="MILLI", UnitType="ABSORBEDDOSEUNIT")
unit6 = ifc.createIfcSIUnit(Name="CUBIC_METRE", Prefix="CENTI", UnitType="ABSORBEDDOSEUNIT")
ifc.createIfcUnitAssignment(Units=[unit2])
subject.import_units()
props = bpy.context.scene.BIMUnitProperties
assert len(props.units) == 6
assert props.units[0].ifc_definition_id == unit1.id()
assert props.units[0].name == ""
assert props.units[0].is_assigned is False
assert props.units[0].unit_type == unit1.UnitType
assert props.units[0].ifc_class == unit1.is_a()
assert props.units[1].ifc_definition_id == unit2.id()
assert props.units[1].name == "Currency"
assert props.units[1].is_assigned is True
assert props.units[1].unit_type == "CURRENCY"
assert props.units[1].ifc_class == unit2.is_a()
assert props.units[2].ifc_definition_id == unit3.id()
assert props.units[2].name == "Name"
assert props.units[2].is_assigned is False
assert props.units[2].unit_type == unit3.UnitType
assert props.units[2].ifc_class == unit3.is_a()
assert props.units[3].ifc_definition_id == unit4.id()
assert props.units[3].name == "Name"
assert props.units[3].is_assigned is False
assert props.units[3].unit_type == unit4.UnitType
assert props.units[3].ifc_class == unit4.is_a()
assert props.units[4].ifc_definition_id == unit5.id()
assert props.units[4].name == "MILLIAMPERE"
assert props.units[4].is_assigned is False
assert props.units[4].unit_type == unit5.UnitType
assert props.units[4].ifc_class == unit5.is_a()
assert props.units[5].ifc_definition_id == unit6.id()
assert props.units[5].name == "CUBIC CENTIMETRE"
assert props.units[5].is_assigned is False
assert props.units[5].unit_type == unit6.UnitType
assert props.units[5].ifc_class == unit6.is_a()
class TestIsSceneUnitMetric(NewFile):
def test_run(self):
props = bpy.context.scene.unit_settings
props.system = "METRIC"
assert subject.is_scene_unit_metric() is True
props.system = "IMPERIAL"
assert subject.is_scene_unit_metric() is False
props.system = "NONE"
assert subject.is_scene_unit_metric() is True
class TestSetActiveUnit(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
unit = ifc.createIfcSIUnit()
subject.set_active_unit(unit)
assert bpy.context.scene.BIMUnitProperties.active_unit_id == unit.id()
@@ -0,0 +1,37 @@
import ifcopenshell
import ifcopenshell.util.unit
class Usecase:
def __init__(self, file, **settings):
self.file = file
self.settings = {"unit_type": "LENGTHUNIT", "name": "METRE", "prefix": None, "conversion_offset": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
if self.settings["unit_type"] == "LENGTHUNIT":
dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0)
si_unit = self.file.createIfcSIUnit(UnitType=self.settings["unit_type"], Name="METRE")
elif self.settings["unit_type"] == "AREAUNIT":
dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0)
si_unit = self.file.createIfcSIUnit(UnitType=self.settings["unit_type"], Name="SQUARE_METRE")
elif self.settings["unit_type"] == "VOLUMEUNIT":
dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0)
si_unit = self.file.createIfcSIUnit(UnitType=self.settings["unit_type"], Name="CUBIC_METRE")
conversion_real = ifcopenshell.util.unit.si_conversions.get(self.settings["name"], 1)
value_component = self.file.create_entity("IfcReal", **{"wrappedValue": conversion_real})
conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit)
if self.settings["conversion_offset"]:
return self.file.createIfcConversionBasedUnit(
dimensional_exponents,
self.settings["unit_type"],
self.settings["name"],
conversion_factor,
self.settings["conversion_offset"],
)
return self.file.createIfcConversionBasedUnit(
dimensional_exponents, self.settings["unit_type"], self.settings["name"], conversion_factor
)