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.BIMMaterialProperties,
prop.BIMMeshProperties,
prop.BIMFacet,
prop.BIMFilterGroup,
ui.BIM_UL_generic,
ui.BIM_UL_topics,
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)
def draw_filter(layout, props, data, module):
def draw_filter(layout, filter_groups, data, module):
if not data.is_loaded:
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.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()
row = box.row(align=True)
@@ -17,8 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from blenderbim.bim.module.search.prop import BIMFilterGroup
from blenderbim.bim.prop import StrProperty
from blenderbim.bim.prop import StrProperty, BIMFilterGroup
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -83,7 +83,7 @@ class BIM_PT_ifccsv(Panel):
row.prop(props, "concat_value")
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.operator("bim.add_csv_attribute", icon="ADD")
@@ -35,6 +35,7 @@ classes = (
operator.AddScheduleToSheet,
operator.AddSheet,
operator.AddTextLiteral,
operator.AssignSelectedObjectAsProduct,
operator.BuildSchedule,
operator.CleanWireframes,
operator.ContractSheet,
@@ -49,13 +50,14 @@ classes = (
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DuplicateDrawing,
operator.AssignSelectedObjectAsProduct,
operator.EditAssignedProduct,
operator.EditElementFilter,
operator.EditSheet,
operator.EditText,
operator.EditTextPopup,
operator.EnableAddAnnotationType,
operator.EnableEditingAssignedProduct,
operator.EnableEditingElementFilter,
operator.EnableEditingText,
operator.ExpandSheet,
operator.ExpandTargetView,
@@ -96,6 +98,7 @@ classes = (
ui.BIM_PT_sheets,
ui.BIM_PT_drawings,
ui.BIM_PT_camera,
ui.BIM_PT_element_filters,
ui.BIM_PT_drawing_underlay,
ui.BIM_PT_schedules,
ui.BIM_PT_references,
@@ -29,6 +29,7 @@ def refresh():
SheetsData.is_loaded = False
DocumentsData.is_loaded = False
DrawingsData.is_loaded = False
ElementFiltersData.is_loaded = False
AnnotationData.is_loaded = False
DecoratorData.data = {}
DecoratorData.cut_cache = {}
@@ -137,6 +138,55 @@ class DrawingsData:
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:
data = {}
is_loaded = False
@@ -2527,3 +2527,36 @@ class SelectAssignedProduct(bpy.types.Operator, Operator):
def _execute(self, 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.bim.module.drawing.annotation as annotation
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 refresh as refresh_drawing_data
from pathlib import Path
@@ -379,6 +380,9 @@ class BIMCameraProperties(PropertyGroup):
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
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"
# 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,
DocumentsData,
DrawingsData,
ElementFiltersData,
DecoratorData,
)
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]
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):
bl_label = "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_z"])
class BIM_PT_derived_coordinates(Panel):
bl_label = "Derived Coordinates"
bl_idname = "BIM_PT_derived_coordinates"
@@ -48,8 +48,6 @@ classes = (
operator.ShowAllElements,
operator.ToggleFilterSelection,
prop.BIMColour,
prop.BIMFacet,
prop.BIMFilterGroup,
prop.BIMFilterClasses,
prop.BIMFilterBuildingStoreys,
prop.BIMSearchProperties,
@@ -64,11 +64,8 @@ class AddFilterGroup(Operator):
module: StringProperty()
def execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups.add()
filter_groups = tool.Search.get_filter_groups(self.module)
filter_groups.add()
return {"FINISHED"}
@@ -79,11 +76,8 @@ class RemoveFilterGroup(Operator):
module: StringProperty()
def execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups.remove(self.index)
filter_groups = tool.Search.get_filter_groups(self.module)
filter_groups.remove(self.index)
return {"FINISHED"}
@@ -95,11 +89,8 @@ class RemoveFilter(Operator):
module: StringProperty()
def execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
props.filter_groups[self.group_index].filters.remove(self.index)
filter_groups = tool.Search.get_filter_groups(self.module)
filter_groups[self.group_index].filters.remove(self.index)
return {"FINISHED"}
@@ -111,11 +102,8 @@ class AddFilter(Operator):
module: StringProperty()
def execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
new = props.filter_groups[self.index].filters.add()
filter_groups = tool.Search.get_filter_groups(self.module)
new = filter_groups[self.index].filters.add()
new.type = self.type
return {"FINISHED"}
@@ -129,10 +117,7 @@ class SelectFilterElements(bpy.types.Operator):
module: StringProperty()
def execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
filter_groups = tool.Search.get_filter_groups(self.module)
global_ids = []
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -145,9 +130,9 @@ class SelectFilterElements(bpy.types.Operator):
name = "globalid-filter-" + ifcopenshell.guid.new()
text_data = bpy.data.texts.new(name)
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:
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"}
@@ -163,13 +148,9 @@ class EditFilterQuery(Operator, tool.Ifc.Operator):
if self.query == self.old_query:
return
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
filter_groups = tool.Search.get_filter_groups(self.module)
try:
tool.Search.import_filter_query(self.query, props.filter_groups)
tool.Search.import_filter_query(self.query, filter_groups)
except:
return
@@ -178,12 +159,9 @@ class EditFilterQuery(Operator, tool.Ifc.Operator):
row.prop(self, "query", text="")
def invoke(self, context, event):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
filter_groups = tool.Search.get_filter_groups(self.module)
self.query = tool.Search.export_filter_query(props.filter_groups)
self.query = tool.Search.export_filter_query(filter_groups)
self.old_query = self.query
return context.window_manager.invoke_props_dialog(self)
@@ -220,13 +198,10 @@ class SaveSearch(Operator, tool.Ifc.Operator):
if not self.name:
return
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
filter_groups = tool.Search.get_filter_groups(self.module)
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)
except:
return
@@ -257,12 +232,9 @@ class LoadSearch(Operator, tool.Ifc.Operator):
module: StringProperty()
def _execute(self, context):
if self.module == "search":
props = context.scene.BIMSearchProperties
elif self.module == "csv":
props = context.scene.CsvProperties
filter_groups = tool.Search.get_filter_groups(self.module)
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):
props = context.scene.BIMSearchProperties
@@ -20,7 +20,7 @@ import bpy
import blenderbim.tool as tool
from ifcopenshell import util
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.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
from bpy.types import PropertyGroup
@@ -98,18 +98,6 @@ class BIMFilterBuildingStoreys(PropertyGroup):
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):
name: StringProperty(name="Name")
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
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):
row = self.layout.row(align=True)
+13
View File
@@ -456,3 +456,16 @@ class BIMMeshProperties(PropertyGroup):
ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter)
material_checksum: StringProperty(name="Material 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):
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
def import_filter_query(cls, query, filter_groups):
filter_groups.clear()