mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Experimental new thumbnail with smarter layer / profile thumbnails and no timers.
This commit is contained in:
@@ -328,7 +328,11 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements):
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
self.element_types = set(self.file.by_type("IfcElementType"))
|
||||
self.element_types = set(
|
||||
self.file.by_type("IfcElementType")
|
||||
+ self.file.by_type("IfcDoorStyle")
|
||||
+ self.file.by_type("IfcWindowStyle")
|
||||
)
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
|
||||
@@ -29,21 +29,32 @@ def refresh():
|
||||
|
||||
class AuthoringData:
|
||||
data = {}
|
||||
type_thumbnails = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
if not hasattr(cls, "data"):
|
||||
cls.data = {}
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
cls.load_ifc_classes()
|
||||
cls.load_relating_types()
|
||||
cls.load_relating_types_browser()
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail()
|
||||
cls.data["is_voidable_element"] = cls.is_voidable_element()
|
||||
cls.data["has_visible_openings"] = cls.has_visible_openings()
|
||||
cls.data["active_class"] = cls.active_class()
|
||||
|
||||
@classmethod
|
||||
def type_thumbnail(cls):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if not props.relating_type_id:
|
||||
return 0
|
||||
element = tool.Ifc.get().by_id(int(props.relating_type_id))
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
return 0
|
||||
return cls.type_thumbnails.get(element.id(), None) or 0
|
||||
|
||||
@classmethod
|
||||
def load_ifc_classes(cls):
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
|
||||
@@ -339,6 +339,122 @@ class DynamicallyVoidProduct(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_type_thumbnails"
|
||||
bl_label = "Load Type Thumbnails"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
processing = set()
|
||||
# Only process at most one class at a time.
|
||||
# Large projects have hundreds of types which can lead to unnecessary lag.
|
||||
queue = tool.Ifc.get().by_type(self.ifc_class)
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
while queue:
|
||||
# if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation
|
||||
element = queue.pop()
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
if not obj:
|
||||
continue # Nothing to process
|
||||
elif AuthoringData.type_thumbnails.get(element.id(), None):
|
||||
continue # Already processed
|
||||
elif obj.preview and obj.preview.icon_id:
|
||||
AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id
|
||||
continue
|
||||
|
||||
if obj.data:
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
else:
|
||||
size = 128
|
||||
img = Image.new("RGBA", (size, size))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
profile = material.MaterialProfiles[0].Profile
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, profile)
|
||||
verts = shape.verts
|
||||
edges = shape.edges
|
||||
grouped_verts = [[verts[i], verts[i + 1]] for i in range(0, len(verts), 3)]
|
||||
grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
|
||||
|
||||
max_x = max([v[0] for v in grouped_verts])
|
||||
min_x = min([v[0] for v in grouped_verts])
|
||||
max_y = max([v[1] for v in grouped_verts])
|
||||
min_y = min([v[1] for v in grouped_verts])
|
||||
|
||||
dim_x = max_x - min_x
|
||||
dim_y = max_y - min_y
|
||||
max_dim = max([dim_x, dim_y])
|
||||
scale = 100 / max_dim
|
||||
|
||||
for vert in grouped_verts:
|
||||
vert[0] = round(scale * (vert[0] - min_x)) + ((size / 2) - scale * (dim_x / 2))
|
||||
vert[1] = round(scale * (vert[1] - min_y)) + ((size / 2) - scale * (dim_y / 2))
|
||||
|
||||
for e in grouped_edges:
|
||||
draw.line((tuple(grouped_verts[e[0]]), tuple(grouped_verts[e[1]])), fill="white", width=2)
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
|
||||
total_thickness = sum(thicknesses)
|
||||
si_total_thickness = total_thickness * unit_scale
|
||||
if si_total_thickness < 0.05:
|
||||
width = 10
|
||||
elif si_total_thickness < 0.1:
|
||||
width = 20
|
||||
elif si_total_thickness < 0.2:
|
||||
width = 30
|
||||
elif si_total_thickness < 0.3:
|
||||
width = 40
|
||||
else:
|
||||
width = 50
|
||||
|
||||
height = 100
|
||||
|
||||
if element.is_a("IfcSlabType"):
|
||||
width, height = height, width
|
||||
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=2)
|
||||
current_thickness = 0
|
||||
for thickness in thicknesses:
|
||||
current_thickness += thickness
|
||||
if element.is_a("IfcSlabType"):
|
||||
y = (current_thickness / total_thickness) * height
|
||||
line = [x_offset, y_offset + y, x_offset + width, y_offset + y]
|
||||
else:
|
||||
x = (current_thickness / total_thickness) * width
|
||||
line = [x_offset + x, y_offset, x_offset + x, y_offset + height]
|
||||
draw.line(line, fill="white", width=2)
|
||||
else:
|
||||
# For things like parametric duct segments
|
||||
draw.line([0, 0, size, size], fill="red", width=2)
|
||||
draw.line([0, size, size, 0], fill="red", width=2)
|
||||
|
||||
pixels = [item for sublist in img.getdata() for item in sublist]
|
||||
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
|
||||
obj.preview.image_size = size, size
|
||||
obj.preview.image_pixels_float = pixels
|
||||
|
||||
queue.append(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, settings):
|
||||
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
|
||||
if not box_context:
|
||||
|
||||
@@ -72,10 +72,8 @@ def update_icon_id(self, context, browser=False):
|
||||
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
AuthoringData.load_ifc_classes()
|
||||
AuthoringData.load_relating_types()
|
||||
if not self.updating:
|
||||
update_icon_id(self, context)
|
||||
AuthoringData.data["relating_types_ids"] = AuthoringData.relating_types()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
|
||||
|
||||
def update_ifc_class_browser(self, context):
|
||||
@@ -93,8 +91,7 @@ def update_ifc_class_browser(self, context):
|
||||
|
||||
def update_relating_type(self, context):
|
||||
AuthoringData.load_relating_types()
|
||||
if not self.updating:
|
||||
update_icon_id(self, context)
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
|
||||
|
||||
def update_relating_type_browser(self, context):
|
||||
|
||||
@@ -74,10 +74,10 @@ class BimTool(WorkSpaceTool):
|
||||
AuthoringData.data["relating_types_ids"] if "relating_types_ids" in AuthoringData.data else False
|
||||
)
|
||||
|
||||
if ifc_classes and relating_types_ids and not props.icon_id and not is_tool_header:
|
||||
# hack Dion won't like to show a preview also on the first time the sidebar is shown
|
||||
bpy.app.timers.register(lambda: prop.update_ifc_class(props, "lost_context"))
|
||||
bpy.app.timers.register(lambda: prop.update_relating_type(props, "lost_context"))
|
||||
#if ifc_classes and relating_types_ids and not props.icon_id and not is_tool_header:
|
||||
# # hack Dion won't like to show a preview also on the first time the sidebar is shown
|
||||
# bpy.app.timers.register(lambda: prop.update_ifc_class(props, "lost_context"))
|
||||
# bpy.app.timers.register(lambda: prop.update_relating_type(props, "lost_context"))
|
||||
|
||||
ifc_class = props.ifc_class
|
||||
relating_type_id = props.relating_type_id
|
||||
@@ -96,8 +96,8 @@ class BimTool(WorkSpaceTool):
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
if ifc_classes:
|
||||
row = layout.row()
|
||||
row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
|
||||
#row = layout.row()
|
||||
#row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -107,7 +107,7 @@ class BimTool(WorkSpaceTool):
|
||||
op.ifc_class = ifc_class
|
||||
if relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(relating_type_id)
|
||||
else:
|
||||
elif is_sidebar:
|
||||
row = layout.row(align=True)
|
||||
if ifc_classes:
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
@@ -120,9 +120,14 @@ class BimTool(WorkSpaceTool):
|
||||
prop_with_search(row, props, "relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
if is_sidebar and ifc_classes and relating_types_ids:
|
||||
|
||||
if AuthoringData.data["type_thumbnail"]:
|
||||
box = layout.box()
|
||||
box.template_icon(icon_value=props.icon_id, scale=8)
|
||||
box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5)
|
||||
else:
|
||||
box = layout.box()
|
||||
op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH")
|
||||
op.ifc_class = props.ifc_class
|
||||
|
||||
if ifc_classes:
|
||||
row = layout.row(align=True)
|
||||
|
||||
Reference in New Issue
Block a user