diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 3d8bf6b965..de25096754 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -146,7 +146,10 @@ classes = [ # Structural analysis ui.BIM_PT_tab_structural, # Construction scheduling - ui.BIM_PT_tab_4D5D, + ui.BIM_PT_tab_status, + ui.BIM_PT_tab_resources, + ui.BIM_PT_tab_cost, + ui.BIM_PT_tab_sequence, # Facility management ui.BIM_PT_tab_handover, ui.BIM_PT_tab_operations, diff --git a/src/blenderbim/blenderbim/bim/module/cost/ui.py b/src/blenderbim/blenderbim/bim/module/cost/ui.py index 0d00b12a71..ae7f17da8c 100644 --- a/src/blenderbim/blenderbim/bim/module/cost/ui.py +++ b/src/blenderbim/blenderbim/bim/module/cost/ui.py @@ -29,7 +29,8 @@ class BIM_PT_cost_schedules(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_cost" + bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/csv/prop.py b/src/blenderbim/blenderbim/bim/module/csv/prop.py index 96fc51e822..ba89b6aed7 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/prop.py +++ b/src/blenderbim/blenderbim/bim/module/csv/prop.py @@ -38,24 +38,24 @@ class CsvAttribute(PropertyGroup): sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")]) group: EnumProperty( items=[ - ("NONE", "None", ""), - ("GROUP", "Group", "All rows where this value is identical will be merged."), - ("CONCAT", "Concatenation", "Concatenate values if values vary within a group."), - ("VARIES", "Varies", "Show a custom value if values vary within a group."), - ("SUM", "Sum", "Sums the total value of rows in a group."), - ("AVERAGE", "Average", "Averages the total value of rows in a group."), - ("MIN", "Min", "Gets the minimum value of rows in a group."), - ("MAX", "Max", "Gets the maximum value of rows in a group."), + ("NONE", "None", "Don't group any rows"), + ("GROUP", "Group", "All rows where this value is identical will be merged"), + ("CONCAT", "Concatenation", "Concatenate values if values vary within a group"), + ("VARIES", "Varies", "Show a custom value if values vary within a group"), + ("SUM", "Sum", "Sums the total value of rows in a group"), + ("AVERAGE", "Average", "Averages the total value of rows in a group"), + ("MIN", "Min", "Gets the minimum value of rows in a group"), + ("MAX", "Max", "Gets the maximum value of rows in a group"), ] ) varies_value: StringProperty(default="Varies", name="Varies Value") summary: EnumProperty( items=[ - ("NONE", "None", ""), - ("SUM", "Sum", "Sums the total value of all rows."), - ("AVERAGE", "Average", "Averages the total value of all rows."), - ("MIN", "Min", "Gets the minimum value of all rows."), - ("MAX", "Max", "Gets the maximum value of all rows."), + ("NONE", "None", "Don't provide a summary row"), + ("SUM", "Sum", "Sums the total value of all rows"), + ("AVERAGE", "Average", "Averages the total value of all rows"), + ("MIN", "Min", "Gets the minimum value of all rows"), + ("MAX", "Max", "Gets the maximum value of all rows"), ] ) diff --git a/src/blenderbim/blenderbim/bim/module/resource/ui.py b/src/blenderbim/blenderbim/bim/module/resource/ui.py index 963b807ae6..65015ba254 100644 --- a/src/blenderbim/blenderbim/bim/module/resource/ui.py +++ b/src/blenderbim/blenderbim/bim/module/resource/ui.py @@ -28,7 +28,8 @@ class BIM_PT_resources(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_resources" + bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py index 23779b4616..c2f79dc7f3 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/__init__.py @@ -118,12 +118,18 @@ classes = ( operator.UnassignWorkSchedule, operator.VisualiseWorkScheduleDate, operator.VisualiseWorkScheduleDateRange, + operator.EnableStatusFilters, + operator.DisableStatusFilters, + operator.ActivateStatusFilters, + operator.SelectStatusFilter, prop.WorkPlan, prop.BIMWorkPlanProperties, prop.Task, prop.TaskResource, prop.TaskProduct, prop.ISODuration, + prop.IFCStatus, + prop.BIMStatusProperties, prop.BIMWorkScheduleProperties, prop.BIMTaskTreeProperties, prop.BIMTaskTypeColor, @@ -133,6 +139,7 @@ classes = ( prop.BIMWorkCalendarProperties, prop.DatePickerProperties, prop.BIMDateTextProperties, + ui.BIM_PT_status, ui.BIM_PT_work_plans, ui.BIM_PT_work_schedules, ui.BIM_PT_work_calendars, @@ -165,6 +172,7 @@ def menu_func_import(self, context): def register(): + bpy.types.Scene.BIMStatusProperties = bpy.props.PointerProperty(type=prop.BIMStatusProperties) bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties) bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties) bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties) @@ -177,6 +185,7 @@ def register(): def unregister(): + del bpy.types.Scene.BIMStatusProperties del bpy.types.Scene.BIMWorkPlanProperties del bpy.types.Scene.BIMWorkScheduleProperties del bpy.types.Scene.BIMTaskTreeProperties diff --git a/src/blenderbim/blenderbim/bim/module/sequence/operator.py b/src/blenderbim/blenderbim/bim/module/sequence/operator.py index 08b48c979a..e4ac5efa44 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/operator.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/operator.py @@ -27,11 +27,98 @@ import webbrowser import blenderbim.core.sequence as core import blenderbim.tool as tool import blenderbim.bim.module.sequence.helper as helper +import ifcopenshell.util.sequence +import ifcopenshell.util.selector from datetime import datetime from dateutil import parser, relativedelta from blenderbim.bim.ifc import IfcStore from bpy_extras.io_utils import ImportHelper -import ifcopenshell.util.sequence + + +class EnableStatusFilters(bpy.types.Operator): + bl_idname = "bim.enable_status_filters" + bl_label = "Enable Status Filters" + + def execute(self, context): + props = context.scene.BIMStatusProperties + props.is_enabled = True + + props.statuses.clear() + + statuses = set() + for element in tool.Ifc.get().by_type("IfcPropertyEnumeratedValue"): + if element.Name == "Status": + pset = element.PartOfPset[0] + if pset.Name.startswith("Pset_") and pset.Name.endswith("Common"): + statuses.update(element.EnumerationValues) + elif pset.Name == "EPset_Status": # Our secret sauce + statuses.update(element.EnumerationValues) + elif element.Name == "UserDefinedStatus": + statuses.add(element.NominalValue) + + statuses = ["No Status"] + sorted([s.wrappedValue for s in statuses]) + + for status in statuses: + new = props.statuses.add() + new.name = status + return {"FINISHED"} + + +class DisableStatusFilters(bpy.types.Operator): + bl_idname = "bim.disable_status_filters" + bl_label = "Disable Status Filters" + + def execute(self, context): + props = context.scene.BIMStatusProperties + props.is_enabled = False + return {"FINISHED"} + + +class ActivateStatusFilters(bpy.types.Operator): + bl_idname = "bim.activate_status_filters" + bl_label = "Activate Status Filters" + + def execute(self, context): + props = context.scene.BIMStatusProperties + + query = [] + visible_statuses = {s.name for s in props.statuses if s.is_visible} + for name in visible_statuses: + if name == "No Status": + q = f"IfcProduct, /Pset_.*Common/.Status=NULL, EPset_Status.Status=NULL" + else: + q = f"IfcProduct, /Pset_.*Common/.Status={name} + IfcProduct, EPset_Status.Status={name}" + query.append(q) + query = " + ".join(query) + + if not query: + return {"FINISHED"} + + visible_elements = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query) + + for obj in bpy.context.view_layer.objects: + element = tool.Ifc.get_entity(obj) + if not element or not element.is_a("IfcProduct"): + continue + obj.hide_set(element not in visible_elements) + return {"FINISHED"} + + +class SelectStatusFilter(bpy.types.Operator): + bl_idname = "bim.select_status_filter" + bl_label = "Select Status Filter" + name: bpy.props.StringProperty() + + def execute(self, context): + props = context.scene.BIMStatusProperties + query = f"IfcProduct, /Pset_.*Common/.Status={self.name} + IfcProduct, EPset_Status.Status={self.name}" + if self.name == "No Status": + query = f"IfcProduct, /Pset_.*Common/.Status=NULL, EPset_Status.Status=NULL" + for element in ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query): + obj = tool.Ifc.get_object(element) + if obj: + obj.select_set(True) + return {"FINISHED"} class AddWorkPlan(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/sequence/prop.py b/src/blenderbim/blenderbim/bim/module/sequence/prop.py index 711e783f9f..2de4293a0d 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/prop.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/prop.py @@ -381,6 +381,16 @@ class ISODuration(PropertyGroup): seconds: IntProperty(name="Seconds", default=0) +class IFCStatus(PropertyGroup): + name: StringProperty(name="Name") + is_visible: BoolProperty(name="Is Visible", default=True) + + +class BIMStatusProperties(PropertyGroup): + is_enabled: BoolProperty(name="Is Enabled") + statuses: CollectionProperty(name="Statuses", type=IFCStatus) + + class BIMWorkScheduleProperties(PropertyGroup): work_schedule_predefined_types: EnumProperty( items=get_schedule_predefined_types, name="Predefined Type", default=None diff --git a/src/blenderbim/blenderbim/bim/module/sequence/ui.py b/src/blenderbim/blenderbim/bim/module/sequence/ui.py index 5339fcae58..61f8a22311 100644 --- a/src/blenderbim/blenderbim/bim/module/sequence/ui.py +++ b/src/blenderbim/blenderbim/bim/module/sequence/ui.py @@ -30,6 +30,38 @@ from blenderbim.bim.module.sequence.data import ( ) +class BIM_PT_status(Panel): + bl_label = "Status" + bl_idname = "BIM_PT_status" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_parent_id = "BIM_PT_tab_status" + bl_options = {"HIDE_HEADER"} + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + self.props = context.scene.BIMStatusProperties + + if not self.props.is_enabled: + row = self.layout.row() + row.operator("bim.enable_status_filters", icon="GREASEPENCIL") + return + + row = self.layout.row(align=True) + row.operator("bim.activate_status_filters", icon="TIME") + row.operator("bim.disable_status_filters", icon="CANCEL", text="") + + for status in self.props.statuses: + row = self.layout.row() + row.label(text=status.name) + row.prop(status, "is_visible", text="", emboss=False, icon="HIDE_OFF" if status.is_visible else "HIDE_ON") + row.operator("bim.select_status_filter", icon="RESTRICT_SELECT_OFF", text="").name = status.name + + class BIM_PT_work_plans(Panel): bl_label = "Work Plans" bl_idname = "BIM_PT_work_plans" @@ -37,7 +69,7 @@ class BIM_PT_work_plans(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_sequence" @classmethod def poll(cls, context): @@ -107,7 +139,7 @@ class BIM_PT_work_schedules(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_sequence" @classmethod def poll(cls, context): @@ -550,7 +582,7 @@ class BIM_PT_animation_Color_Scheme(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_sequence" @classmethod def poll(cls, context): @@ -888,7 +920,7 @@ class BIM_PT_work_calendars(Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_parent_id = "BIM_PT_tab_4D5D" + bl_parent_id = "BIM_PT_tab_sequence" @classmethod def poll(cls, context): diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index 7a568e85fa..8526bffa49 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -408,12 +408,53 @@ class BIM_PT_geometry(Panel): pass -class BIM_PT_tab_4D5D(Panel): - bl_label = "Costing and Scheduling" +class BIM_PT_tab_status(Panel): + bl_label = "Status" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_resources(Panel): + bl_label = "Resources" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_cost(Panel): + bl_label = "Cost" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() + + def draw(self, context): + pass + + +class BIM_PT_tab_sequence(Panel): + bl_label = "Construction Scheduling" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "scene" - bl_options = {"HIDE_HEADER"} @classmethod def poll(cls, context):