Drawing module now is in sync with latest C++ upgrades. See #1153.

This commit is contained in:
Dion Moult
2021-11-08 08:33:19 +11:00
parent e0e8281a1e
commit e4a27d49c9
6 changed files with 129 additions and 43 deletions
+1 -1
View File
@@ -93,7 +93,7 @@ endif
cp -r blenderbim/* dist/blenderbim/ cp -r blenderbim/* dist/blenderbim/
# Provides IfcOpenShell Python functionality # Provides IfcOpenShell Python functionality
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-93c5906-$(PLATFORM)64.zip cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-61e510e-$(PLATFORM)64.zip
cd dist/working && unzip ifcblender* cd dist/working && unzip ifcblender*
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/ cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
@@ -20,7 +20,7 @@
* { stroke-linecap: round; stroke-linejoin: round; } * { stroke-linecap: round; stroke-linejoin: round; }
text { fill: black; stroke: none; } text { fill: black; stroke: none; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; } .cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; } .annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.hidden { stroke-dasharray: 3, 2; } .hidden { stroke-dasharray: 3, 2; }
+2 -1
View File
@@ -236,6 +236,7 @@ def setDefaultProperties(scene):
) )
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner) ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
ifcopenshell.api.owner.settings.get_application = get_application ifcopenshell.api.owner.settings.get_application = get_application
# TODO: Move to drawing module
if len(bpy.context.scene.DocProperties.drawing_styles) == 0: if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
drawing_style.name = "Technical" drawing_style.name = "Technical"
@@ -289,7 +290,7 @@ def setDefaultProperties(scene):
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5, RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5), RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False, RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True, RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0, RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False, RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False, RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
@@ -263,7 +263,7 @@ def get_active_drawing(scene):
return None, None return None, None
try: try:
drawing = props.active_drawing drawing = props.active_drawing
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera return drawing.camera.users_collection[0], drawing.camera
except (KeyError, IndexError): except (KeyError, IndexError):
raise RuntimeError("missing drawing collection") raise RuntimeError("missing drawing collection")
@@ -37,8 +37,9 @@ import blenderbim.bim.module.drawing.annotation as annotation
import blenderbim.bim.module.drawing.sheeter as sheeter import blenderbim.bim.module.drawing.sheeter as sheeter
import blenderbim.bim.module.drawing.scheduler as scheduler import blenderbim.bim.module.drawing.scheduler as scheduler
import blenderbim.bim.module.drawing.helper as helper import blenderbim.bim.module.drawing.helper as helper
from blenderbim.bim.module.drawing.prop import RasterStyleProperty from lxml import etree
from mathutils import Vector, Matrix, Euler, geometry from mathutils import Vector, Matrix, Euler, geometry
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.group.data import Data as GroupData from ifcopenshell.api.group.data import Data as GroupData
from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.pset.data import Data as PsetData
@@ -280,38 +281,89 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_linework_cache: if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path return svg_path
# If we have already calculated it in the SVG in the past, don't recalculate
edited_guids = set()
for obj in IfcStore.edited_objs:
element = tool.Ifc.get_entity(obj)
edited_guids.add(element.GlobalId) if hasattr(element, "GlobalId") else None
cached_linework = set()
if os.path.isfile(svg_path):
tree = etree.parse(svg_path)
root = tree.getroot()
cached_linework = {
el.get("{http://www.ifcopenshell.org/ns}guid")
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]")
}
cached_linework -= edited_guids
# This is a work in progress. See #1153 and #1564. # This is a work in progress. See #1153 and #1564.
import hashlib
import ifcopenshell.draw import ifcopenshell.draw
files = [tool.Ifc.get()] files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
draw_settings = ifcopenshell.draw.draw_settings()
draw_settings.drawing_guid = self.camera_element.GlobalId for ifc_path, ifc in files.items():
draw_settings.css = False # Don't use draw.main() just whilst we're prototyping and experimenting
draw_settings.auto_floorplan = False ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
draw_settings.scale = self.scale ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
# Get all representation contexts to see what we're dealing with
# A drawing prioritises a target view context first, followed by a body context as a fallback
body_contexts = []
target_view_contexts = []
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
target_view_contexts.append(rep_context.id())
continue
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
body_contexts.append(rep_context.id())
continue
if rep_context.is_a() == "IfcGeometricRepresentationContext" and rep_context.ContextType == "Model":
body_contexts.append(rep_context.id())
continue
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
# elements = set([tool.Ifc.get_entity(o) for o in bpy.context.visible_objects if tool.Ifc.get_entity(o).is_a("IfcProduct")])
self.setup_serialiser(ifc)
cache_settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True, SEW_SHELLS=True
)
cache = ifcopenshell.geom.serializers.hdf5(ifc_cache_path, cache_settings)
if target_view_contexts:
geom_settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True, SEW_SHELLS=True, INCLUDE_CURVES=True
)
geom_settings.set_context_ids(target_view_contexts)
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
it.set_cache(cache)
processed = set()
for elem in self.yield_from_iterator(it):
processed.add(ifc.by_guid(elem.guid))
self.serialiser.write(elem)
elements -= processed
iterators = []
for f in files:
geom_settings = ifcopenshell.geom.settings(APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True)
body_contexts = [
c.id()
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
if c.ContextIdentifier in ["Body", "Facetation"]
]
if body_contexts: if body_contexts:
geom_settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True, SEW_SHELLS=True
)
geom_settings.set_context_ids(body_contexts) geom_settings.set_context_ids(body_contexts)
exclude = f.by_type("IfcOpeningElement") it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW": it.set_cache(cache)
# For plans, we exclude windows and doors by default for elem in self.yield_from_iterator(it):
exclude += f.by_type("IfcWindow") + f.by_type("IfcDoor") self.serialiser.write(elem)
# Exclude all annotation apart from the active one so we don't cut through them
exclude += [e for e in f.by_type("IfcAnnotation") if e != self.camera_element]
iterators.append(ifcopenshell.geom.iterator(geom_settings, f, multiprocessing.cpu_count(), exclude=exclude)) geom_settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True, SEW_SHELLS=True
)
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
for elem in self.yield_from_iterator(it):
self.serialiser.write(elem)
results = ifcopenshell.draw.main(draw_settings, files, iterators=iterators, merge_projection=False) self.serialiser.finalize()
results = self.svg_buffer.get_value()
from lxml import etree
root = etree.fromstring(results) root = etree.fromstring(results)
self.enrich_linework_with_metadata(root) self.enrich_linework_with_metadata(root)
@@ -321,26 +373,57 @@ class CreateDrawing(bpy.types.Operator):
return svg_path return svg_path
def setup_serialiser(self, ifc):
self.svg_settings = ifcopenshell.geom.settings(
APPLY_DEFAULT_MATERIALS=True, DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True
)
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
self.serialiser.setFile(ifc)
self.serialiser.setWithoutStoreys(True)
self.serialiser.setPolygonal(True)
self.serialiser.setUseNamespace(True)
self.serialiser.setAlwaysProject(True)
self.serialiser.setAutoElevation(False)
self.serialiser.setAutoSection(False)
self.serialiser.setPrintSpaceNames(False)
self.serialiser.setPrintSpaceAreas(False)
self.serialiser.setDrawDoorArcs(False)
self.serialiser.setNoCSS(True)
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
self.serialiser.setScale(self.scale)
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
# tree = ifcopenshell.geom.tree()
# This instructs the tree to explode BReps into faces and return
# the style of the face when running tree.select_ray()
# tree.enable_face_styles(True)
def yield_from_iterator(self, it):
if it.initialize():
while True:
yield it.get()
if not it.next():
break
def enrich_linework_with_metadata(self, root): def enrich_linework_with_metadata(self, root):
ifc = tool.Ifc.get() ifc = tool.Ifc.get()
for el in root.findall('.//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]'): for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
classes = [] classes = ["cut"]
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid")) element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
if material: if material:
if material.is_a("IfcMaterialLayerSet"): if material.is_a("IfcMaterialLayerSet"):
name = material.LayerSetName or "null" name = material.LayerSetName or "null"
else: else:
name = getattr(material, "Name", "null") name = getattr(material, "Name", "null") or "null"
name = self.canonicalise_class_name(name) name = self.canonicalise_class_name(name)
classes.append(f"material-{name}") classes.append(f"material-{name}")
if classes: el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
def move_projection_to_bottom(self, root): def move_projection_to_bottom(self, root):
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value # https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
group = root.find('{http://www.w3.org/2000/svg}g') group = root.find("{http://www.w3.org/2000/svg}g")
#group[:] = sorted(group, key=lambda e : "projection" in e.get("class")) # group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
group[:] = reversed(group) group[:] = reversed(group)
def canonicalise_class_name(self, name): def canonicalise_class_name(self, name):
@@ -337,13 +337,15 @@ class BIMCameraProperties(PropertyGroup):
# For now, this JSON dump are all the parameters that determine a camera's "Block representation" # For now, this JSON dump are all the parameters that determine a camera's "Block representation"
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed # By checking this, you will know whether or not the camera IFC representation needs to be refreshed
def update_representation(self, obj): def update_representation(self, obj):
representation = json.dumps({ representation = json.dumps(
"matrix": [list(x) for x in obj.matrix_world], {
"raster_x": self.raster_x, "matrix": [list(x) for x in obj.matrix_world],
"raster_y": self.raster_y, "raster_x": self.raster_x,
"ortho_scale": obj.data.ortho_scale, "raster_y": self.raster_y,
"clip_end": obj.data.clip_end, "ortho_scale": obj.data.ortho_scale,
}) "clip_end": obj.data.clip_end,
}
)
if self.representation != representation: if self.representation != representation:
self.representation = representation self.representation = representation
return True return True