mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0
This commit is contained in:
+4
-1
@@ -94,4 +94,7 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
@@ -275,6 +275,7 @@ class TopicHandler:
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
@@ -287,6 +288,7 @@ class TopicHandler:
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
|
||||
@@ -57,6 +57,7 @@ classes = (
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.UnloadBcfProject,
|
||||
operator.ViewBcfTopic,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
|
||||
@@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
if self.filepath:
|
||||
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
@@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class UnloadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_bcf_project"
|
||||
bl_label = "Unload BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfstore.BcfStore.set(None)
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfTopics(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topics"
|
||||
bl_label = "Load BCF Topics"
|
||||
|
||||
@@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel):
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
|
||||
if not props.is_loaded:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
return
|
||||
|
||||
row.operator("bim.save_bcf_project", text="Save Project")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_bcf_project", icon="EXPORT", text="Save Project")
|
||||
row.operator("bim.unload_bcf_project", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "name")
|
||||
|
||||
@@ -33,6 +33,9 @@ classes = (
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
operator.UndoBrick,
|
||||
operator.RedoBrick,
|
||||
operator.SerializeBrick,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
|
||||
@@ -110,7 +110,8 @@ class BrickschemaData:
|
||||
return []
|
||||
results = []
|
||||
for alias, uri in BrickStore.graph.namespaces():
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
# results.append((uri, f"{alias}: {uri}", ""))
|
||||
results.append((uri, f"{alias}", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -189,3 +189,24 @@ class RemoveBrick(bpy.types.Operator, Operator):
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
class UndoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.undo_brick"
|
||||
bl_label = "Undo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.undo_brick(tool.Brick)
|
||||
|
||||
class RedoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.redo_brick"
|
||||
bl_label = "Redo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.redo_brick(tool.Brick)
|
||||
|
||||
class SerializeBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.serialize_brick"
|
||||
bl_label = "Serialize Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.serialize_brick(tool.Brick)
|
||||
@@ -58,6 +58,13 @@ class BIM_PT_brickschema(Panel):
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.undo_brick", icon="LOOP_BACK")
|
||||
row.operator("bim.redo_brick", icon="LOOP_FORWARDS")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.serialize_brick")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
|
||||
@@ -23,7 +23,7 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
@@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
|
||||
if context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint, mat):
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
context.scene.collection.objects.link(camera)
|
||||
camera.matrix_world = Matrix(mat)
|
||||
|
||||
bcf_camera = viewpoint.visualization_info.perspective_camera
|
||||
p = bcf_camera.camera_view_point
|
||||
z = bcf_camera.camera_direction
|
||||
z = Vector([z.x, z.y, z.z]) * -1
|
||||
y = bcf_camera.camera_up_vector
|
||||
y = Vector([y.x, y.y, y.z])
|
||||
x = y.cross(z)
|
||||
|
||||
mat = Matrix([
|
||||
[x[0], y[0], z[0], p.x],
|
||||
[x[1], y[1], z[1], p.y],
|
||||
[x[2], y[2], z[2], p.z],
|
||||
[0, 0, 0, 0],
|
||||
])
|
||||
|
||||
camera.matrix_world = mat
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
@@ -246,7 +263,8 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
return context.scene.render.filepath
|
||||
with open(context.scene.render.filepath, "rb") as f:
|
||||
return ("snapshot.png", f.read())
|
||||
|
||||
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
|
||||
@@ -111,10 +111,13 @@ class ConnectionsData:
|
||||
for rel in connected_to:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
related_element = rel.RelatedPort
|
||||
related_element_connection_type = ""
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
@@ -128,10 +131,13 @@ class ConnectionsData:
|
||||
for rel in connected_from:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
relating_element = rel.RelatingPort
|
||||
relating_element_connection_type = ""
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
|
||||
@@ -292,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
@@ -310,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset = 0
|
||||
queue = queue[offset : offset + 9]
|
||||
|
||||
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
|
||||
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
|
||||
|
||||
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.051:
|
||||
width = 10
|
||||
elif si_total_thickness <= 0.11:
|
||||
width = 20
|
||||
elif si_total_thickness <= 0.21:
|
||||
width = 30
|
||||
elif si_total_thickness <= 0.31:
|
||||
width = 40
|
||||
else:
|
||||
width = 50
|
||||
|
||||
height = 100
|
||||
|
||||
is_horizontal = False
|
||||
if element.is_a("IfcSlabType"):
|
||||
is_horizontal = True
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", None)
|
||||
if layer_set_direction == "AXIS2":
|
||||
is_horizontal = False
|
||||
elif layer_set_direction == "AXIS3":
|
||||
is_horizontal = True
|
||||
|
||||
if is_horizontal:
|
||||
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=5)
|
||||
current_thickness = 0
|
||||
del thicknesses[-1]
|
||||
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)
|
||||
elif False:
|
||||
# TODO: things like parametric duct segments
|
||||
pass
|
||||
elif not element.RepresentationMaps:
|
||||
# Empties are represented by a generic thumbnail
|
||||
width = height = 100
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2)
|
||||
draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
else:
|
||||
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)
|
||||
if tool.Model.update_thumbnail_for_element(element):
|
||||
queue.append(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -472,7 +375,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj.matrix_world = newmat
|
||||
|
||||
|
||||
|
||||
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:
|
||||
|
||||
@@ -145,12 +145,21 @@ class DumbProfileRegenerator:
|
||||
objs = []
|
||||
if not profile:
|
||||
return
|
||||
|
||||
element_types = set()
|
||||
for element in self.get_elements_using_profile(profile):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
if element.is_a("IfcElementType"):
|
||||
element_types.add(element)
|
||||
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
|
||||
# update related thumbnails
|
||||
for element in self.get_element_types_using_profile(profile):
|
||||
tool.Model.update_thumbnail_for_element(element, refresh=True)
|
||||
|
||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
||||
self.file = ifc_file
|
||||
objs = []
|
||||
@@ -165,9 +174,10 @@ class DumbProfileRegenerator:
|
||||
|
||||
def get_elements_using_profile(self, profile):
|
||||
results = []
|
||||
for profile_set in [
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]:
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcMaterialProfileSetUsage"):
|
||||
continue
|
||||
@@ -181,6 +191,18 @@ class DumbProfileRegenerator:
|
||||
results.extend(rel.RelatedObjects)
|
||||
return results
|
||||
|
||||
def get_element_types_using_profile(self, profile):
|
||||
results = []
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
results.extend(inverse.RelatedObjects)
|
||||
return results
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
|
||||
@@ -164,7 +164,7 @@ class BIM_PT_project(Panel):
|
||||
op.should_save_as = False
|
||||
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
|
||||
op.should_save_as = True
|
||||
row.operator("bim.unload_project", text="", icon="X")
|
||||
row.operator("bim.unload_project", text="", icon="CANCEL")
|
||||
|
||||
def draw_create_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
|
||||
@@ -109,3 +109,14 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
|
||||
ifc.run("library.remove_reference", reference=reference)
|
||||
brick.remove_brick(brick_uri)
|
||||
brick.run_refresh_brick_viewer()
|
||||
|
||||
def undo_brick(brick):
|
||||
brick.undo_brick()
|
||||
brick.run_refresh_brick_viewer()
|
||||
|
||||
def redo_brick(brick):
|
||||
brick.redo_brick()
|
||||
brick.run_refresh_brick_viewer()
|
||||
|
||||
def serialize_brick(brick, file_name="BlenderBIMSerializeTest.ttl"):
|
||||
brick.serialize_brick(file_name)
|
||||
@@ -25,6 +25,7 @@ import blenderbim.tool as tool
|
||||
|
||||
try:
|
||||
import brickschema
|
||||
import brickschema.persistent
|
||||
import urllib.parse
|
||||
from rdflib import Literal, URIRef, Namespace
|
||||
from rdflib.namespace import RDF
|
||||
@@ -32,14 +33,20 @@ except:
|
||||
# See #1860
|
||||
print("Warning: brickschema not available.")
|
||||
|
||||
# silence known rdflib_sqlalchemy TypeError warning
|
||||
# see https://github.com/BrickSchema/Brick/issues/513#issuecomment-1558493675
|
||||
import logging
|
||||
logger = logging.getLogger("rdflib")
|
||||
logger.setLevel(logging.ERROR)
|
||||
|
||||
class Brick(blenderbim.core.tool.Brick):
|
||||
@classmethod
|
||||
def add_brick(cls, namespace, brick_class):
|
||||
ns = Namespace(namespace)
|
||||
brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())]
|
||||
BrickStore.graph.add((brick, RDF.type, URIRef(brick_class)))
|
||||
BrickStore.graph.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed")))
|
||||
with BrickStore.graph.new_changeset("PROJECT") as cs:
|
||||
cs.add((brick, RDF.type, URIRef(brick_class)))
|
||||
cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed")))
|
||||
return str(brick)
|
||||
|
||||
@classmethod
|
||||
@@ -100,7 +107,7 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
|
||||
@classmethod
|
||||
def clear_project(cls):
|
||||
BrickStore.graph = None
|
||||
BrickStore.purge()
|
||||
bpy.context.scene.BIMBrickProperties.active_brick_class == ""
|
||||
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
|
||||
|
||||
@@ -249,24 +256,24 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
|
||||
@classmethod
|
||||
def load_brick_file(cls, filepath):
|
||||
if not BrickStore.schema:
|
||||
BrickStore.schema = brickschema.Graph()
|
||||
if not BrickStore.schema: # important check for running under test cases
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||
BrickStore.schema.load_file(schema_path)
|
||||
BrickStore.graph = brickschema.Graph().load_file(filepath) + BrickStore.schema
|
||||
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
||||
with BrickStore.graph.new_changeset("SCHEMA") as cs:
|
||||
cs.load_file(BrickStore.schema)
|
||||
with BrickStore.graph.new_changeset("PROJECT") as cs:
|
||||
cs.load_file(filepath)
|
||||
BrickStore.path = filepath
|
||||
|
||||
@classmethod
|
||||
def new_brick_file(cls):
|
||||
if not BrickStore.schema:
|
||||
BrickStore.schema = brickschema.Graph()
|
||||
#BrickStore.schema = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
||||
if not BrickStore.schema: # important check for running under test cases
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||
BrickStore.schema.load_file(schema_path)
|
||||
#BrickStore.schema.load_graph(schema_path)
|
||||
BrickStore.graph = brickschema.Graph() + BrickStore.schema
|
||||
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
||||
with BrickStore.graph.new_changeset("SCHEMA") as cs:
|
||||
cs.load_file(BrickStore.schema)
|
||||
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
|
||||
BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#"))
|
||||
BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#"))
|
||||
@@ -281,8 +288,10 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
|
||||
@classmethod
|
||||
def remove_brick(cls, brick_uri):
|
||||
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
|
||||
BrickStore.graph.remove(triple)
|
||||
if(BrickStore.graph.triples((URIRef(brick_uri), None, None))):
|
||||
with BrickStore.graph.new_changeset("PROJECT") as cs:
|
||||
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
|
||||
cs.remove(triple)
|
||||
|
||||
@classmethod
|
||||
def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None):
|
||||
@@ -308,14 +317,39 @@ class Brick(blenderbim.core.tool.Brick):
|
||||
def set_active_brick_class(cls, brick_class):
|
||||
bpy.context.scene.BIMBrickProperties.active_brick_class = brick_class
|
||||
|
||||
@classmethod
|
||||
def undo_brick(cls):
|
||||
if(len(BrickStore.graph.versions()) > 1):
|
||||
BrickStore.graph.undo()
|
||||
|
||||
@classmethod
|
||||
def redo_brick(cls):
|
||||
with BrickStore.graph.conn() as conn:
|
||||
redo_record = conn.execute(
|
||||
"SELECT * from redos " "ORDER BY timestamp ASC LIMIT 1"
|
||||
).fetchone()
|
||||
if redo_record is not None:
|
||||
BrickStore.graph.redo()
|
||||
|
||||
@classmethod
|
||||
def serialize_brick(cls, file_name):
|
||||
#temporary file path, could either be user selected for "save as" or use the BrickStore.path for simply "save"
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
dest = os.path.join(cwd, "..", "bim", "schema", file_name)
|
||||
BrickStore.get_project().serialize(destination=dest, format="turtle")
|
||||
|
||||
class BrickStore:
|
||||
schema = None
|
||||
graph = None
|
||||
path = None
|
||||
|
||||
schema = None # this is now a os path
|
||||
path = None # file path if the project was loaded in
|
||||
graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project"
|
||||
# "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
BrickStore.schema = None
|
||||
BrickStore.graph = None
|
||||
BrickStore.path = None
|
||||
BrickStore.path = None
|
||||
|
||||
@classmethod
|
||||
def get_project(cls):
|
||||
return BrickStore.graph.graph_at(graph="PROJECT")
|
||||
@@ -202,7 +202,6 @@ class Loader(blenderbim.core.tool.Loader):
|
||||
ifc_path = Path(tool.Ifc.get_path())
|
||||
image_url = ifc_path.parent / image_url
|
||||
|
||||
# import pdb; pdb.set_trace()
|
||||
if not image_url.exists():
|
||||
print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.")
|
||||
continue
|
||||
|
||||
@@ -29,6 +29,7 @@ from mathutils import Matrix, Vector
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
import collections
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
|
||||
|
||||
class Model(blenderbim.core.tool.Model):
|
||||
@@ -630,3 +631,101 @@ class Model(blenderbim.core.tool.Model):
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_thumbnail_for_element(cls, element, refresh=False):
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
return # Nothing to process
|
||||
|
||||
if not refresh and element.id() in AuthoringData.type_thumbnails:
|
||||
return # Already processed
|
||||
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
|
||||
# if object has .data we can use default blender .asset_generate_preview()
|
||||
if not obj.data:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
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
|
||||
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
|
||||
|
||||
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.051:
|
||||
width = 10
|
||||
elif si_total_thickness <= 0.11:
|
||||
width = 20
|
||||
elif si_total_thickness <= 0.21:
|
||||
width = 30
|
||||
elif si_total_thickness <= 0.31:
|
||||
width = 40
|
||||
else:
|
||||
width = 50
|
||||
|
||||
height = 100
|
||||
|
||||
is_horizontal = False
|
||||
if element.is_a("IfcSlabType"):
|
||||
is_horizontal = True
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", None)
|
||||
if layer_set_direction == "AXIS2":
|
||||
is_horizontal = False
|
||||
elif layer_set_direction == "AXIS3":
|
||||
is_horizontal = True
|
||||
|
||||
if is_horizontal:
|
||||
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=5)
|
||||
current_thickness = 0
|
||||
del thicknesses[-1]
|
||||
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)
|
||||
elif False:
|
||||
# TODO: things like parametric duct segments
|
||||
pass
|
||||
elif not element.RepresentationMaps:
|
||||
# Empties are represented by a generic thumbnail
|
||||
width = height = 100
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2)
|
||||
draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
else:
|
||||
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.preview.image_size = size, size
|
||||
obj.preview.image_pixels_float = pixels
|
||||
|
||||
AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id
|
||||
|
||||
@@ -75,7 +75,8 @@ def an_empty_blender_session():
|
||||
# default project settings
|
||||
bpy.context.scene.unit_settings.system = "METRIC"
|
||||
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
|
||||
bpy.context.scene.BIMProjectProperties.template_file = '0'
|
||||
bpy.context.scene.BIMProjectProperties.template_file = "0"
|
||||
bpy.context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound = False
|
||||
|
||||
|
||||
@given("an empty IFC project")
|
||||
|
||||
@@ -307,10 +307,8 @@ class TestImportBrickItems(NewFile):
|
||||
class TestLoadBrickFile(NewFile):
|
||||
def test_run(self):
|
||||
# We stub the schema to make tests run faster
|
||||
BrickStore.schema = brickschema.Graph()
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl")
|
||||
BrickStore.schema.load_file(schema_path)
|
||||
BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl")
|
||||
|
||||
# This is the actual test
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -322,10 +320,8 @@ class TestLoadBrickFile(NewFile):
|
||||
class TestNewBrickFile(NewFile):
|
||||
def test_run(self):
|
||||
# We stub the schema to make tests run faster
|
||||
BrickStore.schema = brickschema.Graph()
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl")
|
||||
BrickStore.schema.load_file(schema_path)
|
||||
BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl")
|
||||
|
||||
# This is the actual test
|
||||
subject.new_brick_file()
|
||||
|
||||
@@ -122,14 +122,20 @@ class Clasher:
|
||||
for clash in clash_set["clashes"].values():
|
||||
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}'
|
||||
topic = bcfxml.add_topic(title, title, "IfcClash")
|
||||
topic.add_viewpoint_from_point_and_guids(
|
||||
viewpoint = topic.add_viewpoint_from_point_and_guids(
|
||||
np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"],
|
||||
)
|
||||
snapshot = self.get_viewpoint_snapshot(viewpoint)
|
||||
if snapshot:
|
||||
topic.markup.viewpoints[0].snapshot = snapshot[0]
|
||||
viewpoint.snapshot = snapshot[1]
|
||||
suffix = f".{i}" if i else ""
|
||||
bcfxml.save_project(f"{self.settings.output}{suffix}")
|
||||
|
||||
def get_viewpoint_snapshot(self, viewpoint, mat):
|
||||
return None # Possible to overload this function in a GUI application if used as a library
|
||||
def get_viewpoint_snapshot(self, viewpoint):
|
||||
# Possible to overload this function in a GUI application if used as a library.
|
||||
# Should return a tuple of (filename, bytes).
|
||||
return None
|
||||
|
||||
def export_json(self):
|
||||
clash_sets = self.clash_sets.copy()
|
||||
|
||||
@@ -29,9 +29,10 @@ import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
from deepdiff import DeepDiff
|
||||
|
||||
|
||||
@@ -141,8 +142,9 @@ class IfcDiff:
|
||||
continue
|
||||
if should_check_geometry:
|
||||
# Option 1: check everything heuristically using the iterator (seems faster)
|
||||
potential_old_changes.append(old)
|
||||
potential_new_changes.append(new)
|
||||
if ifcopenshell.util.representation.get_representation(new, "Model", "Body", "MODEL_VIEW"):
|
||||
potential_old_changes.append(old)
|
||||
potential_new_changes.append(new)
|
||||
# Option 2: check first using Python, then fallback to iterator (twice as slow)
|
||||
# diff = self.diff_element_basic_geometry(old, new)
|
||||
# if diff:
|
||||
|
||||
Reference in New Issue
Block a user