You can now save, load, and create custom colour legends of properties

This commit is contained in:
Dion Moult
2023-07-23 15:20:14 +10:00
parent b1cab9484c
commit dd827bb4c5
8 changed files with 246 additions and 35 deletions
@@ -28,7 +28,7 @@ class BIM_PT_groups(Panel):
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_project_setup"
bl_parent_id = "BIM_PT_tab_grouping_and_filtering"
@classmethod
def poll(cls, context):
@@ -25,17 +25,20 @@ classes = (
operator.AddFilter,
operator.AddFilterGroup,
operator.AddToIfcGroup,
operator.ColourByProperty,
operator.ColourByAttribute,
operator.ColourByClass,
operator.ColourByPset,
operator.FilterModelElements,
operator.IfcSelector,
operator.LoadColourscheme,
operator.LoadQuery,
operator.LoadSearch,
operator.OpenQueryLibrary,
operator.RemoveFilter,
operator.RemoveFilterGroup,
operator.ResetObjectColours,
operator.SaveColourscheme,
operator.SaveSearch,
operator.SaveSelectorQuery,
operator.Search,
@@ -45,6 +48,7 @@ classes = (
operator.SelectPset,
operator.ShowAllElements,
operator.ToggleFilterSelection,
prop.BIMColour,
prop.BIMFacet,
prop.BIMFilterGroup,
prop.BIMFilterClasses,
@@ -56,6 +60,8 @@ classes = (
prop.SearchQueryGroup,
prop.IfcSelectorProperties,
ui.BIM_PT_search,
ui.BIM_PT_colour_by_property,
ui.BIM_UL_colourscheme,
ui.BIM_UL_ifc_class_filter,
ui.BIM_UL_ifc_building_storey_filter,
ui.BIM_PT_IFCSelector,
@@ -23,6 +23,7 @@ import blenderbim.tool as tool
def refresh():
SearchData.is_loaded = False
ColourByPropertyData.is_loaded = False
class SearchData:
@@ -47,3 +48,31 @@ class SearchData:
except:
pass
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
class ColourByPropertyData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {}
cls.data["saved_colourschemes"] = cls.saved_colourschemes()
@classmethod
def saved_colourschemes(cls):
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("colourscheme", 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")]
@@ -149,7 +149,11 @@ class SaveSearch(Operator, tool.Ifc.Operator):
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
description = json.dumps({"type": "BBIM_Search", "query": query})
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
if group:
group = group[0]
else:
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=results, group=group)
def invoke(self, context, event):
@@ -176,6 +180,121 @@ class LoadSearch(Operator, tool.Ifc.Operator):
return context.window_manager.invoke_props_dialog(self)
class ColourByProperty(Operator):
bl_idname = "bim.colour_by_property"
bl_label = "Colour by Property"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
IfcStore.begin_transaction(self)
self.store_state(context)
result = self._execute(context)
IfcStore.add_transaction_operation(self)
IfcStore.end_transaction(self)
return result
def _execute(self, context):
props = context.scene.BIMSearchProperties
query = props.colourscheme_query
if not query:
self.report({"ERROR"}, "No Query Provided")
return {"CANCELLED"}
colours = cycle(colour_list)
colourscheme = {}
if len(props.colourscheme):
colourscheme = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
for obj in context.visible_objects:
element = tool.Ifc.get_entity(obj)
if not element:
continue
value = str(ifcopenshell.util.selector.get_element_value(element, query))
if value not in colourscheme:
colourscheme[value] = next(colours)[0:3]
obj.color = (*colourscheme[value], 1)
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
areas[0].spaces[0].shading.color_type = "OBJECT"
props.colourscheme.clear()
for value, colour in colourscheme.items():
new = props.colourscheme.add()
new.name = str(value)
new.colour = colour[0:3]
return {"FINISHED"}
def store_state(self, context):
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
if areas:
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
def rollback(self, data):
if data:
data["area"].spaces[0].shading.color_type = data["color_type"]
def commit(self, data):
if data:
data["area"].spaces[0].shading.color_type = "OBJECT"
class SaveColourscheme(Operator, tool.Ifc.Operator):
bl_idname = "bim.save_colourscheme"
bl_label = "Save Colourscheme"
bl_description = "Save colourscheme to an IFC group"
bl_options = {"REGISTER", "UNDO"}
name: StringProperty(name="Name")
def _execute(self, context):
if not self.name:
return
props = context.scene.BIMSearchProperties
query = props.colourscheme_query
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
if group:
group = group[0]
description = json.loads(group.Description)
description["colourscheme"] = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
description["colourscheme_query"] = query
group.Description = json.dumps(description)
else:
description = json.dumps({"type": "BBIM_Search", "colourscheme": query})
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class LoadColourscheme(Operator, tool.Ifc.Operator):
bl_idname = "bim.load_colourscheme"
bl_label = "Load Colourscheme"
bl_description = "Load colourscheme from an IFC group"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMSearchProperties
group = tool.Ifc.get().by_id(int(props.saved_searches))
description = json.loads(group.Description)
props.colourscheme_query = description.get("colourscheme_query")
props.colourscheme.clear()
for name, colour in description.get("colourscheme", {}).items():
new = props.colourscheme.add()
new.name = name
new.colour = colour
def draw(self, context):
props = context.scene.BIMSearchProperties
row = self.layout.row()
row.prop(props, "saved_colourschemes", text="")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class SelectGlobalId(Operator):
"""Click to select the objects that match with the given Global ID"""
@@ -503,14 +622,16 @@ class ColourByClass(Operator):
class ResetObjectColours(Operator):
"""Reset the colour of selected objects"""
"""Reset the colour of visible objects"""
bl_idname = "bim.reset_object_colours"
bl_label = "Reset Colours"
def execute(self, context):
for obj in context.selected_objects:
for obj in context.visible_objects:
obj.color = (1, 1, 1, 1)
props = context.scene.BIMSearchProperties
props.colourscheme.clear()
return {"FINISHED"}
@@ -22,7 +22,7 @@ from ifcopenshell.util.selector import Selector
import blenderbim.tool as tool
from blenderbim.bim.prop import ObjProperty, StrProperty
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.search.data import SearchData
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData
from bpy.types import PropertyGroup
from blenderbim.tool.ifc import Ifc
from . import ui, prop, operator
@@ -44,6 +44,12 @@ def get_saved_searches(self, context):
return SearchData.data["saved_searches"]
def get_saved_colourschemes(self, context):
if not ColourByPropertyData.is_loaded:
ColourByPropertyData.load()
return ColourByPropertyData.data["saved_colourschemes"]
def update_is_class_selected(self, context):
if self.is_selected:
for obj in self.unselected_objects:
@@ -87,9 +93,9 @@ class BIMFilterBuildingStoreys(PropertyGroup):
class BIMFacet(PropertyGroup):
name: StringProperty(name="Type")
pset: StringProperty(name="Type")
value: StringProperty(name="Type")
name: StringProperty(name="Name")
pset: StringProperty(name="Pset")
value: StringProperty(name="Value")
type: StringProperty(name="Type")
comparison: StringProperty(name="Comparison")
@@ -98,6 +104,11 @@ 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)
class BIMSearchProperties(PropertyGroup):
filter_query: StringProperty(name="Filter Query")
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
@@ -114,6 +125,10 @@ class BIMSearchProperties(PropertyGroup):
],
)
saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches")
saved_colourschemes: EnumProperty(items=get_saved_colourschemes, name="Saved Colourschemes")
colourscheme_query: StringProperty(name="Colourscheme Query", default="class")
colourscheme: CollectionProperty(type=BIMColour)
active_colourscheme_index: IntProperty(name="Active Colourscheme Index")
should_use_regex: BoolProperty(name="Search With Regex", default=False)
should_ignorecase: BoolProperty(name="Search Ignoring Case", default=True)
global_id: StringProperty(name="GlobalId")
@@ -18,7 +18,7 @@
import bpy
from bpy.types import Panel
from blenderbim.bim.module.search.data import SearchData
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData
class BIM_PT_search(Panel):
@@ -138,6 +138,49 @@ class BIM_PT_search(Panel):
row.operator("bim.activate_ifc_building_storey_filter", icon="FILTER")
class BIM_PT_colour_by_property(Panel):
bl_label = "Colour By Property"
bl_idname = "BIM_PT_colour_by_property"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_options = {"DEFAULT_CLOSED"}
bl_parent_id = "BIM_PT_tab_grouping_and_filtering"
def draw(self, context):
if not ColourByPropertyData.is_loaded:
ColourByPropertyData.load()
props = context.scene.BIMSearchProperties
row = self.layout.row(align=True)
row.label(text=f"{len(ColourByPropertyData.data['saved_colourschemes'])} Saved Colourschemes")
if ColourByPropertyData.data["saved_colourschemes"]:
row.operator("bim.load_colourscheme", text="", icon="IMPORT")
row.operator("bim.save_colourscheme", text="", icon="EXPORT")
row = self.layout.row()
row.prop(props, "colourscheme_query", text="Query")
row = self.layout.row(align=True)
row.operator("bim.colour_by_property", icon="BRUSH_DATA")
row.operator("bim.reset_object_colours")
if len(props.colourscheme):
self.layout.template_list("BIM_UL_colourscheme", "", props, "colourscheme", props, "active_colourscheme_index")
class BIM_UL_colourscheme(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
props = context.scene.BIMWorkScheduleProperties
if not item:
return
row = layout.row(align=True)
row.label(text=item.name)
row.prop(item, "colour", text="")
class BIM_UL_ifc_class_filter(bpy.types.UIList):
use_filter_linked: bpy.props.BoolProperty(name="Included", default=True, options=set(), description="Filter")
+1 -2
View File
@@ -12,9 +12,8 @@ class Search(blenderbim.core.tool.Search):
def import_filter_query(cls, group, filter_groups):
query = json.loads(group.Description)["query"]
filter_groups.clear()
l = lark.Lark(ifcopenshell.util.selector.filter_elements_grammar)
transformer = ImportFilterQueryTransformer(filter_groups)
transformer.transform(l.parse(query))
transformer.transform(ifcopenshell.util.selector.filter_elements_grammar.parse(query))
@classmethod
def export_filter_query(cls, filter_groups):
@@ -24,7 +24,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.classification
filter_elements_grammar = """start: filter_group
filter_elements_grammar = lark.Lark("""start: filter_group
filter_group: facet_list ("+" facet_list)*
facet_list: facet ("," facet)*
@@ -86,38 +86,36 @@ filter_elements_grammar = """start: filter_group
NEWLINE: (CR? LF)+
%ignore WS // Disregard spaces in text
"""
""")
get_element_grammar = lark.Lark("""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
// Embed common.lark for packaging
_STRING_INNER: /.*?/
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
LCASE_LETTER: "a".."z"
UCASE_LETTER: "A".."Z"
LETTER: UCASE_LETTER | LCASE_LETTER
WORD: LETTER+
WS: /[ \\t\\f\\r\\n]/+
%ignore WS // Disregard spaces in text
""")
def get_element_value(element, query):
l = lark.Lark(
"""start: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
// Embed common.lark for packaging
_STRING_INNER: /.*?/
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
LCASE_LETTER: "a".."z"
UCASE_LETTER: "A".."Z"
LETTER: UCASE_LETTER | LCASE_LETTER
WORD: LETTER+
WS: /[ \\t\\f\\r\\n]/+
%ignore WS // Disregard spaces in text
"""
)
start = l.parse(query)
start = get_element_grammar.parse(query)
filter_query = Selector.parse_filter_query(start.children[0])
return Selector.get_element_value(element, filter_query["keys"], filter_query["is_regex"])
def filter_elements(ifc_file, query, elements=None):
l = lark.Lark(filter_elements_grammar)
transformer = FacetTransformer(ifc_file, elements)
transformer.transform(l.parse(query))
transformer.transform(filter_elements_grammar.parse(query))
return transformer.get_results()
return transformer.elements