mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Merge remote-tracking branch 'upstream/v0.7.0' into pset_custom_units_test
This commit is contained in:
@@ -133,9 +133,9 @@ mapper = {
|
||||
'NetWeight' : None,
|
||||
},
|
||||
'Qto_CoveringBaseQuantities' : {
|
||||
'Width' : "get_height",
|
||||
'GrossArea' : "get_gross_footprint_area",
|
||||
'NetArea' : "get_net_footprint_area",
|
||||
'Width' : "get_covering_width",
|
||||
'GrossArea' : "get_covering_gross_area",
|
||||
'NetArea' : "get_covering_net_area",
|
||||
},
|
||||
'Qto_PipeSegmentBaseQuantities' : {
|
||||
'Length' : "get_length",
|
||||
|
||||
@@ -136,6 +136,48 @@ class QtoCalculator:
|
||||
OBB_gross_footprint_area = self.get_gross_footprint_area(OBB_obj)
|
||||
return OBB_gross_footprint_area
|
||||
|
||||
def get_parametric_axis(self, obj):
|
||||
relating_type = ifcopenshell.util.element.get_type(tool.Ifc.get_entity(obj))
|
||||
if relating_type:
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = None
|
||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
||||
if layer_set_direction == "AXIS2":
|
||||
return "AXIS2"
|
||||
elif layer_set_direction == "AXIS3":
|
||||
return "AXIS3"
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_covering_gross_area(self, obj):
|
||||
get_parametric_axis = self.get_parametric_axis(obj)
|
||||
if not get_parametric_axis:
|
||||
return self.get_gross_footprint_area(obj)
|
||||
elif get_parametric_axis == "AXIS2":
|
||||
return self.get_gross_side_area(obj)
|
||||
elif get_parametric_axis == "AXIS3":
|
||||
return self.get_gross_footprint_area(obj)
|
||||
|
||||
def get_covering_net_area(self, obj):
|
||||
get_parametric_axis = self.get_parametric_axis(obj)
|
||||
if not get_parametric_axis:
|
||||
return self.get_net_footprint_area(obj)
|
||||
elif get_parametric_axis == "AXIS2":
|
||||
return self.get_net_side_area(obj)
|
||||
elif get_parametric_axis == "AXIS3":
|
||||
return self.get_net_footprint_area(obj)
|
||||
|
||||
def get_covering_width(self, obj):
|
||||
get_parametric_axis = self.get_parametric_axis(obj)
|
||||
if not get_parametric_axis:
|
||||
return self.get_height(obj)
|
||||
elif get_parametric_axis == "AXIS2":
|
||||
return self.get_width(obj)
|
||||
elif get_parametric_axis == "AXIS3":
|
||||
return self.get_height(obj)
|
||||
|
||||
def get_width(self, o):
|
||||
"""_summary_: Returns the width of the object bounding box
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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 blenderbim.tool as tool
|
||||
|
||||
def refresh():
|
||||
QtoData.is_loaded = False
|
||||
|
||||
class QtoData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"has_cost_item" : cls.has_cost_item(),
|
||||
"relating_cost_items" : cls.relating_cost_items(),
|
||||
}
|
||||
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def has_cost_item(cls):
|
||||
return False if not cls.relating_cost_items() else True
|
||||
|
||||
@classmethod
|
||||
def relating_cost_items(cls):
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return []
|
||||
|
||||
results = []
|
||||
relating_cost_items = tool.Qto.get_related_cost_item_quantities(element)
|
||||
for relating_cost_item in relating_cost_items:
|
||||
results.append(
|
||||
{
|
||||
'cost_item_id' : relating_cost_item['cost_item_id'],
|
||||
'cost_item_name' : relating_cost_item['cost_item_name'],
|
||||
'quantity_id' : relating_cost_item['quantity_id'],
|
||||
'quantity_name' : relating_cost_item['quantity_name'],
|
||||
'quantity_value' : relating_cost_item['quantity_value'],
|
||||
'quantity_type' : relating_cost_item['quantity_type'],
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
# 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/>.
|
||||
|
||||
from bpy.types import Panel
|
||||
import bpy
|
||||
from blenderbim.bim.module.qto.data import QtoData
|
||||
|
||||
|
||||
class BIM_PT_qto_utilities(Panel):
|
||||
class BIM_PT_qto_utilities(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_qto_utilities"
|
||||
bl_label = "Quantity Take-off"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
@@ -28,6 +29,9 @@ class BIM_PT_qto_utilities(Panel):
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
if not QtoData.is_loaded:
|
||||
QtoData.load()
|
||||
|
||||
layout = self.layout
|
||||
props = context.scene.BIMQtoProperties
|
||||
|
||||
@@ -51,9 +55,34 @@ class BIM_PT_qto_utilities(Panel):
|
||||
row.prop(props, "qto_name", text="")
|
||||
row.prop(props, "prop_name", text="")
|
||||
row.operator("bim.quantify_objects", icon="COPYDOWN", text="")
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.assign_objects_base_qto")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.calculate_all_quantities", icon="MOD_EDGESPLIT")
|
||||
|
||||
if context.selected_objects:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Relating Cost Item:")
|
||||
|
||||
if QtoData.data['has_cost_item']:
|
||||
for relating_cost_item in QtoData.data['relating_cost_items']:
|
||||
row.label(text=f"\n")
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Cost item name:")
|
||||
row.label(text=f"{relating_cost_item['cost_item_name']}")
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Quantity name:")
|
||||
row.label(text=f"{relating_cost_item['quantity_name']}")
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Quantity value:")
|
||||
row.label(text=f"{relating_cost_item['quantity_value']}")
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"Quantity type:")
|
||||
row.label(text=f"{relating_cost_item['quantity_type']}")
|
||||
row = layout.row(align=True)
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.label(text = f"No cost item related")
|
||||
|
||||
|
||||
@@ -518,6 +518,7 @@ class Qto:
|
||||
def add_product_base_qto(cls, product): pass
|
||||
def get_new_calculated_quantity(cls, qto_name, quantity_name, object): pass
|
||||
def get_new_guessed_quantity(cls, object, qto_name, quantity_name, ): pass
|
||||
def get_related_cost_item_quantities(cls, product): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -625,7 +626,7 @@ class Sequence:
|
||||
def get_recurrence_pattern_attributes(cls, recurrence_pattern): pass
|
||||
def get_recurrence_pattern_times(cls): pass
|
||||
def get_rel_sequence_attributes(cls): pass
|
||||
|
||||
|
||||
def get_selected_resource(cls): pass
|
||||
def get_start_date(cls): pass
|
||||
def get_task_attribute_value(cls, attribute_name): pass
|
||||
|
||||
@@ -127,3 +127,54 @@ class Qto(blenderbim.core.tool.Qto):
|
||||
):
|
||||
continue
|
||||
return rel.RelatingPropertyDefinition
|
||||
|
||||
@classmethod
|
||||
def get_related_cost_item_quantities(cls, product):
|
||||
"""_summary_: Returns the related cost item and related quantities of the product
|
||||
|
||||
:param ifc-instance product: ifc instance
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
:return list of dictionaries in the form [
|
||||
{
|
||||
"cost_item_id" : XX,
|
||||
"cost_item_name" : XX,
|
||||
"quantity_id" : XX,
|
||||
"quantity_name" : XX,
|
||||
"quantity_value" : XX,
|
||||
"quantity_type" : XX
|
||||
}]
|
||||
:rtype list
|
||||
|
||||
Example:
|
||||
|
||||
.. code::Python
|
||||
import blenderbim.tool as tool
|
||||
|
||||
relating_cost_items = tool.Qto.relating_cost_items(my_beautiful_wall)
|
||||
for relating_cost_item in relating_cost_items:
|
||||
print(f"RELATING COST ITEM NAME: {relating_cost_item["cost_item_name"]}")
|
||||
print(f"RELATING COST QUANTITY NAME: {relating_cost_item["quantity_name"]}")
|
||||
...
|
||||
"""
|
||||
quantities = cls.get_base_qto(product).Quantities
|
||||
model = tool.Ifc.get()
|
||||
cost_items = model.by_type("IfcCostItem")
|
||||
result = []
|
||||
|
||||
for cost_item in cost_items:
|
||||
cost_item_quantities = cost_item.CostQuantities if cost_item.CostQuantities is not None else []
|
||||
for cost_item_quantity in cost_item_quantities:
|
||||
for quantity in quantities:
|
||||
if quantity == cost_item_quantity:
|
||||
result.append(
|
||||
{
|
||||
"cost_item_id" : cost_item.id(),
|
||||
"cost_item_name" : cost_item.Name,
|
||||
"quantity_id" : quantity.id(),
|
||||
"quantity_name" : quantity.Name,
|
||||
"quantity_value" : quantity[3],
|
||||
"quantity_type" : quantity.is_a(),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -142,3 +142,19 @@ class TestGetBaseQto(test.bim.bootstrap.NewFile):
|
||||
product = tool.Ifc.get_entity(wall_obj)
|
||||
assert not subject.get_base_qto(product) == True
|
||||
|
||||
class TestGetRelatedCostItemQuantities(test.bim.bootstrap.NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
wall = ifc.createIfcWall()
|
||||
product = tool.Ifc.get_entity(wall)
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", ifc)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", ifc, cost_schedule=schedule)
|
||||
ifcopenshell.api.run("cost.edit_cost_item", ifc, cost_item=item, attributes={"Name": "Foo"})
|
||||
qto = ifcopenshell.api.run("pset.add_qto", ifc, product=wall, name="Qto_WallBaseQuantities")
|
||||
ifcopenshell.api.run("pset.edit_qto", ifc, qto=qto, properties={"NetVolume": 42.0})
|
||||
ifcopenshell.api.run("cost.assign_cost_item_quantity", ifc, cost_item=item, products=[wall], prop_name="NetVolume")
|
||||
assert subject.get_related_cost_item_quantities(wall)[0]['cost_item_name'] == "Foo"
|
||||
assert subject.get_related_cost_item_quantities(wall)[0]['quantity_name'] == "NetVolume"
|
||||
assert subject.get_related_cost_item_quantities(wall)[0]['quantity_value'] == 42
|
||||
assert subject.get_related_cost_item_quantities(wall)[0]['quantity_type'] == "IfcQuantityVolume"
|
||||
|
||||
@@ -808,7 +808,7 @@ def copy(ifc_file, element):
|
||||
return new
|
||||
|
||||
|
||||
def copy_deep(ifc_file, element, exclude=None, exclude_callback=None):
|
||||
def copy_deep(ifc_file, element, exclude=None, exclude_callback=None, copied_entities=None):
|
||||
"""
|
||||
Recursively copy an element and all of its directly related subelements.
|
||||
|
||||
@@ -825,10 +825,22 @@ def copy_deep(ifc_file, element, exclude=None, exclude_callback=None):
|
||||
:param exclude_callback: A callback to determine whether or not to exclude
|
||||
an entity or not. Returns True to exclude and False to exclude.
|
||||
:type exclude_callback: function,optional
|
||||
:param copied_entities: A dictionary of IDs as keys and entities as values
|
||||
to reuse when coming across the same entity twice. This can typically
|
||||
be left as None.
|
||||
:type copied_entities: dict[int:ifcopenshell.entity_instance.entity_instance]
|
||||
:return: The newly copied element
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
"""
|
||||
if copied_entities is None:
|
||||
copied_entities = {}
|
||||
else:
|
||||
copied_entity = copied_entities.get(element.id(), None)
|
||||
if copied_entity:
|
||||
return copied_entity
|
||||
new = ifc_file.create_entity(element.is_a())
|
||||
if element.id():
|
||||
copied_entities[element.id()] = new
|
||||
for i, attribute in enumerate(element):
|
||||
if attribute is None:
|
||||
continue
|
||||
@@ -838,7 +850,7 @@ def copy_deep(ifc_file, element, exclude=None, exclude_callback=None):
|
||||
elif exclude_callback and exclude_callback(attribute):
|
||||
pass
|
||||
else:
|
||||
attribute = copy_deep(ifc_file, attribute, exclude=exclude)
|
||||
attribute = copy_deep(ifc_file, attribute, exclude=exclude, copied_entities=copied_entities)
|
||||
elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance):
|
||||
if exclude and any([attribute[0].is_a(e) for e in exclude]):
|
||||
pass
|
||||
@@ -847,7 +859,13 @@ def copy_deep(ifc_file, element, exclude=None, exclude_callback=None):
|
||||
else:
|
||||
attribute = list(attribute)
|
||||
for j, item in enumerate(attribute):
|
||||
attribute[j] = copy_deep(ifc_file, item, exclude=exclude, exclude_callback=exclude_callback)
|
||||
attribute[j] = copy_deep(
|
||||
ifc_file,
|
||||
item,
|
||||
exclude=exclude,
|
||||
exclude_callback=exclude_callback,
|
||||
copied_entities=copied_entities,
|
||||
)
|
||||
if new.attribute_name(i) == "GlobalId":
|
||||
new[i] = ifcopenshell.guid.new()
|
||||
else:
|
||||
|
||||
@@ -125,7 +125,7 @@ def assert_valid_inverse(attr, val, schema):
|
||||
aggr = attr.type_of_aggregation_string().upper()
|
||||
|
||||
if aggr:
|
||||
aggr_str = f'{aggr} [{b1}:{b2}] OF '
|
||||
aggr_str = f'{aggr} [{b1}:{"?" if b2 == -1 else b2}] OF '
|
||||
else:
|
||||
aggr_str = ''
|
||||
|
||||
|
||||
@@ -707,3 +707,19 @@ class TestCopyDeepIFC4(test.bootstrap.IFC4):
|
||||
rel.RelatedObjects = [element]
|
||||
rel2 = subject.copy_deep(self.file, rel, exclude_callback=lambda x: x.is_a("IfcWall"))
|
||||
assert rel.RelatedObjects == rel2.RelatedObjects
|
||||
|
||||
def test_copying_and_reusing_element_references(self):
|
||||
points = self.file.createIfcCartesianPointList2D()
|
||||
subelement1 = self.file.createIfcIndexedPolyCurve(points)
|
||||
subelement2 = self.file.createIfcIndexedPolyCurve(points)
|
||||
element = self.file.createIfcGeometricCurveSet([subelement1, subelement2])
|
||||
element2 = subject.copy_deep(self.file, element)
|
||||
assert element2.Elements[0].Points.id() == element2.Elements[1].Points.id()
|
||||
|
||||
def test_copying_primitive_entities(self):
|
||||
element = self.file.createIfcIndexedPolyCurve(
|
||||
Segments=(self.file.createIfcLineIndex((1, 2)), self.file.createIfcLineIndex((3, 4)))
|
||||
)
|
||||
element2 = subject.copy_deep(self.file, element)
|
||||
assert element2.Segments[0][0] == (1, 2)
|
||||
assert element2.Segments[1][0] == (3, 4)
|
||||
|
||||
@@ -29,10 +29,10 @@ bl_info = {
|
||||
}
|
||||
|
||||
import importlib
|
||||
from sverchok.utils.logging import info, debug
|
||||
import logging
|
||||
logger = logging.getLogger('sverchok.ifc')
|
||||
from sverchok.ui.nodeview_space_menu import add_node_menu
|
||||
|
||||
|
||||
def nodes_index():
|
||||
return [
|
||||
(
|
||||
|
||||
@@ -23,6 +23,8 @@ import ifcsverchok.helper
|
||||
from bpy.props import StringProperty, EnumProperty
|
||||
from sverchok.node_tree import SverchCustomTreeNode
|
||||
from sverchok.data_structure import updateNode
|
||||
import logging
|
||||
logger = logging.getLogger('sverchok.ifc')
|
||||
|
||||
|
||||
def update_usecase(self, context):
|
||||
@@ -98,8 +100,8 @@ class SvIfcApi(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCor
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(SvIfcTooltip)
|
||||
bpy.utils.register_class(SvIfcApi)
|
||||
bpy.utils.register_class(SvIfcTooltip)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -26,8 +26,8 @@ from sverchok.data_structure import updateNode
|
||||
import importlib
|
||||
|
||||
|
||||
class SvIfcTooltip(bpy.types.Operator):
|
||||
bl_idname = "node.sv_ifc_tooltip"
|
||||
class SvIfcTooltipWIP(bpy.types.Operator):
|
||||
bl_idname = "node.sv_ifc_tooltipWIP"
|
||||
bl_label = "IFC Info"
|
||||
tooltip: bpy.props.StringProperty()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user