271 lines
7.8 KiB
Python
271 lines
7.8 KiB
Python
# Copyright (c) 2021, Manfred Moitzi
|
|
# License: MIT License
|
|
#
|
|
# Purpose:
|
|
# Flips an inverted OCS defined by extrusion vector (0, 0, -1) into a WCS
|
|
# aligned OCS defined by extrusion vector (0, 0, 1).
|
|
#
|
|
# This simplifies 2D entity processing for ezdxf users and creates DXF
|
|
# output for 3rd party DXF libraries which ignore the existence of the OCS.
|
|
#
|
|
# Warning:
|
|
# The WCS representation of OCS entities with flipped extrusion vector
|
|
# is not 100% identical to the source entity, curve orientation and vertex order
|
|
# may change. A mirrored text represented by an extrusion vector (0, 0, -1)
|
|
# cannot represented by an extrusion vector (0, 0, 1), therefore this CANNOT
|
|
# work for text entities or entities including text:
|
|
# TEXT, ATTRIB, ATTDEF, MTEXT, DIMENSION, LEADER, MLEADER
|
|
from __future__ import annotations
|
|
from typing import Iterable, Sequence
|
|
from functools import singledispatch
|
|
import math
|
|
from ezdxf.math import Z_AXIS, Vec3, Vec2
|
|
from ezdxf.entities import (
|
|
DXFGraphic,
|
|
DXFNamespace,
|
|
Circle,
|
|
Arc,
|
|
Ellipse,
|
|
Solid,
|
|
Insert,
|
|
LWPolyline,
|
|
Polyline,
|
|
)
|
|
from ezdxf.entities.polygon import DXFPolygon
|
|
from ezdxf.entities.boundary_paths import (
|
|
PolylinePath,
|
|
EdgePath,
|
|
LineEdge,
|
|
ArcEdge,
|
|
EllipseEdge,
|
|
SplineEdge,
|
|
)
|
|
from ezdxf.lldxf import const
|
|
|
|
__all__ = ["upright", "upright_all"]
|
|
|
|
|
|
def upright(entity: DXFGraphic) -> None:
|
|
"""Flips an inverted :ref:`OCS` defined by extrusion vector (0, 0, -1) into
|
|
a :ref:`WCS` aligned :ref:`OCS` defined by extrusion vector (0, 0, 1).
|
|
DXF entities with other extrusion vectors and unsupported DXF entities will
|
|
be silently ignored. For more information about the limitations read the
|
|
documentation of the :mod:`ezdxf.upright` module.
|
|
|
|
"""
|
|
if not (
|
|
isinstance(entity, DXFGraphic)
|
|
and entity.is_alive
|
|
and entity.dxf.hasattr("extrusion")
|
|
):
|
|
return
|
|
extrusion = Vec3(entity.dxf.extrusion).normalize()
|
|
if extrusion.isclose(FLIPPED_Z_AXIS):
|
|
_flip_dxf_graphic(entity)
|
|
|
|
|
|
def upright_all(entities: Iterable[DXFGraphic]) -> None:
|
|
"""Call function :func:`upright` for all DXF entities in iterable
|
|
`entities`::
|
|
|
|
upright_all(doc.modelspace())
|
|
|
|
"""
|
|
for e in entities:
|
|
upright(e)
|
|
|
|
|
|
FLIPPED_Z_AXIS = -Z_AXIS
|
|
|
|
|
|
def _flip_deg_angle(angle: float) -> float:
|
|
return (180.0 if angle >= 0.0 else -180.0) - angle
|
|
|
|
|
|
def _flip_rad_angle(angle: float) -> float:
|
|
return (math.pi if angle >= 0.0 else -math.pi) - angle
|
|
|
|
|
|
def _flip_vertex(vertex: Vec3) -> Vec3:
|
|
return Vec3(-vertex.x, vertex.y, -vertex.z)
|
|
|
|
|
|
def _flip_2d_vertex(vertex: Vec2) -> Vec2:
|
|
return Vec2(-vertex.x, vertex.y)
|
|
|
|
|
|
def _flip_existing_vertex(dxf: DXFNamespace, name: str) -> None:
|
|
if dxf.hasattr(name):
|
|
vertex = _flip_vertex(dxf.get(name))
|
|
dxf.set(name, vertex)
|
|
|
|
|
|
def _flip_thickness(dxf: DXFNamespace) -> None:
|
|
if dxf.hasattr("thickness"):
|
|
dxf.thickness = -dxf.thickness
|
|
|
|
|
|
def _flip_elevation(dxf: DXFNamespace) -> None:
|
|
if dxf.hasattr("elevation"):
|
|
# works with float (LWPOLYLINE) and Vec3 (POLYLINE)
|
|
dxf.elevation = -dxf.elevation
|
|
|
|
|
|
@singledispatch
|
|
def _flip_dxf_graphic(entity: DXFGraphic) -> None:
|
|
pass # ignore unsupported types on purpose
|
|
|
|
|
|
@_flip_dxf_graphic.register(Circle)
|
|
def _flip_circle(circle: Circle) -> None:
|
|
dxf = circle.dxf
|
|
_flip_existing_vertex(dxf, "center")
|
|
_flip_thickness(dxf)
|
|
dxf.discard("extrusion")
|
|
|
|
|
|
@_flip_dxf_graphic.register(Arc)
|
|
def _flip_arc(arc: Arc) -> None:
|
|
_flip_circle(arc)
|
|
dxf = arc.dxf
|
|
end_angle = dxf.end_angle
|
|
dxf.end_angle = _flip_deg_angle(dxf.start_angle)
|
|
dxf.start_angle = _flip_deg_angle(end_angle)
|
|
|
|
|
|
# SOLID and TRACE - but not 3DFACE!
|
|
@_flip_dxf_graphic.register(Solid)
|
|
def _flip_solid(solid: Solid) -> None:
|
|
dxf = solid.dxf
|
|
for name in const.VERTEXNAMES:
|
|
_flip_existing_vertex(dxf, name)
|
|
_flip_thickness(dxf)
|
|
dxf.discard("extrusion")
|
|
|
|
|
|
@_flip_dxf_graphic.register(Insert)
|
|
def _flip_insert(insert: Insert) -> None:
|
|
# see exploration/upright_insert.py
|
|
dxf = insert.dxf
|
|
_flip_existing_vertex(dxf, "insert")
|
|
dxf.rotation = -dxf.rotation
|
|
dxf.xscale = -dxf.xscale
|
|
dxf.zscale = -dxf.zscale
|
|
# Attached attributes cannot be flipped!
|
|
dxf.discard("extrusion")
|
|
|
|
|
|
@_flip_dxf_graphic.register(Ellipse)
|
|
def _flip_ellipse(ellipse: Ellipse) -> None:
|
|
# ELLIPSE is a WCS entity!
|
|
# just process start- and end params
|
|
dxf = ellipse.dxf
|
|
end_param = -dxf.end_param
|
|
dxf.end_param = -dxf.start_param
|
|
dxf.start_param = end_param
|
|
dxf.discard("extrusion")
|
|
|
|
|
|
@_flip_dxf_graphic.register(LWPolyline)
|
|
def _flip_lwpolyline(polyline: LWPolyline) -> None:
|
|
flipped_points: list[Sequence[float]] = []
|
|
for x, y, start_width, end_width, bulge in polyline.lwpoints:
|
|
bulge = -bulge
|
|
v = _flip_2d_vertex(Vec2(x, y))
|
|
flipped_points.append((v.x, v.y, start_width, end_width, bulge))
|
|
polyline.set_points(flipped_points, format="xyseb")
|
|
_finalize_polyline(polyline.dxf)
|
|
|
|
|
|
def _finalize_polyline(dxf: DXFNamespace):
|
|
_flip_thickness(dxf)
|
|
_flip_elevation(dxf)
|
|
dxf.discard("extrusion")
|
|
|
|
|
|
@_flip_dxf_graphic.register(Polyline)
|
|
def _flip_polyline2d(polyline: Polyline) -> None:
|
|
if not polyline.is_2d_polyline:
|
|
return # ignore silently
|
|
for vertex in polyline.vertices:
|
|
dxf = vertex.dxf
|
|
_flip_existing_vertex(dxf, "location")
|
|
if dxf.hasattr("bulge"):
|
|
dxf.bulge = -dxf.bulge
|
|
_finalize_polyline(polyline.dxf)
|
|
|
|
|
|
# HATCH and MPOLYGON
|
|
@_flip_dxf_graphic.register(DXFPolygon)
|
|
def _flip_polygon(polygon: DXFPolygon) -> None:
|
|
for p in polygon.paths:
|
|
_flip_boundary_path(p)
|
|
_flip_elevation(polygon.dxf)
|
|
polygon.dxf.discard("extrusion")
|
|
|
|
|
|
@singledispatch
|
|
def _flip_boundary_path(path) -> None:
|
|
raise TypeError(f"unsupported path type: {path!r}")
|
|
|
|
|
|
@_flip_boundary_path.register(PolylinePath)
|
|
def _flip_polyline_path(polyline: PolylinePath) -> None:
|
|
flipped_vertices: list[tuple[float, float, float]] = []
|
|
for x, y, bulge in polyline.vertices:
|
|
bulge = -bulge
|
|
v = _flip_2d_vertex(Vec2(x, y))
|
|
flipped_vertices.append((v.x, v.y, bulge))
|
|
polyline.vertices = flipped_vertices
|
|
|
|
|
|
@_flip_boundary_path.register(EdgePath)
|
|
def _flip_edge_path(edges: EdgePath) -> None:
|
|
# see exploration/upright_hatch.py
|
|
for edge in edges:
|
|
_flip_edge(edge)
|
|
|
|
|
|
@singledispatch
|
|
def _flip_edge(edge) -> None:
|
|
raise TypeError(f"unsupported edge type: {edge!r}")
|
|
|
|
|
|
@_flip_edge.register(LineEdge)
|
|
def _flip_line_edge(edge: LineEdge) -> None:
|
|
edge.start = _flip_2d_vertex(edge.start)
|
|
edge.end = _flip_2d_vertex(edge.end)
|
|
|
|
|
|
@_flip_edge.register(ArcEdge)
|
|
def _flip_arc_edge(edge: ArcEdge) -> None:
|
|
edge.center = _flip_2d_vertex(edge.center)
|
|
# Start- and end angles are always stored in counter-clockwise orientation!
|
|
end_angle = edge.end_angle
|
|
edge.end_angle = _flip_deg_angle(edge.start_angle)
|
|
edge.start_angle = _flip_deg_angle(end_angle)
|
|
edge.ccw = not edge.ccw
|
|
|
|
|
|
@_flip_edge.register(EllipseEdge)
|
|
def _flip_ellipse_edge(edge: EllipseEdge) -> None:
|
|
edge.center = _flip_2d_vertex(edge.center)
|
|
edge.major_axis = _flip_2d_vertex(edge.major_axis)
|
|
# Ellipse params as angles in degrees - not radians!
|
|
# Do not exchange start- and end angles!
|
|
# see exploration/upright_hatch.py
|
|
edge.start_angle = _flip_deg_angle(edge.start_angle)
|
|
edge.end_angle = _flip_deg_angle(edge.end_angle)
|
|
edge.ccw = not edge.ccw
|
|
|
|
|
|
@_flip_edge.register(SplineEdge)
|
|
def _flip_spline_edge(edge: SplineEdge) -> None:
|
|
flip_2d_vertex = _flip_2d_vertex
|
|
if edge.start_tangent is not None:
|
|
edge.start_tangent = flip_2d_vertex(edge.start_tangent)
|
|
if edge.end_tangent is not None:
|
|
edge.end_tangent = flip_2d_vertex(edge.end_tangent)
|
|
edge.control_points = [flip_2d_vertex(v) for v in edge.control_points]
|
|
edge.fit_points = [flip_2d_vertex(v) for v in edge.fit_points]
|