Merge branch 'IfcOpenShell:v0.7.0' into v0.7.0

This commit is contained in:
Carlos Dias
2023-07-03 08:48:10 -03:00
committed by GitHub
22 changed files with 298 additions and 152 deletions
+4 -1
View File
@@ -94,4 +94,7 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
# apple # apple
.DS_Store .DS_Store
# Brickschema
src/blenderbim/blenderbim/bim/schema/Brick.ttl
+2
View File
@@ -275,6 +275,7 @@ class TopicHandler:
""" """
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler) new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
self.add_visinfo_handler(new_viewpoint) self.add_visinfo_handler(new_viewpoint)
return new_viewpoint
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None: 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 """Add a viewpoint pointing at an XYZ point in space
@@ -287,6 +288,7 @@ class TopicHandler:
position, *guids, xml_handler=self._xml_handler position, *guids, xml_handler=self._xml_handler
) )
self.add_visinfo_handler(vi_handler) self.add_visinfo_handler(vi_handler)
return vi_handler
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None: def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
@@ -57,6 +57,7 @@ classes = (
operator.SelectBcfBimSnippetReference, operator.SelectBcfBimSnippetReference,
operator.SelectBcfDocumentReference, operator.SelectBcfDocumentReference,
operator.SelectBcfHeaderFile, operator.SelectBcfHeaderFile,
operator.UnloadBcfProject,
operator.ViewBcfTopic, operator.ViewBcfTopic,
prop.BcfReferenceLink, prop.BcfReferenceLink,
prop.BcfLabel, prop.BcfLabel,
@@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator):
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"}) filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
def execute(self, context): def execute(self, context):
context.scene.BCFProperties.is_loaded = False
if self.filepath: if self.filepath:
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath) bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
bcfxml = bcfstore.BcfStore.get_bcfxml() bcfxml = bcfstore.BcfStore.get_bcfxml()
@@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator):
return {"RUNNING_MODAL"} 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): class LoadBcfTopics(bpy.types.Operator):
bl_idname = "bim.load_bcf_topics" bl_idname = "bim.load_bcf_topics"
bl_label = "Load BCF Topics" bl_label = "Load BCF Topics"
@@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel):
scene = context.scene scene = context.scene
props = scene.BCFProperties 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: 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 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 = layout.row()
row.prop(props, "name") row.prop(props, "name")
@@ -33,6 +33,9 @@ classes = (
operator.RewindBrickClass, operator.RewindBrickClass,
operator.ViewBrickClass, operator.ViewBrickClass,
operator.ViewBrickItem, operator.ViewBrickItem,
operator.UndoBrick,
operator.RedoBrick,
operator.SerializeBrick,
prop.Brick, prop.Brick,
prop.BIMBrickProperties, prop.BIMBrickProperties,
ui.BIM_PT_brickschema, ui.BIM_PT_brickschema,
@@ -110,7 +110,8 @@ class BrickschemaData:
return [] return []
results = [] results = []
for alias, uri in BrickStore.graph.namespaces(): 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 return results
@classmethod @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, library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
brick_uri=props.bricks[props.active_brick_index].uri, 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.add_brick_feed", text="", icon="PLUGIN")
row.operator("bim.remove_brick", text="", icon="X") 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") self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
for attribute in BrickschemaData.data["attributes"]: for attribute in BrickschemaData.data["attributes"]:
@@ -23,7 +23,7 @@ import bmesh
import logging import logging
import numpy as np import numpy as np
import ifcopenshell import ifcopenshell
from mathutils import Matrix from mathutils import Matrix, Vector
from math import radians from math import radians
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
@@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator):
_, extension = os.path.splitext(self.filepath) _, extension = os.path.splitext(self.filepath)
if extension != ".json": if extension != ".json":
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
settings = ifcclash.ClashSettings() settings = ifcclash.ClashSettings()
settings.output = self.filepath settings.output = self.filepath
settings.logger = logging.getLogger("Clash") settings.logger = logging.getLogger("Clash")
@@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator):
if context.scene.BIMClashProperties.should_create_clash_snapshots: 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") camera = bpy.data.objects.get("IFC Clash Camera")
if not camera: if not camera:
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera")) camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
context.scene.collection.objects.link(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 context.scene.camera = camera
camera.data.angle = radians(60) camera.data.angle = radians(60)
area = next(area for area in context.screen.areas if area.type == "VIEW_3D") 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.image_settings.file_format = "PNG"
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
bpy.ops.render.opengl(write_still=True) 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 clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
@@ -111,10 +111,13 @@ class ConnectionsData:
for rel in connected_to: for rel in connected_to:
if element.is_a("IfcDistributionPort"): if element.is_a("IfcDistributionPort"):
related_element = rel.RelatedPort related_element = rel.RelatedPort
related_element_connection_type = ""
else: else:
related_element = rel.RelatedElement related_element = rel.RelatedElement
if element.is_a("IfcRelConnectsPathElements"):
related_element_connection_type = rel.RelatedConnectionType related_element_connection_type = rel.RelatedConnectionType
else:
related_element_connection_type = ""
results.append( results.append(
{ {
@@ -128,10 +131,13 @@ class ConnectionsData:
for rel in connected_from: for rel in connected_from:
if element.is_a("IfcDistributionPort"): if element.is_a("IfcDistributionPort"):
relating_element = rel.RelatingPort relating_element = rel.RelatingPort
relating_element_connection_type = ""
else: else:
relating_element = rel.RelatingElement relating_element = rel.RelatingElement
if element.is_a("IfcRelConnectsPathElements"):
relating_element_connection_type = rel.RelatingConnectionType relating_element_connection_type = rel.RelatingConnectionType
else:
relating_element_connection_type = ""
results.append( results.append(
{ {
@@ -292,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
offset: bpy.props.IntProperty() offset: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
from PIL import Image, ImageDraw
if bpy.app.background: if bpy.app.background:
return return
@@ -310,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
offset = 0 offset = 0
queue = queue[offset : offset + 9] queue = queue[offset : offset + 9]
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
while queue: while queue:
# if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation # if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation
element = queue.pop() element = queue.pop()
obj = tool.Ifc.get_object(element) if tool.Model.update_thumbnail_for_element(element):
queue.append(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)
return {"FINISHED"} return {"FINISHED"}
@@ -472,7 +375,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
obj.matrix_world = newmat obj.matrix_world = newmat
def generate_box(usecase_path, ifc_file, settings): def generate_box(usecase_path, ifc_file, settings):
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW") box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
if not box_context: if not box_context:
@@ -145,12 +145,21 @@ class DumbProfileRegenerator:
objs = [] objs = []
if not profile: if not profile:
return return
element_types = set()
for element in self.get_elements_using_profile(profile): for element in self.get_elements_using_profile(profile):
obj = tool.Ifc.get_object(element) obj = tool.Ifc.get_object(element)
if obj: if obj:
objs.append(obj) objs.append(obj)
if element.is_a("IfcElementType"):
element_types.add(element)
DumbProfileRecalculator().recalculate(objs) 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): def regenerate_from_profile(self, usecase_path, ifc_file, settings):
self.file = ifc_file self.file = ifc_file
objs = [] objs = []
@@ -165,9 +174,10 @@ class DumbProfileRegenerator:
def get_elements_using_profile(self, profile): def get_elements_using_profile(self, profile):
results = [] results = []
for profile_set in [ profile_sets = [
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") 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): for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"): if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue continue
@@ -181,6 +191,18 @@ class DumbProfileRegenerator:
results.extend(rel.RelatedObjects) results.extend(rel.RelatedObjects)
return results 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): def regenerate_from_type(self, usecase_path, ifc_file, settings):
obj = tool.Ifc.get_object(settings["related_object"]) obj = tool.Ifc.get_object(settings["related_object"])
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: 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.should_save_as = False
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As") op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
op.should_save_as = True 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): def draw_create_project_ui(self, context):
props = context.scene.BIMProperties props = context.scene.BIMProperties
+11
View File
@@ -109,3 +109,14 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
ifc.run("library.remove_reference", reference=reference) ifc.run("library.remove_reference", reference=reference)
brick.remove_brick(brick_uri) brick.remove_brick(brick_uri)
brick.run_refresh_brick_viewer() 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)
+56 -22
View File
@@ -25,6 +25,7 @@ import blenderbim.tool as tool
try: try:
import brickschema import brickschema
import brickschema.persistent
import urllib.parse import urllib.parse
from rdflib import Literal, URIRef, Namespace from rdflib import Literal, URIRef, Namespace
from rdflib.namespace import RDF from rdflib.namespace import RDF
@@ -32,14 +33,20 @@ except:
# See #1860 # See #1860
print("Warning: brickschema not available.") 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): class Brick(blenderbim.core.tool.Brick):
@classmethod @classmethod
def add_brick(cls, namespace, brick_class): def add_brick(cls, namespace, brick_class):
ns = Namespace(namespace) ns = Namespace(namespace)
brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())] brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())]
BrickStore.graph.add((brick, RDF.type, URIRef(brick_class))) with BrickStore.graph.new_changeset("PROJECT") as cs:
BrickStore.graph.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed"))) 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) return str(brick)
@classmethod @classmethod
@@ -100,7 +107,7 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod @classmethod
def clear_project(cls): def clear_project(cls):
BrickStore.graph = None BrickStore.purge()
bpy.context.scene.BIMBrickProperties.active_brick_class == "" bpy.context.scene.BIMBrickProperties.active_brick_class == ""
bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear() bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear()
@@ -249,24 +256,24 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod @classmethod
def load_brick_file(cls, filepath): def load_brick_file(cls, filepath):
if not BrickStore.schema: if not BrickStore.schema: # important check for running under test cases
BrickStore.schema = brickschema.Graph()
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
BrickStore.schema.load_file(schema_path) BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
BrickStore.graph = brickschema.Graph().load_file(filepath) + BrickStore.schema 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 BrickStore.path = filepath
@classmethod @classmethod
def new_brick_file(cls): def new_brick_file(cls):
if not BrickStore.schema: if not BrickStore.schema: # important check for running under test cases
BrickStore.schema = brickschema.Graph()
#BrickStore.schema = brickschema.persistent.VersionedGraphCollection("sqlite://")
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
BrickStore.schema.load_file(schema_path) BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
#BrickStore.schema.load_graph(schema_path) with BrickStore.graph.new_changeset("SCHEMA") as cs:
BrickStore.graph = brickschema.Graph() + BrickStore.schema cs.load_file(BrickStore.schema)
BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#"))
BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#"))
BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#")) BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#"))
@@ -281,8 +288,10 @@ class Brick(blenderbim.core.tool.Brick):
@classmethod @classmethod
def remove_brick(cls, brick_uri): def remove_brick(cls, brick_uri):
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): if(BrickStore.graph.triples((URIRef(brick_uri), None, None))):
BrickStore.graph.remove(triple) with BrickStore.graph.new_changeset("PROJECT") as cs:
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
cs.remove(triple)
@classmethod @classmethod
def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None): 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): def set_active_brick_class(cls, brick_class):
bpy.context.scene.BIMBrickProperties.active_brick_class = 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: class BrickStore:
schema = None schema = None # this is now a os path
graph = None path = None # file path if the project was loaded in
path = None 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 @staticmethod
def purge(): def purge():
BrickStore.schema = None BrickStore.schema = None
BrickStore.graph = None BrickStore.graph = None
BrickStore.path = None BrickStore.path = None
@classmethod
def get_project(cls):
return BrickStore.graph.graph_at(graph="PROJECT")
-1
View File
@@ -202,7 +202,6 @@ class Loader(blenderbim.core.tool.Loader):
ifc_path = Path(tool.Ifc.get_path()) ifc_path = Path(tool.Ifc.get_path())
image_url = ifc_path.parent / image_url image_url = ifc_path.parent / image_url
# import pdb; pdb.set_trace()
if not image_url.exists(): if not image_url.exists():
print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.") print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.")
continue continue
+99
View File
@@ -29,6 +29,7 @@ from mathutils import Matrix, Vector
from blenderbim.bim import import_ifc from blenderbim.bim import import_ifc
from blenderbim.bim.module.geometry.helper import Helper from blenderbim.bim.module.geometry.helper import Helper
import collections import collections
from blenderbim.bim.module.model.data import AuthoringData
class Model(blenderbim.core.tool.Model): class Model(blenderbim.core.tool.Model):
@@ -630,3 +631,101 @@ class Model(blenderbim.core.tool.Model):
is_global=True, is_global=True,
should_sync_changes_first=False, 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
+2 -1
View File
@@ -75,7 +75,8 @@ def an_empty_blender_session():
# default project settings # default project settings
bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.system = "METRIC"
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" 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") @given("an empty IFC project")
+2 -6
View File
@@ -307,10 +307,8 @@ class TestImportBrickItems(NewFile):
class TestLoadBrickFile(NewFile): class TestLoadBrickFile(NewFile):
def test_run(self): def test_run(self):
# We stub the schema to make tests run faster # We stub the schema to make tests run faster
BrickStore.schema = brickschema.Graph()
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl") BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl")
BrickStore.schema.load_file(schema_path)
# This is the actual test # This is the actual test
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
@@ -322,10 +320,8 @@ class TestLoadBrickFile(NewFile):
class TestNewBrickFile(NewFile): class TestNewBrickFile(NewFile):
def test_run(self): def test_run(self):
# We stub the schema to make tests run faster # We stub the schema to make tests run faster
BrickStore.schema = brickschema.Graph()
cwd = os.path.dirname(os.path.realpath(__file__)) cwd = os.path.dirname(os.path.realpath(__file__))
schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl") BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl")
BrickStore.schema.load_file(schema_path)
# This is the actual test # This is the actual test
subject.new_brick_file() subject.new_brick_file()
+9 -3
View File
@@ -122,14 +122,20 @@ class Clasher:
for clash in clash_set["clashes"].values(): for clash in clash_set["clashes"].values():
title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}' 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 = 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"], 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 "" suffix = f".{i}" if i else ""
bcfxml.save_project(f"{self.settings.output}{suffix}") bcfxml.save_project(f"{self.settings.output}{suffix}")
def get_viewpoint_snapshot(self, viewpoint, mat): def get_viewpoint_snapshot(self, viewpoint):
return None # Possible to overload this function in a GUI application if used as a library # 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): def export_json(self):
clash_sets = self.clash_sets.copy() clash_sets = self.clash_sets.copy()
+5 -3
View File
@@ -29,9 +29,10 @@ import multiprocessing
import ifcopenshell import ifcopenshell
import ifcopenshell.geom import ifcopenshell.geom
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.placement import ifcopenshell.util.placement
import ifcopenshell.util.classification import ifcopenshell.util.classification
import ifcopenshell.util.selector import ifcopenshell.util.representation
from deepdiff import DeepDiff from deepdiff import DeepDiff
@@ -141,8 +142,9 @@ class IfcDiff:
continue continue
if should_check_geometry: if should_check_geometry:
# Option 1: check everything heuristically using the iterator (seems faster) # Option 1: check everything heuristically using the iterator (seems faster)
potential_old_changes.append(old) if ifcopenshell.util.representation.get_representation(new, "Model", "Body", "MODEL_VIEW"):
potential_new_changes.append(new) potential_old_changes.append(old)
potential_new_changes.append(new)
# Option 2: check first using Python, then fallback to iterator (twice as slow) # Option 2: check first using Python, then fallback to iterator (twice as slow)
# diff = self.diff_element_basic_geometry(old, new) # diff = self.diff_element_basic_geometry(old, new)
# if diff: # if diff: