mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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 json
|
||||
import bisect
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
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.props import (
|
||||
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):
|
||||
bl_idname = "bim.add_filter_group"
|
||||
bl_label = "Add Filter Group"
|
||||
@@ -270,27 +255,74 @@ class ColourByProperty(Operator):
|
||||
self.report({"ERROR"}, "No Query Provided")
|
||||
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 = {}
|
||||
|
||||
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}
|
||||
|
||||
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 in colourscheme:
|
||||
colourscheme[value]["total"] += 1
|
||||
value = ifcopenshell.util.selector.get_element_value(element, query)
|
||||
if is_qualitative:
|
||||
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:
|
||||
colourscheme[value] = {"colour": next(colours)[0:3], "total": 1}
|
||||
obj.color = (*colourscheme[value]["colour"], 1)
|
||||
if value is None:
|
||||
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"]:
|
||||
areas[0].spaces[0].shading.color_type = "OBJECT"
|
||||
|
||||
props.colourscheme.clear()
|
||||
for value in sorted(colourscheme.keys()):
|
||||
for value in natsorted(colourscheme.keys()):
|
||||
data = colourscheme[value]
|
||||
new = props.colourscheme.add()
|
||||
new.name = str(value)
|
||||
@@ -331,13 +363,40 @@ class SelectByProperty(Operator):
|
||||
|
||||
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:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
value = str(ifcopenshell.util.selector.get_element_value(element, query))
|
||||
if value == active_value:
|
||||
obj.select_set(True)
|
||||
value = ifcopenshell.util.selector.get_element_value(element, query)
|
||||
if is_qualitative:
|
||||
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"}
|
||||
|
||||
|
||||
|
||||
@@ -123,6 +123,15 @@ 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")
|
||||
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)
|
||||
active_colourscheme_index: IntProperty(name="Active Colourscheme Index")
|
||||
filter_type: StringProperty(name="Filter Type")
|
||||
|
||||
@@ -85,6 +85,9 @@ class BIM_PT_colour_by_property(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(props, "colourscheme_query", text="Query")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "pallette")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.colour_by_property", icon="BRUSH_DATA")
|
||||
row.operator("bim.reset_object_colours")
|
||||
|
||||
@@ -4,6 +4,7 @@ import lark
|
||||
import bonsai.core.tool
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.selector
|
||||
from itertools import cycle
|
||||
from bonsai.bim.prop import BIMFacet
|
||||
from typing import Union, Literal
|
||||
|
||||
@@ -113,6 +114,70 @@ class Search(bonsai.core.tool.Search):
|
||||
return value
|
||||
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):
|
||||
def __init__(self, filter_groups):
|
||||
|
||||
Reference in New Issue
Block a user