mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
Add support for colouring by quantitative colour legend, not just qualitative categories
This commit is contained in:
@@ -18,15 +18,16 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import json
|
import json
|
||||||
|
import bisect
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.ifc import IfcStore
|
|
||||||
import bonsai.core.search as core
|
import bonsai.core.search as core
|
||||||
from itertools import cycle
|
from bonsai.bim.ifc import IfcStore
|
||||||
|
from natsort import natsorted
|
||||||
from bpy.types import PropertyGroup, Operator
|
from bpy.types import PropertyGroup, Operator
|
||||||
from bpy.props import (
|
from bpy.props import (
|
||||||
PointerProperty,
|
PointerProperty,
|
||||||
@@ -40,22 +41,6 @@ from bpy.props import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
colour_list = [
|
|
||||||
(0.651, 0.81, 0.892, 1),
|
|
||||||
(0.121, 0.471, 0.706, 1),
|
|
||||||
(0.699, 0.876, 0.54, 1),
|
|
||||||
(0.199, 0.629, 0.174, 1),
|
|
||||||
(0.983, 0.605, 0.602, 1),
|
|
||||||
(0.89, 0.101, 0.112, 1),
|
|
||||||
(0.989, 0.751, 0.427, 1),
|
|
||||||
(0.986, 0.497, 0.1, 1),
|
|
||||||
(0.792, 0.699, 0.839, 1),
|
|
||||||
(0.414, 0.239, 0.603, 1),
|
|
||||||
(0.993, 0.999, 0.6, 1),
|
|
||||||
(0.693, 0.349, 0.157, 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AddFilterGroup(Operator):
|
class AddFilterGroup(Operator):
|
||||||
bl_idname = "bim.add_filter_group"
|
bl_idname = "bim.add_filter_group"
|
||||||
bl_label = "Add Filter Group"
|
bl_label = "Add Filter Group"
|
||||||
@@ -270,27 +255,74 @@ class ColourByProperty(Operator):
|
|||||||
self.report({"ERROR"}, "No Query Provided")
|
self.report({"ERROR"}, "No Query Provided")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
colours = cycle(colour_list)
|
is_qualitative = props.pallette in ("tab10", "paired")
|
||||||
|
|
||||||
|
if is_qualitative:
|
||||||
|
colours = tool.Search.get_qualitative_pallette(props.pallette)
|
||||||
|
|
||||||
colourscheme = {}
|
colourscheme = {}
|
||||||
|
|
||||||
if len(props.colourscheme):
|
obj_values = {}
|
||||||
|
min_value = None
|
||||||
|
max_value = None
|
||||||
|
|
||||||
|
if is_qualitative and len(props.colourscheme):
|
||||||
colourscheme = {cs.name: {"colour": cs.colour[0:3], "total": 0} for cs in props.colourscheme}
|
colourscheme = {cs.name: {"colour": cs.colour[0:3], "total": 0} for cs in props.colourscheme}
|
||||||
|
|
||||||
for obj in context.visible_objects:
|
for obj in context.visible_objects:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
continue
|
continue
|
||||||
value = str(ifcopenshell.util.selector.get_element_value(element, query))
|
value = ifcopenshell.util.selector.get_element_value(element, query)
|
||||||
if value in colourscheme:
|
if is_qualitative:
|
||||||
colourscheme[value]["total"] += 1
|
value = str(value)
|
||||||
|
if value in colourscheme:
|
||||||
|
colourscheme[value]["total"] += 1
|
||||||
|
else:
|
||||||
|
colourscheme[value] = {"colour": next(colours)[0:3], "total": 1}
|
||||||
|
obj.color = (*colourscheme[value]["colour"], 1)
|
||||||
else:
|
else:
|
||||||
colourscheme[value] = {"colour": next(colours)[0:3], "total": 1}
|
if value is None:
|
||||||
obj.color = (*colourscheme[value]["colour"], 1)
|
obj.color = (0, 0, 0, 1)
|
||||||
|
if "None" in colourscheme:
|
||||||
|
colourscheme["None"]["total"] += 1
|
||||||
|
else:
|
||||||
|
colourscheme["None"] = {"colour": (0, 0, 0), "total": 1}
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
if min_value is None or value < min_value:
|
||||||
|
min_value = value
|
||||||
|
if max_value is None or value > max_value:
|
||||||
|
max_value = value
|
||||||
|
obj_values[obj] = value
|
||||||
|
except:
|
||||||
|
obj.color = (0, 0, 0, 1)
|
||||||
|
|
||||||
|
if not is_qualitative:
|
||||||
|
steps = 10
|
||||||
|
step_size = (max_value - min_value) / (steps - 1)
|
||||||
|
values = []
|
||||||
|
for i in range(steps):
|
||||||
|
step_value = min_value + i * step_size
|
||||||
|
values.append(step_value)
|
||||||
|
colourscheme[str(step_value)] = {
|
||||||
|
"colour": tool.Search.get_quantitative_pallette("foo", step_value, min_value, max_value),
|
||||||
|
"total": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for obj, value in obj_values.items():
|
||||||
|
index = bisect.bisect_right(values, value)
|
||||||
|
if index >= len(values):
|
||||||
|
index = -1
|
||||||
|
colourscheme[str(values[index])]["total"] += 1
|
||||||
|
obj.color = (*tool.Search.get_quantitative_pallette("foo", value, min_value, max_value), 1)
|
||||||
|
|
||||||
if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]:
|
if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]:
|
||||||
areas[0].spaces[0].shading.color_type = "OBJECT"
|
areas[0].spaces[0].shading.color_type = "OBJECT"
|
||||||
|
|
||||||
props.colourscheme.clear()
|
props.colourscheme.clear()
|
||||||
for value in sorted(colourscheme.keys()):
|
for value in natsorted(colourscheme.keys()):
|
||||||
data = colourscheme[value]
|
data = colourscheme[value]
|
||||||
new = props.colourscheme.add()
|
new = props.colourscheme.add()
|
||||||
new.name = str(value)
|
new.name = str(value)
|
||||||
@@ -331,13 +363,40 @@ class SelectByProperty(Operator):
|
|||||||
|
|
||||||
active_value = props.colourscheme[props.active_colourscheme_index].name
|
active_value = props.colourscheme[props.active_colourscheme_index].name
|
||||||
|
|
||||||
|
is_qualitative = props.pallette in ("tab10", "paired")
|
||||||
|
|
||||||
|
if not is_qualitative:
|
||||||
|
values = []
|
||||||
|
for colour in props.colourscheme:
|
||||||
|
try:
|
||||||
|
values.append(float(colour.name))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
values = sorted(values)
|
||||||
|
|
||||||
for obj in context.visible_objects:
|
for obj in context.visible_objects:
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
continue
|
continue
|
||||||
value = str(ifcopenshell.util.selector.get_element_value(element, query))
|
value = ifcopenshell.util.selector.get_element_value(element, query)
|
||||||
if value == active_value:
|
if is_qualitative:
|
||||||
obj.select_set(True)
|
if str(value) == active_value:
|
||||||
|
obj.select_set(True)
|
||||||
|
else:
|
||||||
|
if active_value == "None":
|
||||||
|
if value is None:
|
||||||
|
obj.select_set(True)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
index = bisect.bisect_right(values, value)
|
||||||
|
if index >= len(values):
|
||||||
|
index = -1
|
||||||
|
if values[index] == float(active_value):
|
||||||
|
obj.select_set(True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,15 @@ class BIMSearchProperties(PropertyGroup):
|
|||||||
saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches")
|
saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches")
|
||||||
saved_colourschemes: EnumProperty(items=get_saved_colourschemes, name="Saved Colourschemes")
|
saved_colourschemes: EnumProperty(items=get_saved_colourschemes, name="Saved Colourschemes")
|
||||||
colourscheme_query: StringProperty(name="Colourscheme Query", default="class")
|
colourscheme_query: StringProperty(name="Colourscheme Query", default="class")
|
||||||
|
pallette: EnumProperty(
|
||||||
|
items=[
|
||||||
|
("tab10", "Default", "Contrasting colours to distinguish categories"),
|
||||||
|
("paired", "Paired", "Pairs of colours to distinguish categories"),
|
||||||
|
("coolwarm", "CoolWarm", "A diverging linear range from blue to red with white in the middle"),
|
||||||
|
("spectral", "Spectral", "A diverging spectral range from red to blue with white in the middle"),
|
||||||
|
],
|
||||||
|
name="Pallette",
|
||||||
|
)
|
||||||
colourscheme: CollectionProperty(type=BIMColour)
|
colourscheme: CollectionProperty(type=BIMColour)
|
||||||
active_colourscheme_index: IntProperty(name="Active Colourscheme Index")
|
active_colourscheme_index: IntProperty(name="Active Colourscheme Index")
|
||||||
filter_type: StringProperty(name="Filter Type")
|
filter_type: StringProperty(name="Filter Type")
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ class BIM_PT_colour_by_property(Panel):
|
|||||||
row = self.layout.row()
|
row = self.layout.row()
|
||||||
row.prop(props, "colourscheme_query", text="Query")
|
row.prop(props, "colourscheme_query", text="Query")
|
||||||
|
|
||||||
|
row = self.layout.row()
|
||||||
|
row.prop(props, "pallette")
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.operator("bim.colour_by_property", icon="BRUSH_DATA")
|
row.operator("bim.colour_by_property", icon="BRUSH_DATA")
|
||||||
row.operator("bim.reset_object_colours")
|
row.operator("bim.reset_object_colours")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import lark
|
|||||||
import bonsai.core.tool
|
import bonsai.core.tool
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
|
from itertools import cycle
|
||||||
from bonsai.bim.prop import BIMFacet
|
from bonsai.bim.prop import BIMFacet
|
||||||
from typing import Union, Literal
|
from typing import Union, Literal
|
||||||
|
|
||||||
@@ -113,6 +114,70 @@ class Search(bonsai.core.tool.Search):
|
|||||||
return value
|
return value
|
||||||
return '"' + value.replace('"', '\\"') + '"'
|
return '"' + value.replace('"', '\\"') + '"'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_qualitative_pallette(cls, theme: str = "tab10") -> cycle:
|
||||||
|
if theme == "paired":
|
||||||
|
pass
|
||||||
|
return cycle(
|
||||||
|
[
|
||||||
|
(0.651, 0.81, 0.892, 1),
|
||||||
|
(0.121, 0.471, 0.706, 1),
|
||||||
|
(0.699, 0.876, 0.54, 1),
|
||||||
|
(0.199, 0.629, 0.174, 1),
|
||||||
|
(0.983, 0.605, 0.602, 1),
|
||||||
|
(0.89, 0.101, 0.112, 1),
|
||||||
|
(0.989, 0.751, 0.427, 1),
|
||||||
|
(0.986, 0.497, 0.1, 1),
|
||||||
|
(0.792, 0.699, 0.839, 1),
|
||||||
|
(0.414, 0.239, 0.603, 1),
|
||||||
|
(0.993, 0.999, 0.6, 1),
|
||||||
|
(0.693, 0.349, 0.157, 1),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def interpolate_color(cls, c1, c2, factor):
|
||||||
|
return tuple((1 - factor) * x + factor * y for x, y in zip(c1, c2))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_quantitative_pallette(cls, theme: str, value, min_val, max_val):
|
||||||
|
palette = [
|
||||||
|
(0.227, 0.298, 0.753),
|
||||||
|
(0.282, 0.376, 0.820),
|
||||||
|
(0.345, 0.463, 0.886),
|
||||||
|
(0.412, 0.545, 0.937),
|
||||||
|
(0.482, 0.620, 0.973),
|
||||||
|
(0.553, 0.686, 0.992),
|
||||||
|
(0.616, 0.741, 0.996),
|
||||||
|
(0.686, 0.792, 0.984),
|
||||||
|
(0.753, 0.827, 0.961),
|
||||||
|
(0.812, 0.851, 0.918),
|
||||||
|
(0.865, 0.863, 0.863),
|
||||||
|
(0.910, 0.835, 0.792),
|
||||||
|
(0.945, 0.792, 0.714),
|
||||||
|
(0.965, 0.737, 0.635),
|
||||||
|
(0.965, 0.671, 0.553),
|
||||||
|
(0.957, 0.604, 0.482),
|
||||||
|
(0.929, 0.518, 0.404),
|
||||||
|
(0.890, 0.424, 0.329),
|
||||||
|
(0.839, 0.322, 0.263),
|
||||||
|
(0.773, 0.196, 0.200),
|
||||||
|
(0.702, 0.012, 0.149),
|
||||||
|
]
|
||||||
|
|
||||||
|
if value < min_val:
|
||||||
|
value = min_val
|
||||||
|
if value > max_val:
|
||||||
|
value = max_val
|
||||||
|
|
||||||
|
scale = (value - min_val) / (max_val - min_val) * (len(palette) - 1)
|
||||||
|
index = int(scale)
|
||||||
|
fraction = scale - index
|
||||||
|
|
||||||
|
if index >= len(palette) - 1:
|
||||||
|
return palette[-1]
|
||||||
|
return cls.interpolate_color(palette[index], palette[index + 1], fraction)
|
||||||
|
|
||||||
|
|
||||||
class ImportFilterQueryTransformer(lark.Transformer):
|
class ImportFilterQueryTransformer(lark.Transformer):
|
||||||
def __init__(self, filter_groups):
|
def __init__(self, filter_groups):
|
||||||
|
|||||||
Reference in New Issue
Block a user