mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
You can now save, load, and create custom colour legends of properties
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user