diff --git a/src/blenderbim/blenderbim/bim/module/resource/__init__.py b/src/blenderbim/blenderbim/bim/module/resource/__init__.py index c91d7b7591..095ee1c427 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/resource/__init__.py @@ -50,6 +50,8 @@ classes = ( operator.CalculateResourceWork, operator.ImportResources, operator.EditProductivityData, + operator.ConstrainResourceWork, + operator.RemoveUsageConstraint, prop.Resource, prop.BIMResourceProperties, prop.BIMResourceTreeProperties, diff --git a/src/blenderbim/blenderbim/bim/module/resource/data.py b/src/blenderbim/blenderbim/bim/module/resource/data.py index 21f4250027..b854002ce4 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/data.py +++ b/src/blenderbim/blenderbim/bim/module/resource/data.py @@ -56,6 +56,7 @@ class ResourceData: "type": resource.is_a(), "BaseQuantity": base_quantity, } + results[resource.id()]["Benchmarks"] = tool.Resource.get_resource_benchmarks(resource) if resource.is_a() in ["IfcLaborResource", "IfcConstructionEquipmentResource"]: results[resource.id()]["Productivity"] = {} results[resource.id()]["InheritedProductivity"] = {} diff --git a/src/blenderbim/blenderbim/bim/module/resource/operator.py b/src/blenderbim/blenderbim/bim/module/resource/operator.py index 22fc5a0f07..c160ef6d60 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/operator.py +++ b/src/blenderbim/blenderbim/bim/module/resource/operator.py @@ -363,3 +363,29 @@ class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): core.edit_productivity_pset(tool.Ifc, tool.Resource) + + +class ConstrainResourceWork(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_usage_constraint" + bl_label = "Constrain Resource Work" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + attribute: bpy.props.StringProperty() + + def _execute(self, context): + core.add_usage_constraint( + tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute + ) + + +class RemoveUsageConstraint(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.remove_usage_constraint" + bl_label = "Remove Usage Constraint" + bl_options = {"REGISTER", "UNDO"} + resource: bpy.props.IntProperty() + attribute: bpy.props.StringProperty() + + def _execute(self, context): + core.remove_usage_constraint( + tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute + ) diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index 10a22625c3..f4b325571a 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -92,6 +92,49 @@ class BIM_PT_resources(Panel): row = self.layout.row(align=True) row.label(text="Resource type cannot have productivity data", icon="ERROR") else: + is_usage_locked = False + is_work_locked = False + for constraint in resource["Benchmarks"] or []: + for metric in constraint["metrics"] or []: + if ( + metric["ConstraintGrade"] == "HARD" + and metric["reference"] + and metric["reference"] == "Usage.ScheduleUsage" + ): + is_usage_locked = True + elif ( + metric["ConstraintGrade"] == "HARD" + and metric["reference"] + and metric["reference"] == "Usage.ScheduleWork" + ): + is_work_locked = True + row = self.layout.row() + row.label(text="Resource Work") + schedule_usage = "Schedule Usage: {}".format(resource.get("ScheduleUsage")) + schedule_work = "Schedule Work: {}".format(resource.get("ScheduleWork")) + row = self.layout.row() + row.alignment = "LEFT" + row.label(text=schedule_usage, icon="ARMATURE_DATA") + row2 = self.layout.row() + row2.alignment = "LEFT" + row2.label(text=schedule_work, icon="ARMATURE_DATA") + if not is_usage_locked: + op = row.operator("bim.add_usage_constraint", text="", icon="UNLOCKED") + op.resource = ifc_definition_id + op.attribute = "Usage.ScheduleUsage" + else: + op = row.operator("bim.remove_usage_constraint", text="", icon="LOCKED") + op.resource = ifc_definition_id + op.attribute = "Usage.ScheduleUsage" + if not is_work_locked: + op = row2.operator("bim.add_usage_constraint", text="", icon="UNLOCKED") + op.resource = ifc_definition_id + op.attribute = "Usage.ScheduleWork" + else: + op = row2.operator("bim.remove_usage_constraint", text="", icon="LOCKED") + op.resource = ifc_definition_id + op.attribute = "Usage.ScheduleWork" + productivity = resource["Productivity"] parent_productivity = resource["InheritedProductivity"] diff --git a/src/blenderbim/blenderbim/core/resource.py b/src/blenderbim/blenderbim/core/resource.py index 0beafa9b9b..cc660e8685 100644 --- a/src/blenderbim/blenderbim/core/resource.py +++ b/src/blenderbim/blenderbim/core/resource.py @@ -182,3 +182,35 @@ def edit_productivity_pset(ifc, resource_tool): else: pset = ifc.run("pset.add_pset", product=resource, name="EPset_Productivity") ifc.run("pset.edit_pset", pset=pset, properties=resource_tool.get_productivity_attributes()) + + +def add_usage_constraint(ifc, resource_tool, resource=None, reference_path=None): + metric = resource_tool.has_usage_metric(resource) + if metric: + return print("Must remove existing metric first") + + objective = ifc.run("constraint.add_objective") + ifc.run("constraint.edit_objective", objective=objective, attributes={"ObjectiveQualifier": "PARAMETER"}) + metric = ifc.run("constraint.add_metric", objective=objective) + ifc.run( + "constraint.edit_metric", + metric=metric, + attributes={ + "ConstraintGrade": "HARD", + "Benchmark": "EQUALTO", + }, + ) + ifc.run("constraint.add_metric_reference", metric=metric, reference_path=reference_path) + ifc.run("constraint.assign_constraint", product=resource, constraint=objective) + + +def remove_usage_constraint(ifc, resource_tool, resource, reference_path): + constraints = resource_tool.get_constraints(resource) + for constraint in constraints: + metrics = resource_tool.get_metrics(constraint) + for metric in metrics: + reference = resource_tool.get_metric_reference(metric, is_deep=True) + if reference == reference_path: + ifc.run("constraint.remove_metric", metric=metric) + ifc.run("constraint.unassign_constraint", product=resource, constraint=constraint) + ifc.run("constraint.remove_constraint", constraint=constraint) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 797f556e25..452c84d70f 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -617,6 +617,13 @@ class Resource: def load_resource_properties(cls): pass def load_resource_time_attributes(cls, resource_time): pass def load_resources(cls): pass + def get_constraints(cls, resource): pass + def get_metrics(cls, constraint): pass + def get_metric_reference(cls, metric, is_deep): pass + def get_resource_benchmarks(cls, resource): pass + def has_metric_constraint(cls, resource, attribute): pass + def has_usage_metric(cls, resource): pass + @interface class Root: diff --git a/src/blenderbim/blenderbim/tool/resource.py b/src/blenderbim/blenderbim/tool/resource.py index ea41bfbe6c..f8b92074f1 100644 --- a/src/blenderbim/blenderbim/tool/resource.py +++ b/src/blenderbim/blenderbim/tool/resource.py @@ -380,4 +380,72 @@ class Resource(blenderbim.core.tool.Resource): "pset.edit_pset", pset=pset, properties=attributes, - ) \ No newline at end of file + ) + + @classmethod + def get_constraints(cls, resource): + constraints = [] + for rel in resource.HasAssociations or []: + if rel.is_a("IfcRelAssociatesConstraint"): + constraints.append(rel.RelatingConstraint) + return constraints + + @classmethod + def get_metrics(cls, constraint): + metrics = [] + for metric in constraint.BenchmarkValues or []: + metrics.append(metric) + return metrics + + @classmethod + def get_metric_reference(cls, metric, is_deep=True): + def get_reference_Attribute(ref, path): + if ref: + if is_deep: + if not path: + path = ref.AttributeIdentifier + else: + path += ".{}".format(ref.AttributeIdentifier) if ref.AttributeIdentifier else "" + return get_reference_Attribute(ref.InnerReference, path) + else: + return ref.AttributeIdentifier + return path + + reference = metric.ReferencePath + return get_reference_Attribute(reference, "") + + @classmethod + def get_resource_benchmarks(cls, resource): + constraints = [] + for constraint in cls.get_constraints(resource) or []: + metrics = [] + for metric in cls.get_metrics(constraint) or []: + metrics.append( + { + "reference": cls.get_metric_reference(metric), + "Benchmark": metric.Benchmark, + "ConstraintGrade": metric.ConstraintGrade, + } + ) + constraints.append({"ObjectiveQualifier": constraint.ObjectiveQualifier, "metrics": metrics}) + return constraints + + @classmethod + def has_metric_constraint(cls, resource, attribute): + constraints = tool.Resource.get_constraints(resource) + metrics = [] + for constraint in constraints: + for metric in tool.Resource.get_metrics(constraint) or []: + is_same_reference = bool( + tool.Resource.get_metric_reference(metric, is_deep=False) == attribute + or tool.Resource.get_metric_reference(metric, is_deep=True) == attribute + ) + if is_same_reference: + metrics.append(metric) + if metrics: + return metrics[0] + return None + + @classmethod + def has_usage_metric(cls, resource): + return cls.has_metric_constraint(resource, "Usage") diff --git a/src/blenderbim/test/bim/feature/resource.feature b/src/blenderbim/test/bim/feature/resource.feature index 836639f16f..71af9ae05a 100644 --- a/src/blenderbim/test/bim/feature/resource.feature +++ b/src/blenderbim/test/bim/feature/resource.feature @@ -249,7 +249,7 @@ Scenario: Add Productivity data When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})" And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()" And I set "scene.BIMResourceProperties.active_resource_index" to "1" - And I set "scene.BIMResourceProperties.should_show_productivity" to "True" + And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True" And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00" And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea" And I set "scene.BIMResourceProductivity.quantity_consumed[0].hours" to "5" @@ -286,7 +286,7 @@ Scenario: Calculate Resource Work When I press "bim.add_resource(ifc_class="IfcLaborResource", parent_resource={crew_resource})" And the variable "labor_resource" is "IfcStore.get_file().by_type('IfcLaborResource')[0].id()" And I set "scene.BIMResourceProperties.active_resource_index" to "1" - And I set "scene.BIMResourceProperties.should_show_productivity" to "True" + And I set "scene.BIMResourceProperties.should_show_resource_tools" to "True" And I set "scene.BIMResourceProductivity.quantity_produced" to "5.00" And I set "scene.BIMResourceProductivity.quantity_produced_name" to "GrossSideArea" And I set "scene.BIMResourceProductivity.quantity_consumed[0].hours" to "5"