mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-10-02 04:59:52 +00:00
Drawing module now is in sync with latest C++ upgrades. See #1153.
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user