File: test_all_ellipse_transformations.py

package info (click to toggle)
ezdxf 0.18.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 46,952 kB
  • sloc: python: 158,141; javascript: 166; cpp: 138; makefile: 116; lisp: 20
file content (138 lines) | stat: -rw-r--r-- 3,995 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#  Copyright (c) 2021, Manfred Moitzi
#  License: MIT License

import pytest
from itertools import product
import math
from ezdxf.math import Matrix44, linspace, Vec3
from ezdxf.entities import Ellipse

UNIFORM_SCALING = [
    (2, 2, 2),
    (-1, 1, 1),
    (1, -1, 1),
    (1, 1, -1),
    (-2, -2, 2),
    (2, -2, -2),
    (-2, 2, -2),
    (-3, -3, -3),
]
NON_UNIFORM_SCALING = [
    (-1, 2, 3),
    (1, -2, 3),
    (1, 2, -3),
    (-3, -2, 1),
    (3, -2, -1),
    (-3, 2, -1),
    (-3, -2, -1),
]

# chose fixed "random" values to narrow error space:
DELTA = [-2, -1, 0, 1, 2]


def synced_transformation(entity, chk, m: Matrix44):
    entity = entity.copy()
    entity.transform(m)
    chk = list(m.transform_vertices(chk))
    return entity, chk


def synced_scaling(entity, chk, sx=1, sy=1, sz=1):
    entity = entity.copy()
    entity.scale(sx, sy, sz)
    chk = list(Matrix44.scale(sx, sy, sz).transform_vertices(chk))
    return entity, chk


def build(angle, dx, dy, dz, axis, start, end, count):
    ellipse = Ellipse.new(
        dxfattribs={
            "start_param": start,
            "end_param": end,
        }
    )
    vertices = list(ellipse.vertices(ellipse.params(count)))
    m = Matrix44.chain(
        Matrix44.axis_rotate(axis=axis, angle=angle),
        Matrix44.translate(dx=dx, dy=dy, dz=dz),
    )
    return synced_transformation(ellipse, vertices, m)


def check(ellipse, vertices, count):
    ellipse_vertices = list(ellipse.vertices(ellipse.params(count)))
    # Ellipse vertices may appear in reverse order
    if not vertices[0].isclose(ellipse_vertices[0], abs_tol=1e-5):
        ellipse_vertices.reverse()

    return all(
        vtx.isclose(chk, abs_tol=1e-5)
        for vtx, chk in zip(ellipse_vertices, vertices)
    )


@pytest.mark.parametrize("sx, sy, sz", UNIFORM_SCALING + NON_UNIFORM_SCALING)
@pytest.mark.parametrize(
    "start, end",
    [
        # closed ellipse fails at non uniform scaling test, because no start-
        # and end param adjustment is applied, so generated vertices do not
        # match test vertices.
        (0, math.pi),  # half ellipse as special case
        (math.pi / 6, math.pi / 6 * 11),  # start < end
        (math.pi / 6 * 11, math.pi / 6),  # start > end
    ],
)
def test_random_ellipse_transformations(sx, sy, sz, start, end):
    vertex_count = 8

    for angle in linspace(0, math.tau, 19):
        for dx, dy, dz in product([2, 0, -2], repeat=3):
            axis = Vec3.random()

            config = (
                f"CONFIG sx={sx}, sy={sy}, sz={sz}; "
                f"start={start:.4f}, end={end:.4f}; angle={angle};"
                f"dx={dx}, dy={dy}, dz={dz}; axis={str(axis)}"
            )
            ellipse0, vertices0 = build(
                angle, dx, dy, dz, axis, start, end, vertex_count
            )
            assert check(ellipse0, vertices0, vertex_count) is True, config
            ellipse1, vertices1 = synced_scaling(
                ellipse0, vertices0, sx, sy, sz
            )
            assert check(ellipse1, vertices1, vertex_count) is True, config


# Detected error conditions:
ERROR_CONFIGS = [
    dict(
        scale=(-3, 2, -1),
        start=5.7596,
        end=0.5236,
        shift=(2, 2, 2),
        axis=(0.9071626602531079, 0.30514034769743803, -0.28973311175906485),
        angles=[4.886921],
    )  # Error occurs only for angle = 280 deg!
]


@pytest.mark.parametrize("config", ERROR_CONFIGS)
def test_error_config(config):
    count = 8
    sx, sy, sz = config["scale"]
    dx, dy, dz = config["shift"]
    start = config["start"]
    end = config["end"]
    axis = config["axis"]
    for angle in config["angles"]:
        ellipse0, vertices0 = build(angle, dx, dy, dz, axis, start, end, count)
        assert check(ellipse0, vertices0, count) is True
        ellipse1, vertices1 = synced_scaling(ellipse0, vertices0, sx, sy, sz)
        assert check(ellipse1, vertices1, count) is True


if __name__ == "__main__":
    pytest.main([__file__])