New graphical element filtering UI for drawing filters that lets you also use saved searches.

This commit is contained in:
Dion Moult
2023-11-22 15:33:30 +11:00
parent 10a4fcfa10
commit b84cb72e1d
16 changed files with 194 additions and 69 deletions
@@ -122,6 +122,8 @@ classes = [
prop.BIMCollectionProperties, prop.BIMCollectionProperties,
prop.BIMMaterialProperties, prop.BIMMaterialProperties,
prop.BIMMeshProperties, prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
ui.BIM_UL_generic, ui.BIM_UL_generic,
ui.BIM_UL_topics, ui.BIM_UL_topics,
ui.BIM_ADDON_preferences, ui.BIM_ADDON_preferences,
+2 -2
View File
@@ -268,7 +268,7 @@ def convert_property_group_from_si(property_group, skip_props=()):
setattr(property_group, prop_name, prop_value) setattr(property_group, prop_name, prop_value)
def draw_filter(layout, props, data, module): def draw_filter(layout, filter_groups, data, module):
if not data.is_loaded: if not data.is_loaded:
data.load() data.load()
@@ -286,7 +286,7 @@ def draw_filter(layout, props, data, module):
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
for i, filter_group in enumerate(props.filter_groups): for i, filter_group in enumerate(filter_groups):
box = layout.box() box = layout.box()
row = box.row(align=True) row = box.row(align=True)
@@ -17,8 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from blenderbim.bim.module.search.prop import BIMFilterGroup from blenderbim.bim.prop import StrProperty, BIMFilterGroup
from blenderbim.bim.prop import StrProperty
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -83,7 +83,7 @@ class BIM_PT_ifccsv(Panel):
row.prop(props, "concat_value") row.prop(props, "concat_value")
layout.use_property_split = False layout.use_property_split = False
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv") blenderbim.bim.helper.draw_filter(self.layout, props.filter_groups, SearchData, "csv")
row = layout.row(align=True) row = layout.row(align=True)
row.operator("bim.add_csv_attribute", icon="ADD") row.operator("bim.add_csv_attribute", icon="ADD")
@@ -35,6 +35,7 @@ classes = (
operator.AddScheduleToSheet, operator.AddScheduleToSheet,
operator.AddSheet, operator.AddSheet,
operator.AddTextLiteral, operator.AddTextLiteral,
operator.AssignSelectedObjectAsProduct,
operator.BuildSchedule, operator.BuildSchedule,
operator.CleanWireframes, operator.CleanWireframes,
operator.ContractSheet, operator.ContractSheet,
@@ -49,13 +50,14 @@ classes = (
operator.DisableEditingSheets, operator.DisableEditingSheets,
operator.DisableEditingText, operator.DisableEditingText,
operator.DuplicateDrawing, operator.DuplicateDrawing,
operator.AssignSelectedObjectAsProduct,
operator.EditAssignedProduct, operator.EditAssignedProduct,
operator.EditElementFilter,
operator.EditSheet, operator.EditSheet,
operator.EditText, operator.EditText,
operator.EditTextPopup, operator.EditTextPopup,
operator.EnableAddAnnotationType, operator.EnableAddAnnotationType,
operator.EnableEditingAssignedProduct, operator.EnableEditingAssignedProduct,
operator.EnableEditingElementFilter,
operator.EnableEditingText, operator.EnableEditingText,
operator.ExpandSheet, operator.ExpandSheet,
operator.ExpandTargetView, operator.ExpandTargetView,
@@ -96,6 +98,7 @@ classes = (
ui.BIM_PT_sheets, ui.BIM_PT_sheets,
ui.BIM_PT_drawings, ui.BIM_PT_drawings,
ui.BIM_PT_camera, ui.BIM_PT_camera,
ui.BIM_PT_element_filters,
ui.BIM_PT_drawing_underlay, ui.BIM_PT_drawing_underlay,
ui.BIM_PT_schedules, ui.BIM_PT_schedules,
ui.BIM_PT_references, ui.BIM_PT_references,
@@ -29,6 +29,7 @@ def refresh():
SheetsData.is_loaded = False SheetsData.is_loaded = False
DocumentsData.is_loaded = False DocumentsData.is_loaded = False
DrawingsData.is_loaded = False DrawingsData.is_loaded = False
ElementFiltersData.is_loaded = False
AnnotationData.is_loaded = False AnnotationData.is_loaded = False
DecoratorData.data = {} DecoratorData.data = {}
DecoratorData.cut_cache = {} DecoratorData.cut_cache = {}
@@ -137,6 +138,55 @@ class DrawingsData:
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing") return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
class ElementFiltersData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"saved_searches": cls.saved_searches(),
"has_include_filter": cls.has_include_filter(),
"has_exclude_filter": cls.has_exclude_filter(),
}
cls.is_loaded = True
@classmethod
def saved_searches(cls):
if not tool.Ifc.get():
return []
groups = tool.Ifc.get().by_type("IfcGroup")
results = []
for group in groups:
try:
data = json.loads(group.Description)
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
results.append(group)
except:
pass
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
@classmethod
def has_include_filter(cls):
obj = bpy.context.scene.camera
if not obj:
return
element = tool.Ifc.get_entity(obj)
if not element:
return
return bool(ifcopenshell.util.element.get_pset(element, "EPset_Drawing", "Include"))
@classmethod
def has_exclude_filter(cls):
obj = bpy.context.scene.camera
if not obj:
return
element = tool.Ifc.get_entity(obj)
if not element:
return
return bool(ifcopenshell.util.element.get_pset(element, "EPset_Drawing", "Exclude"))
class DocumentsData: class DocumentsData:
data = {} data = {}
is_loaded = False is_loaded = False
@@ -2527,3 +2527,36 @@ class SelectAssignedProduct(bpy.types.Operator, Operator):
def _execute(self, context): def _execute(self, context):
core.select_assigned_product(tool.Drawing, context) core.select_assigned_product(tool.Drawing, context)
class EnableEditingElementFilter(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_element_filter"
bl_label = "Enable Editing Element Filter"
bl_options = {"REGISTER", "UNDO"}
filter_mode: bpy.props.StringProperty()
def _execute(self, context):
obj = bpy.context.scene.camera
if obj:
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
class EditElementFilter(bpy.types.Operator, Operator):
bl_idname = "bim.edit_element_filter"
bl_label = "Edit Element Filter"
bl_options = {"REGISTER", "UNDO"}
filter_mode: bpy.props.StringProperty()
def _execute(self, context):
props = context.active_object.data.BIMCameraProperties
obj = bpy.context.scene.camera
element = tool.Ifc.get_entity(obj)
pset = tool.Pset.get_element_pset(element, "EPset_Drawing")
if self.filter_mode == "INCLUDE":
query = tool.Search.export_filter_query(props.include_filter_groups) or None
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Include": query})
elif self.filter_mode == "EXCLUDE":
query = tool.Search.export_filter_query(props.exclude_filter_groups) or None
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Exclude": query})
obj.data.BIMCameraProperties.filter_mode = "NONE"
bpy.ops.bim.activate_drawing(drawing=element.id(), camera_view_point=False)
@@ -25,6 +25,7 @@ import blenderbim.tool as tool
import blenderbim.core.drawing as core import blenderbim.core.drawing as core
import blenderbim.bim.module.drawing.annotation as annotation import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.decoration as decoration import blenderbim.bim.module.drawing.decoration as decoration
from blenderbim.bim.prop import BIMFilterGroup
from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData from blenderbim.bim.module.drawing.data import DrawingsData, DecoratorData, SheetsData, AnnotationData
from blenderbim.bim.module.drawing.data import refresh as refresh_drawing_data from blenderbim.bim.module.drawing.data import refresh as refresh_drawing_data
from pathlib import Path from pathlib import Path
@@ -379,6 +380,9 @@ class BIMCameraProperties(PropertyGroup):
height: FloatProperty(name="Height", default=50, subtype="DISTANCE") height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
is_nts: BoolProperty(name="Is NTS", update=update_is_nts) is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
active_drawing_style_index: IntProperty(name="Active Drawing Style Index") active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
filter_mode: StringProperty(name="Filter Mode", default="NONE")
include_filter_groups: CollectionProperty(type=BIMFilterGroup, name="Include Filter")
exclude_filter_groups: CollectionProperty(type=BIMFilterGroup, name="Exclude Filter")
# For now, this JSON dump are all the parameters that determine a camera's "Block representation" # For now, this JSON dump are all the parameters that determine a camera's "Block representation"
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed # By checking this, you will know whether or not the camera IFC representation needs to be refreshed
@@ -25,6 +25,7 @@ from blenderbim.bim.module.drawing.data import (
SheetsData, SheetsData,
DocumentsData, DocumentsData,
DrawingsData, DrawingsData,
ElementFiltersData,
DecoratorData, DecoratorData,
) )
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
@@ -95,6 +96,56 @@ class BIM_PT_camera(Panel):
op.view = context.scene.camera.name.split("/")[1] op.view = context.scene.camera.name.split("/")[1]
class BIM_PT_element_filters(Panel):
bl_label = "Element Filters"
bl_idname = "BIM_PT_element_filters"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_camera"
@classmethod
def poll(cls, context):
return context.scene.camera and hasattr(context.active_object.data, "BIMCameraProperties")
def draw(self, context):
if not ElementFiltersData.is_loaded:
ElementFiltersData.load()
props = context.scene.camera.data.BIMCameraProperties
if props.filter_mode == "INCLUDE":
blenderbim.bim.helper.draw_filter(
self.layout, props.include_filter_groups, ElementFiltersData, "drawing_include"
)
row = self.layout.row(align=True)
row.operator(
"bim.edit_element_filter", icon="CHECKMARK", text="Save Include Filter"
).filter_mode = "INCLUDE"
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
elif props.filter_mode == "EXCLUDE":
blenderbim.bim.helper.draw_filter(
self.layout, props.exclude_filter_groups, ElementFiltersData, "drawing_exclude"
)
row = self.layout.row(align=True)
row.operator(
"bim.edit_element_filter", icon="CHECKMARK", text="Save Exclude Filter"
).filter_mode = "EXCLUDE"
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
else:
row = self.layout.row(align=True)
text = "Include Filter" if ElementFiltersData.data["has_include_filter"] else "No Include Filter Found"
icon = "GREASEPENCIL" if ElementFiltersData.data["has_include_filter"] else "ADD"
row.label(text=text, icon="FILTER")
row.operator("bim.enable_editing_element_filter", icon=icon, text="").filter_mode = "INCLUDE"
row = self.layout.row(align=True)
text = "Exclude Filter" if ElementFiltersData.data["has_exclude_filter"] else "No Exclude Filter Found"
icon = "GREASEPENCIL" if ElementFiltersData.data["has_exclude_filter"] else "ADD"
row.label(text=text, icon="FILTER")
row.operator("bim.enable_editing_element_filter", icon=icon, text="").filter_mode = "EXCLUDE"
class BIM_PT_drawing_underlay(Panel): class BIM_PT_drawing_underlay(Panel):
bl_label = "Drawing Underlay" bl_label = "Drawing Underlay"
bl_idname = "BIM_PT_drawing_underlay" bl_idname = "BIM_PT_drawing_underlay"
@@ -376,6 +376,7 @@ class BIM_PT_placement(Panel):
row.label(text=PlacementData.data["original_y"]) row.label(text=PlacementData.data["original_y"])
row.label(text=PlacementData.data["original_z"]) row.label(text=PlacementData.data["original_z"])
class BIM_PT_derived_coordinates(Panel): class BIM_PT_derived_coordinates(Panel):
bl_label = "Derived Coordinates" bl_label = "Derived Coordinates"
bl_idname = "BIM_PT_derived_coordinates" bl_idname = "BIM_PT_derived_coordinates"
@@ -48,8 +48,6 @@ classes = (
operator.ShowAllElements, operator.ShowAllElements,
operator.ToggleFilterSelection, operator.ToggleFilterSelection,
prop.BIMColour, prop.BIMColour,
prop.BIMFacet,
prop.BIMFilterGroup,
prop.BIMFilterClasses, prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys, prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties, prop.BIMSearchProperties,
@@ -64,11 +64,8 @@ class AddFilterGroup(Operator):
module: StringProperty() module: StringProperty()
def execute(self, context): def execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties filter_groups.add()
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups.add()
return {"FINISHED"} return {"FINISHED"}
@@ -79,11 +76,8 @@ class RemoveFilterGroup(Operator):
module: StringProperty() module: StringProperty()
def execute(self, context): def execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties filter_groups.remove(self.index)
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -95,11 +89,8 @@ class RemoveFilter(Operator):
module: StringProperty() module: StringProperty()
def execute(self, context): def execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties filter_groups[self.group_index].filters.remove(self.index)
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups[self.group_index].filters.remove(self.index)
return {"FINISHED"} return {"FINISHED"}
@@ -111,11 +102,8 @@ class AddFilter(Operator):
module: StringProperty() module: StringProperty()
def execute(self, context): def execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties new = filter_groups[self.index].filters.add()
elif self.module == "csv":
props = context.scene.CsvProperties
new = props.filter_groups[self.index].filters.add()
new.type = self.type new.type = self.type
return {"FINISHED"} return {"FINISHED"}
@@ -129,10 +117,7 @@ class SelectFilterElements(bpy.types.Operator):
module: StringProperty() module: StringProperty()
def execute(self, context): def execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
global_ids = [] global_ids = []
for obj in context.selected_objects: for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
@@ -145,9 +130,9 @@ class SelectFilterElements(bpy.types.Operator):
name = "globalid-filter-" + ifcopenshell.guid.new() name = "globalid-filter-" + ifcopenshell.guid.new()
text_data = bpy.data.texts.new(name) text_data = bpy.data.texts.new(name)
text_data.from_string(",".join(global_ids)) text_data.from_string(",".join(global_ids))
props.filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']" filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']"
else: else:
props.filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids) filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids)
return {"FINISHED"} return {"FINISHED"}
@@ -163,13 +148,9 @@ class EditFilterQuery(Operator, tool.Ifc.Operator):
if self.query == self.old_query: if self.query == self.old_query:
return return
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
try: try:
tool.Search.import_filter_query(self.query, props.filter_groups) tool.Search.import_filter_query(self.query, filter_groups)
except: except:
return return
@@ -178,12 +159,9 @@ class EditFilterQuery(Operator, tool.Ifc.Operator):
row.prop(self, "query", text="") row.prop(self, "query", text="")
def invoke(self, context, event): def invoke(self, context, event):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
self.query = tool.Search.export_filter_query(props.filter_groups) self.query = tool.Search.export_filter_query(filter_groups)
self.old_query = self.query self.old_query = self.query
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
@@ -220,13 +198,10 @@ class SaveSearch(Operator, tool.Ifc.Operator):
if not self.name: if not self.name:
return return
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
try: try:
query = tool.Search.export_filter_query(props.filter_groups) query = tool.Search.export_filter_query(filter_groups)
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query) results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
except: except:
return return
@@ -257,12 +232,9 @@ class LoadSearch(Operator, tool.Ifc.Operator):
module: StringProperty() module: StringProperty()
def _execute(self, context): def _execute(self, context):
if self.module == "search": filter_groups = tool.Search.get_filter_groups(self.module)
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches)) group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches))
query = tool.Search.import_filter_query(tool.Search.get_group_query(group), props.filter_groups) query = tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
def draw(self, context): def draw(self, context):
props = context.scene.BIMSearchProperties props = context.scene.BIMSearchProperties
@@ -20,7 +20,7 @@ import bpy
import blenderbim.tool as tool import blenderbim.tool as tool
from ifcopenshell import util from ifcopenshell import util
from ifcopenshell.util.selector import Selector from ifcopenshell.util.selector import Selector
from blenderbim.bim.prop import ObjProperty, StrProperty from blenderbim.bim.prop import ObjProperty, StrProperty, BIMFilterGroup
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -98,18 +98,6 @@ class BIMFilterBuildingStoreys(PropertyGroup):
unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects") unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects")
class BIMFacet(PropertyGroup):
name: StringProperty(name="Name")
pset: StringProperty(name="Pset")
value: StringProperty(name="Value")
type: StringProperty(name="Type")
comparison: StringProperty(name="Comparison")
class BIMFilterGroup(PropertyGroup):
filters: CollectionProperty(type=BIMFacet, name="filters")
class BIMColour(PropertyGroup): class BIMColour(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
colour: FloatVectorProperty(name="Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0) colour: FloatVectorProperty(name="Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0)
@@ -36,7 +36,7 @@ class BIM_PT_search(Panel):
props = context.scene.BIMSearchProperties props = context.scene.BIMSearchProperties
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "search") blenderbim.bim.helper.draw_filter(self.layout, props.filter_groups, SearchData, "search")
if len(props.filter_groups): if len(props.filter_groups):
row = self.layout.row(align=True) row = self.layout.row(align=True)
+13
View File
@@ -456,3 +456,16 @@ class BIMMeshProperties(PropertyGroup):
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
material_checksum: StringProperty(name="Material Checksum", default="[]") material_checksum: StringProperty(name="Material Checksum", default="[]")
mesh_checksum: StringProperty(name="Mesh Checksum", default="") mesh_checksum: StringProperty(name="Mesh Checksum", default="")
class BIMFacet(PropertyGroup):
name: StringProperty(name="Name")
pset: StringProperty(name="Pset")
value: StringProperty(name="Value")
type: StringProperty(name="Type")
comparison: StringProperty(name="Comparison")
class BIMFilterGroup(PropertyGroup):
filters: CollectionProperty(type=BIMFacet, name="filters")
+11
View File
@@ -12,6 +12,17 @@ class Search(blenderbim.core.tool.Search):
def get_group_query(cls, group): def get_group_query(cls, group):
return json.loads(group.Description)["query"] return json.loads(group.Description)["query"]
@classmethod
def get_filter_groups(cls, module):
if module == "search":
return bpy.context.scene.BIMSearchProperties.filter_groups
elif module == "csv":
return bpy.context.scene.CsvProperties.filter_groups
elif module == "drawing_include":
return bpy.context.active_object.data.BIMCameraProperties.include_filter_groups
elif module == "drawing_exclude":
return bpy.context.active_object.data.BIMCameraProperties.exclude_filter_groups
@classmethod @classmethod
def import_filter_query(cls, query, filter_groups): def import_filter_query(cls, query, filter_groups):
filter_groups.clear() filter_groups.clear()