mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
experimental svg to dxf conversion for drawings
Example - https://imgur.com/a/AkvLlbZ
This commit is contained in:
@@ -363,6 +363,8 @@ endif
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
|
||||
# Required for SVG to DXF conversion
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "ezxdf" --target=./site-packages
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ classes = (
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
operator.ContractTargetView,
|
||||
operator.ConvertSVGToDXF,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableAddAnnotationType,
|
||||
|
||||
@@ -44,7 +44,7 @@ import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.export_ifc
|
||||
from blenderbim.bim.module.drawing.decoration import CutDecorator
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
from typing import NamedTuple, List
|
||||
from typing import NamedTuple, List, Union
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Color, Matrix
|
||||
from timeit import default_timer as timer
|
||||
@@ -2685,3 +2685,50 @@ class AddReferenceImage(bpy.types.Operator, Operator):
|
||||
)
|
||||
tool.Style.reload_material_from_ifc(material)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
|
||||
class ConvertSVGToDXF(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_svg_to_dxf"
|
||||
bl_label = "Convert SVG to DXF"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
view: bpy.props.StringProperty()
|
||||
bl_description = "Convert current drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all selected drawings"
|
||||
convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# convert all drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.open_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if self.convert_all:
|
||||
drawings = [
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
|
||||
|
||||
drawing_uris: list[Path] = []
|
||||
drawings_not_found: list[str] = []
|
||||
|
||||
for drawing in drawings:
|
||||
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
|
||||
if drawing_uri is None or not os.path.exists(drawing_uri):
|
||||
drawings_not_found.append(drawing.Name)
|
||||
else:
|
||||
drawing_uris.append(Path(drawing_uri))
|
||||
|
||||
if drawings_not_found:
|
||||
msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format(
|
||||
"\n".join(drawings_not_found)
|
||||
)
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
for drawing_uri in drawing_uris:
|
||||
tool.Drawing.convert_svg_to_dxf(drawing_uri, drawing_uri.with_suffix(".dxf"))
|
||||
|
||||
self.report({"INFO"}, f"{len(drawing_uris)} drawings were converted to .dxf.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -268,6 +268,11 @@ class BIM_PT_drawings(Panel):
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
|
||||
convert_to_dxf = row.row(align=True)
|
||||
op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA")
|
||||
op.view = active_drawing.name
|
||||
convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0
|
||||
|
||||
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
|
||||
|
||||
open_drawing_button = row.row(align=True)
|
||||
|
||||
@@ -27,7 +27,6 @@ import shutil
|
||||
import logging
|
||||
import shapely
|
||||
import platform
|
||||
from shapely.ops import unary_union
|
||||
import mathutils
|
||||
import subprocess
|
||||
import numpy as np
|
||||
@@ -43,6 +42,7 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from shapely.ops import unary_union
|
||||
from blenderbim.bim.module.drawing.data import FONT_SIZES, DecoratorData
|
||||
from blenderbim.bim.module.drawing.prop import get_diagram_scales, BOX_ALIGNMENT_POSITIONS, ANNOTATION_TYPES_DATA
|
||||
from lxml import etree
|
||||
@@ -50,6 +50,7 @@ from mathutils import Vector, Matrix
|
||||
from fractions import Fraction
|
||||
import collections
|
||||
from typing import Optional, Union
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Drawing(blenderbim.core.tool.Drawing):
|
||||
@@ -385,7 +386,9 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
def get_document_uri(cls, document, description=None):
|
||||
def get_document_uri(
|
||||
cls, document: ifcopenshell.entity_instance, description: Optional[str] = None
|
||||
) -> Union[str, None]:
|
||||
if getattr(document, "Location", None):
|
||||
if os.path.isabs(document.Location):
|
||||
return document.Location
|
||||
@@ -430,7 +433,7 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
return rel.RelatingGroup
|
||||
|
||||
@classmethod
|
||||
def get_drawing_document(cls, drawing):
|
||||
def get_drawing_document(cls, drawing: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
for rel in drawing.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
return rel.RelatingDocument
|
||||
@@ -1103,7 +1106,6 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
reference_element: ifcopenshell.entity_instance,
|
||||
context: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
|
||||
if reference_element.is_a("IfcGridAxis"):
|
||||
return cls.generate_grid_axis_reference_annotation(drawing, reference_element, context)
|
||||
|
||||
@@ -1991,3 +1993,60 @@ class Drawing(blenderbim.core.tool.Drawing):
|
||||
rotate180z = mathutils.Matrix.Rotation(math.radians(180.0), 4, "Z")
|
||||
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4() @ rotate180z
|
||||
return mathutils.Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
|
||||
|
||||
@classmethod
|
||||
def convert_svg_to_dxf(cls, svg_filepath: Path, dxf_filepath: Path) -> None:
|
||||
import ezdxf
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
IFC = "{http://www.ifcopenshell.org/ns}"
|
||||
|
||||
doc = ezdxf.new("R2010")
|
||||
msp = doc.modelspace()
|
||||
svg = ET.parse(svg_filepath).getroot()
|
||||
|
||||
def finalize_dxf():
|
||||
doc.saveas(dxf_filepath)
|
||||
|
||||
drawing = svg.findall(f"{SVG}g[@{IFC}name]")
|
||||
if not drawing:
|
||||
finalize_dxf()
|
||||
return
|
||||
drawing = drawing[0]
|
||||
|
||||
NUMBER = r"-?\d+\.?\d+"
|
||||
COORD = rf"{NUMBER},{NUMBER}"
|
||||
POLYLINE_PATTERN = rf"M{COORD} (?:L{COORD} ?)+Z? ?"
|
||||
MULTI_POLYLINE_PATTERN = rf"^({POLYLINE_PATTERN})+$"
|
||||
|
||||
for element_g in drawing.findall(f"{SVG}g"):
|
||||
paths = element_g.findall(f"{SVG}path")
|
||||
|
||||
for path in paths:
|
||||
path = path.attrib["d"]
|
||||
|
||||
if not re.match(MULTI_POLYLINE_PATTERN, path):
|
||||
# print(f'Path "{path}" doesn\'t match expected pattern {MULTI_POLYLINE_PATTERN}')
|
||||
continue
|
||||
|
||||
for polyline_path in re.findall(POLYLINE_PATTERN, path):
|
||||
points = re.findall(rf"{NUMBER}", polyline_path)
|
||||
points = [float(p) for p in points]
|
||||
POINT_SIZE = 2
|
||||
|
||||
grouped_points = []
|
||||
for i in range(0, len(points), POINT_SIZE):
|
||||
point = points[i : i + POINT_SIZE]
|
||||
point[1] *= -1
|
||||
grouped_points.append(point)
|
||||
points = grouped_points
|
||||
|
||||
# Z marks closed polylines
|
||||
is_closed_polyline = polyline_path.rstrip().endswith("Z")
|
||||
if is_closed_polyline or len(points) > 2:
|
||||
msp.add_lwpolyline(points, close=is_closed_polyline)
|
||||
else: # LINE
|
||||
msp.add_line(*points)
|
||||
|
||||
finalize_dxf()
|
||||
|
||||
Reference in New Issue
Block a user