This commit is contained in:
Christian Anetzberger
2026-01-22 20:23:51 +01:00
commit a197de9456
4327 changed files with 1235205 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
# 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]