2022-01-19 12:18:33 +11:00
|
|
|
# IfcOpenShell - IFC toolkit and geometry engine
|
|
|
|
|
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.com>
|
|
|
|
|
#
|
|
|
|
|
# This file is part of IfcOpenShell.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
|
|
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2022-05-09 15:35:52 +10:00
|
|
|
"""2D drawing generation and serialisation"""
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
import math
|
|
|
|
|
import json
|
|
|
|
|
import functools
|
|
|
|
|
|
|
|
|
|
import ifcopenshell
|
|
|
|
|
import ifcopenshell.geom
|
|
|
|
|
|
|
|
|
|
from xml.dom.minidom import parseString
|
|
|
|
|
from dataclasses import dataclass, fields
|
2024-07-02 11:40:38 +05:00
|
|
|
from typing import Callable, Sequence
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
import numpy
|
|
|
|
|
|
|
|
|
|
W = ifcopenshell.ifcopenshell_wrapper
|
|
|
|
|
|
|
|
|
|
WHITE = numpy.array((1.0, 1.0, 1.0))
|
|
|
|
|
|
|
|
|
|
DO_NOTHING = lambda *args: None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
class draw_settings:
|
|
|
|
|
width: float = 297.0
|
|
|
|
|
height: float = 420.0
|
|
|
|
|
scale: float = 1.0 / 100.0
|
2021-08-17 15:58:15 +02:00
|
|
|
auto_elevation: bool = False
|
|
|
|
|
auto_section: bool = False
|
|
|
|
|
auto_floorplan: bool = True
|
|
|
|
|
space_names: bool = False
|
|
|
|
|
space_areas: bool = False
|
|
|
|
|
door_arcs: bool = False
|
2021-11-05 11:03:44 +01:00
|
|
|
subtract_before_hlr: bool = False
|
|
|
|
|
cache: bool = False
|
2021-08-17 15:58:15 +02:00
|
|
|
css: bool = True
|
|
|
|
|
storey_heights: str = "none"
|
2021-08-17 14:55:32 +02:00
|
|
|
include_entities: str = ""
|
|
|
|
|
exclude_entities: str = "IfcOpeningElement"
|
2021-10-27 19:42:36 +11:00
|
|
|
drawing_guid: str = ""
|
2021-11-20 14:05:11 +01:00
|
|
|
profile_threshold: int = -1
|
|
|
|
|
cells: bool = True
|
2022-04-24 14:54:49 +02:00
|
|
|
merge_cells: bool = False
|
2022-11-12 15:49:29 +01:00
|
|
|
include_projection: bool = True
|
2023-06-29 13:10:17 +02:00
|
|
|
prefilter: bool = True
|
2023-08-10 15:44:21 +08:00
|
|
|
include_curves: bool = False
|
2023-08-25 12:43:32 +02:00
|
|
|
unify_inputs: bool = True
|
2021-10-27 19:42:36 +11:00
|
|
|
|
|
|
|
|
|
2024-07-02 11:40:38 +05:00
|
|
|
def main(
|
|
|
|
|
settings: draw_settings,
|
|
|
|
|
files: list[ifcopenshell.file],
|
|
|
|
|
iterators: Sequence[ifcopenshell.geom.iterator] = (),
|
|
|
|
|
merge_projection: bool = True,
|
|
|
|
|
progress_function: Callable = DO_NOTHING,
|
|
|
|
|
):
|
2021-11-05 11:03:44 +01:00
|
|
|
|
|
|
|
|
geom_settings = ifcopenshell.geom.settings(
|
|
|
|
|
# when not doing booleans, proper solids from shells isn't a requirement
|
2024-07-04 11:08:27 +05:00
|
|
|
REORIENT_SHELLS=settings.subtract_before_hlr,
|
2021-11-05 11:03:44 +01:00
|
|
|
)
|
|
|
|
|
|
2024-07-02 14:53:19 +05:00
|
|
|
# this is required for serialization
|
|
|
|
|
geom_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
|
|
|
|
geom_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
|
|
|
|
|
geom_settings.set("apply-default-materials", True)
|
|
|
|
|
|
2021-10-27 19:42:36 +11:00
|
|
|
if not iterators:
|
|
|
|
|
iterator_kwargs = {}
|
|
|
|
|
if settings.include_entities:
|
|
|
|
|
iterator_kwargs["include"] = settings.include_entities.split(",")
|
|
|
|
|
elif settings.exclude_entities:
|
|
|
|
|
iterator_kwargs["exclude"] = settings.exclude_entities.split(",")
|
|
|
|
|
|
|
|
|
|
# We have to keep the iterator in memory because otherwise
|
|
|
|
|
# the styles are cleared up.
|
|
|
|
|
iterators = list(
|
|
|
|
|
map(
|
2021-11-05 11:03:44 +01:00
|
|
|
functools.partial(ifcopenshell.geom.iterator, geom_settings, **iterator_kwargs),
|
2021-10-27 19:42:36 +11:00
|
|
|
files,
|
|
|
|
|
)
|
2021-08-17 14:55:32 +02:00
|
|
|
)
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-05 11:03:44 +01:00
|
|
|
if settings.cache:
|
|
|
|
|
cache = ifcopenshell.geom.serializers.hdf5("cache.h5", geom_settings)
|
|
|
|
|
for it in iterators:
|
|
|
|
|
it.set_cache(cache)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
# Initialize serializer
|
|
|
|
|
buffer = ifcopenshell.geom.serializers.buffer()
|
2024-07-02 14:53:19 +05:00
|
|
|
serialiser_settings = ifcopenshell.geom.serializer_settings()
|
|
|
|
|
sr = ifcopenshell.geom.serializers.svg(buffer, geom_settings, serialiser_settings)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
2021-08-17 15:58:15 +02:00
|
|
|
sr.setFile(files[0])
|
|
|
|
|
if settings.auto_floorplan:
|
|
|
|
|
sr.setSectionHeightsFromStoreys()
|
2021-08-17 14:55:32 +02:00
|
|
|
|
2021-10-27 19:42:36 +11:00
|
|
|
if settings.drawing_guid:
|
|
|
|
|
sr.setElevationRefGuid(settings.drawing_guid)
|
|
|
|
|
sr.setWithoutStoreys(True)
|
|
|
|
|
# If you want to filter by IfcAnnotation ObjectType named "DRAWING"
|
2022-01-10 15:42:24 +11:00
|
|
|
# sr.setElevationRef("DRAWING")
|
2021-10-27 19:42:36 +11:00
|
|
|
|
2021-08-17 14:55:32 +02:00
|
|
|
# required for svgfill
|
|
|
|
|
sr.setPolygonal(True)
|
|
|
|
|
sr.setUseNamespace(True)
|
|
|
|
|
|
2022-11-12 15:49:29 +01:00
|
|
|
sr.setAlwaysProject(settings.include_projection)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
2021-11-20 14:05:11 +01:00
|
|
|
sr.setProfileThreshold(settings.profile_threshold)
|
2021-08-17 14:55:32 +02:00
|
|
|
sr.setBoundingRectangle(settings.width, settings.height)
|
|
|
|
|
sr.setScale(settings.scale)
|
|
|
|
|
sr.setAutoElevation(settings.auto_elevation)
|
|
|
|
|
sr.setAutoSection(settings.auto_section)
|
2021-08-17 15:58:15 +02:00
|
|
|
sr.setPrintSpaceNames(settings.space_names)
|
|
|
|
|
sr.setPrintSpaceAreas(settings.space_areas)
|
|
|
|
|
sr.setDrawDoorArcs(settings.door_arcs)
|
|
|
|
|
sr.setNoCSS(not settings.css)
|
2021-11-05 11:03:44 +01:00
|
|
|
if settings.subtract_before_hlr:
|
|
|
|
|
sr.setSubtractionSettings(W.ALWAYS)
|
2021-10-27 19:42:36 +11:00
|
|
|
|
2023-06-29 13:10:17 +02:00
|
|
|
sr.setUsePrefiltering(settings.prefilter)
|
2023-08-25 12:43:32 +02:00
|
|
|
sr.setUnifyInputs(settings.unify_inputs)
|
2023-06-29 13:10:17 +02:00
|
|
|
|
2021-08-17 15:58:15 +02:00
|
|
|
try:
|
2022-01-10 15:42:24 +11:00
|
|
|
sh = ["none", "full", "left"].index(settings.storey_heights)
|
2021-08-17 15:58:15 +02:00
|
|
|
sr.setDrawStoreyHeights(sh)
|
|
|
|
|
except:
|
|
|
|
|
raise ValueError("storey_heights should be one of {'none', 'full', 'left'}")
|
2021-10-27 19:42:36 +11:00
|
|
|
|
2021-08-17 14:55:32 +02:00
|
|
|
"""
|
|
|
|
|
# It is also possible to add drawing planes manually
|
2021-10-27 19:42:36 +11:00
|
|
|
import bpy
|
|
|
|
|
obj = bpy.context.active_object
|
2021-08-17 14:55:32 +02:00
|
|
|
sr.addDrawing(
|
2021-10-27 19:42:36 +11:00
|
|
|
obj.matrix_world.transposed()[3][0:3], # location
|
|
|
|
|
obj.matrix_world.transposed()[2][0:3], # z axis (view direction)
|
|
|
|
|
obj.matrix_world.transposed()[0][0:3], # x axis
|
2021-08-17 14:55:32 +02:00
|
|
|
"Test", # drawing name
|
|
|
|
|
True # include projection
|
|
|
|
|
)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Initialize tree
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# Loop over iterators for geometric content
|
|
|
|
|
for i, it in enumerate(iterators):
|
2024-07-02 11:40:38 +05:00
|
|
|
for elem in it:
|
2021-08-17 14:55:32 +02:00
|
|
|
sr.write(elem)
|
|
|
|
|
if elem.type != "IfcSpace":
|
|
|
|
|
tree.add_element(elem)
|
|
|
|
|
progress_function("file", i, "progress", it.progress())
|
|
|
|
|
|
|
|
|
|
progress_function("hidden line rendering")
|
|
|
|
|
sr.finalize()
|
|
|
|
|
|
|
|
|
|
# Obtain SVG output from serializer buffer
|
|
|
|
|
svg_data_1 = buffer.get_value()
|
|
|
|
|
|
2021-10-27 19:42:36 +11:00
|
|
|
if not merge_projection:
|
|
|
|
|
return svg_data_1
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:11 +01:00
|
|
|
if not settings.cells:
|
|
|
|
|
return svg_data_1.encode("ascii", "xmlcharrefreplace")
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2021-08-17 14:55:32 +02:00
|
|
|
def yield_groups(n):
|
|
|
|
|
if n.nodeType == n.ELEMENT_NODE and n.tagName == "g":
|
|
|
|
|
yield n
|
|
|
|
|
for c in n.childNodes:
|
|
|
|
|
yield from yield_groups(c)
|
|
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
dom1 = parseString(svg_data_1)
|
|
|
|
|
svg1 = dom1.childNodes[0]
|
2021-08-17 14:55:32 +02:00
|
|
|
# From file 1 we take the groups to be substituted
|
|
|
|
|
groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"]
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
# Parse SVG into vector of line segments
|
|
|
|
|
#
|
|
|
|
|
# The second argument 'projection' tells the parser to only include <g> groups
|
|
|
|
|
# that have the classname 'projection'. The IfcOpenShell SVG serializer puts
|
|
|
|
|
# the hidden line rendering output into this group. So the sections are not
|
|
|
|
|
# included here as they already form closed loops.
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
ls_groups = W.svg_to_line_segments(svg_data_1, "projection")
|
|
|
|
|
for i, (ls, g1) in enumerate(zip(ls_groups, groups1)):
|
|
|
|
|
progress_function("creating cells", i)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2021-08-17 14:55:32 +02:00
|
|
|
projection, g1 = g1, g1.parentNode
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
svgfill_context = W.context(W.FILTERED_CARTESIAN_QUOTIENT, 1.0e-3)
|
2023-04-21 16:29:04 +02:00
|
|
|
|
|
|
|
|
# remove duplicates (without tolerance)
|
|
|
|
|
ls = list(map(tuple, set(map(frozenset, ls))))
|
|
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
svgfill_context.add(ls)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
if settings.merge_cells:
|
|
|
|
|
# To be refined:
|
|
|
|
|
# - Find cells on original line segments
|
|
|
|
|
# - Associate cells with IFC entities for merging
|
|
|
|
|
# - Merge cells by discarding edges
|
2024-07-02 14:49:14 +05:00
|
|
|
# - Associate cells with IFC entities for styling
|
2022-04-24 14:54:49 +02:00
|
|
|
num_passes = 1
|
|
|
|
|
else:
|
|
|
|
|
num_passes = 0
|
2024-07-02 14:49:14 +05:00
|
|
|
|
|
|
|
|
for iteration in range(num_passes + 1):
|
|
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
# initialize empty group, note that in the current approach only one
|
|
|
|
|
# group is stored
|
|
|
|
|
ps = W.svg_groups_of_polygons()
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
if iteration != 0 or svgfill_context.build():
|
|
|
|
|
svgfill_context.write(ps)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
"""
|
|
|
|
|
# Debugging tool to plot line segments and cells
|
|
|
|
|
from matplotlib import pyplot as plt
|
|
|
|
|
|
|
|
|
|
arr = numpy.array(ls).reshape((-1, 2, 2))
|
|
|
|
|
for x in arr:
|
|
|
|
|
plt.plot(x.T[0], x.T[1])
|
|
|
|
|
for x in ps[0]:
|
|
|
|
|
plt.fill(numpy.array(x.boundary).T[0], numpy.array(x.boundary).T[1])
|
|
|
|
|
"""
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
if iteration != num_passes:
|
|
|
|
|
pairs = svgfill_context.get_face_pairs()
|
2024-07-02 14:49:14 +05:00
|
|
|
semantics = [None] * (max(pairs) + 1)
|
2022-04-24 14:54:49 +02:00
|
|
|
# For every edge print the two neighbouring faces
|
|
|
|
|
# for x in range(0, len(pairs), 2):
|
|
|
|
|
# print(x // 2, *pairs[x:x+2])
|
|
|
|
|
|
|
|
|
|
# Reserialize cells into an SVG string
|
|
|
|
|
svg_data_2 = W.polygons_to_svg(ps, True)
|
|
|
|
|
|
|
|
|
|
# We parse both SVG files to create on document with the combination of sections from
|
|
|
|
|
# the output directly from the serializer and the cells found from the hidden line
|
|
|
|
|
# rendering
|
|
|
|
|
dom2 = parseString(svg_data_2)
|
|
|
|
|
svg2 = dom2.childNodes[0]
|
|
|
|
|
# file 2 only has the groups we are interested in.
|
|
|
|
|
# in fact in the approach, it's only a single group
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
g2 = list(yield_groups(svg2))[0]
|
|
|
|
|
|
|
|
|
|
# These are attributes on the original group that we can use to reconstruct
|
|
|
|
|
# a 4x4 matrix of the projection used in the SVG generation process
|
|
|
|
|
nm = g1.getAttribute("ifc:name")
|
|
|
|
|
m4 = numpy.array(json.loads(g1.getAttribute("ifc:plane")))
|
|
|
|
|
m3 = numpy.array(json.loads(g1.getAttribute("ifc:matrix3")))
|
|
|
|
|
m44 = numpy.eye(4)
|
|
|
|
|
m44[0][0:2] = m3[0][0:2]
|
|
|
|
|
m44[1][0:2] = m3[1][0:2]
|
|
|
|
|
m44[0][3] = m3[0][2]
|
|
|
|
|
m44[1][3] = m3[1][2]
|
|
|
|
|
m44 = numpy.linalg.inv(m44)
|
|
|
|
|
|
|
|
|
|
def project(xy, z=0.0):
|
|
|
|
|
xyzw = m44 @ numpy.array(xy + [z, 1.0])
|
|
|
|
|
xyzw[1] *= -1.0
|
|
|
|
|
return (m4 @ xyzw)[0:3]
|
|
|
|
|
|
|
|
|
|
def pythonize(arr):
|
|
|
|
|
return tuple(map(float, arr))
|
|
|
|
|
|
|
|
|
|
# Loop over the cell paths
|
|
|
|
|
for pi, p in enumerate(g2.getElementsByTagName("path")):
|
|
|
|
|
|
|
|
|
|
progress_function("group", i, "pass", iteration, "path", pi)
|
|
|
|
|
|
|
|
|
|
d = p.getAttribute("d")
|
|
|
|
|
# point inside is an attribute that comes from line_segments_to_polygons()
|
|
|
|
|
# it is an arbitrary point guaranteed to be inside the polygon and outside
|
|
|
|
|
# of any potential inner bounds. We can use this to construct a ray to find
|
|
|
|
|
# the face of the IFC element that the cell belongs to.
|
|
|
|
|
assert p.hasAttribute("ifc:pointInside")
|
|
|
|
|
|
|
|
|
|
xy = list(map(float, p.getAttribute("ifc:pointInside").split(",")))
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
a, b = project(xy, 0.0), project(xy, -100.0)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
inside_elements = tree.select(pythonize(a))
|
2024-07-02 14:49:14 +05:00
|
|
|
|
|
|
|
|
if inside_elements:
|
2022-04-24 14:54:49 +02:00
|
|
|
elements = None
|
|
|
|
|
if iteration != num_passes:
|
|
|
|
|
semantics[pi] = (inside_elements[0], -1)
|
|
|
|
|
else:
|
|
|
|
|
elements = tree.select_ray(pythonize(a), pythonize(b - a))
|
|
|
|
|
|
|
|
|
|
if elements:
|
|
|
|
|
# Put the IFC element entity type on the path for CSS-based styling
|
|
|
|
|
p.setAttribute("class", elements[0].instance.is_a())
|
|
|
|
|
|
|
|
|
|
# Obtain style (IfcOpenShell IfcGeom::Material)
|
|
|
|
|
style = tree.styles()[elements[0].style_index]
|
|
|
|
|
|
|
|
|
|
# This is just a demonstration. We compose a factor of using:
|
|
|
|
|
# - ray intersection distance
|
|
|
|
|
# - dot product ray . face normal
|
|
|
|
|
# - style transparency
|
|
|
|
|
# the factor determines how much white will be interpolated
|
|
|
|
|
# into the style diffuse color.
|
2024-07-03 20:02:13 +02:00
|
|
|
clr = numpy.array(style.diffuse if style else (0.6, 0.6, 0.6))
|
2022-04-24 14:54:49 +02:00
|
|
|
factor = (math.log(elements[0].distance + 2.0) / 7.0) * (1.0 - 0.5 * abs(elements[0].dot_product))
|
2024-07-03 20:02:13 +02:00
|
|
|
if style and style.has_transparency:
|
2022-04-24 14:54:49 +02:00
|
|
|
factor *= 1.0 - style.transparency
|
|
|
|
|
clr = WHITE * (1.0 - factor) + clr * factor
|
|
|
|
|
|
|
|
|
|
svg_fill = "rgb(%s)" % ", ".join(str(f * 255.0) for f in clr[0:3])
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
if iteration != num_passes:
|
|
|
|
|
semantics[pi] = elements[0]
|
|
|
|
|
else:
|
|
|
|
|
svg_fill = "none"
|
|
|
|
|
|
|
|
|
|
p.setAttribute("style", "fill: " + svg_fill)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
if iteration != num_passes:
|
|
|
|
|
to_remove = []
|
|
|
|
|
|
|
|
|
|
for he_idx in range(0, len(pairs), 2):
|
|
|
|
|
# @todo instead of ray_distance, better do (x.point - y.point).dot(x.normal)
|
|
|
|
|
# to see if they're coplanar, because ray-distance will be different in case
|
|
|
|
|
# of element surfaces non-orthogonal to the view direction
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
def format(x):
|
2024-07-02 14:49:14 +05:00
|
|
|
if x is None:
|
|
|
|
|
return None
|
2022-04-24 14:54:49 +02:00
|
|
|
elif isinstance(x, tuple):
|
|
|
|
|
# found to be inside element using tree.select() no face or style info
|
|
|
|
|
return x
|
2024-07-02 14:49:14 +05:00
|
|
|
else:
|
|
|
|
|
return (x.instance.is_a(), x.ray_distance, tuple(x.position))
|
|
|
|
|
|
|
|
|
|
pp = pairs[he_idx : he_idx + 2]
|
2022-04-24 14:54:49 +02:00
|
|
|
if pp == (-1, -1):
|
|
|
|
|
continue
|
|
|
|
|
data = list(map(format, map(semantics.__getitem__, pp)))
|
2024-07-02 14:49:14 +05:00
|
|
|
if None not in data and data[0][0] == data[1][0] and abs(data[0][1] - data[1][1]) < 1.0e-5:
|
2022-04-24 14:54:49 +02:00
|
|
|
to_remove.append(he_idx // 2)
|
|
|
|
|
# Print edge index and semantic data
|
|
|
|
|
# print(he_idx // 2, *data)
|
2024-07-02 14:49:14 +05:00
|
|
|
|
2022-04-24 14:54:49 +02:00
|
|
|
svgfill_context.merge(to_remove)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
# Swap the XML nodes from the files
|
|
|
|
|
# Remove the original hidden line node we still have in the serializer output
|
|
|
|
|
g1.removeChild(projection)
|
|
|
|
|
g2.setAttribute("class", "projection")
|
|
|
|
|
# Find the children of the projection node parent
|
|
|
|
|
children = [x for x in g1.childNodes if x.nodeType == x.ELEMENT_NODE]
|
|
|
|
|
if children:
|
|
|
|
|
# Insert the new semantically enriched cell-based projection node
|
|
|
|
|
# *before* the node with sections from the serializer. SVG derives
|
|
|
|
|
# draw order from node order in the DOM so sections are draw over
|
|
|
|
|
# the projections.
|
|
|
|
|
g1.insertBefore(g2, children[0])
|
|
|
|
|
else:
|
|
|
|
|
# This generally shouldn't happen
|
|
|
|
|
g1.appendChild(g2)
|
|
|
|
|
|
|
|
|
|
data = dom1.toxml()
|
|
|
|
|
data = data.encode("ascii", "xmlcharrefreplace")
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import sys
|
2021-11-20 14:05:53 +01:00
|
|
|
import time
|
2021-08-17 14:55:32 +02:00
|
|
|
import argparse
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
times = []
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
def measure(task, fn):
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
r = fn()
|
|
|
|
|
dt = time.time() - t0
|
|
|
|
|
times.append((task, dt))
|
|
|
|
|
return r
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
def print_progress(*args):
|
2022-01-10 15:42:24 +11:00
|
|
|
print("\r", *args, " " * 10, end="", flush=True)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
|
2024-07-02 15:14:55 +05:00
|
|
|
parser.add_argument(
|
|
|
|
|
"files",
|
|
|
|
|
type=str,
|
|
|
|
|
nargs="+",
|
|
|
|
|
help=(
|
|
|
|
|
"List of files for script to use. "
|
|
|
|
|
"Last file is considered an output file (.svg), all other files are existing IFC files."
|
|
|
|
|
),
|
|
|
|
|
)
|
2021-08-17 14:55:32 +02:00
|
|
|
|
|
|
|
|
for field in fields(draw_settings):
|
|
|
|
|
if field.type == bool:
|
2022-01-10 15:42:24 +11:00
|
|
|
parser.add_argument("--" + field.name.replace("_", "-"), dest=field.name, action="store_true")
|
|
|
|
|
parser.add_argument("--no-" + field.name.replace("_", "-"), dest=field.name, action="store_false")
|
2021-08-17 15:58:15 +02:00
|
|
|
parser.set_defaults(**{field.name: field.default})
|
2021-08-17 14:55:32 +02:00
|
|
|
else:
|
|
|
|
|
parser.add_argument(
|
2021-08-17 15:58:15 +02:00
|
|
|
"--" + field.name.replace("_", "-"), dest=field.name, type=field.type, default=field.default
|
2021-08-17 14:55:32 +02:00
|
|
|
)
|
|
|
|
|
|
2024-07-02 15:14:55 +05:00
|
|
|
args = vars(parser.parse_args())
|
2021-08-17 14:55:32 +02:00
|
|
|
files = args.pop("files")
|
|
|
|
|
output = files.pop()
|
|
|
|
|
|
|
|
|
|
settings = draw_settings(**args)
|
|
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
files = measure("open files", lambda: list(map(ifcopenshell.open, files)))
|
|
|
|
|
result = measure("processing", lambda: main(settings, files, progress_function=print_progress))
|
|
|
|
|
open(output, "wb").write(result)
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
print("\r Done!", " " * 20)
|
2022-01-10 15:42:24 +11:00
|
|
|
|
2021-11-20 14:05:53 +01:00
|
|
|
for t, dt in times:
|
|
|
|
|
print(f"{t}: {dt}")
|