refactor: excel parse
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
# Copyright (c) 2020-2023, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import Iterable, Sequence
|
||||
import math
|
||||
from ezdxf.math import Vec3
|
||||
from .bezier_interpolation import (
|
||||
tangents_cubic_bezier_interpolation,
|
||||
cubic_bezier_interpolation,
|
||||
)
|
||||
from .construct2d import circle_radius_3p
|
||||
|
||||
__all__ = [
|
||||
"estimate_tangents",
|
||||
"estimate_end_tangent_magnitude",
|
||||
"create_t_vector",
|
||||
"chord_length",
|
||||
]
|
||||
|
||||
|
||||
def create_t_vector(fit_points: list[Vec3], method: str) -> list[float]:
|
||||
if method == "uniform":
|
||||
return uniform_t_vector(len(fit_points))
|
||||
elif method in ("distance", "chord"):
|
||||
return distance_t_vector(fit_points)
|
||||
elif method in ("centripetal", "sqrt_chord"):
|
||||
return centripetal_t_vector(fit_points)
|
||||
elif method == "arc":
|
||||
return arc_t_vector(fit_points)
|
||||
else:
|
||||
raise ValueError("Unknown method: {}".format(method))
|
||||
|
||||
|
||||
def uniform_t_vector(length: int) -> list[float]:
|
||||
n = float(length - 1)
|
||||
return [t / n for t in range(length)]
|
||||
|
||||
|
||||
def distance_t_vector(fit_points: list[Vec3]) -> list[float]:
|
||||
return _normalize_distances(list(linear_distances(fit_points)))
|
||||
|
||||
|
||||
def centripetal_t_vector(fit_points: list[Vec3]) -> list[float]:
|
||||
distances = [
|
||||
math.sqrt(p1.distance(p2)) for p1, p2 in zip(fit_points, fit_points[1:])
|
||||
]
|
||||
return _normalize_distances(distances)
|
||||
|
||||
|
||||
def _normalize_distances(distances: Sequence[float]) -> list[float]:
|
||||
total_length = sum(distances)
|
||||
if abs(total_length) <= 1e-12:
|
||||
return []
|
||||
params: list[float] = [0.0]
|
||||
s = 0.0
|
||||
for d in distances[:-1]:
|
||||
s += d
|
||||
params.append(s / total_length)
|
||||
params.append(1.0)
|
||||
return params
|
||||
|
||||
|
||||
def linear_distances(points: Iterable[Vec3]) -> Iterable[float]:
|
||||
prev = None
|
||||
for p in points:
|
||||
if prev is None:
|
||||
prev = p
|
||||
continue
|
||||
yield prev.distance(p)
|
||||
prev = p
|
||||
|
||||
|
||||
def chord_length(points: Iterable[Vec3]) -> float:
|
||||
return sum(linear_distances(points))
|
||||
|
||||
|
||||
def arc_t_vector(fit_points: list[Vec3]) -> list[float]:
|
||||
distances = list(arc_distances(fit_points))
|
||||
return _normalize_distances(distances)
|
||||
|
||||
|
||||
def arc_distances(fit_points: list[Vec3]) -> Iterable[float]:
|
||||
p = fit_points
|
||||
|
||||
def _radii() -> Iterable[float]:
|
||||
for i in range(len(p) - 2):
|
||||
try:
|
||||
radius = circle_radius_3p(p[i], p[i + 1], p[i + 2])
|
||||
except ZeroDivisionError:
|
||||
radius = 0.0
|
||||
yield radius
|
||||
|
||||
r: list[float] = list(_radii())
|
||||
if len(r) == 0:
|
||||
return
|
||||
r.append(r[-1]) # 2x last radius
|
||||
for k in range(0, len(p) - 1):
|
||||
distance = (p[k + 1] - p[k]).magnitude
|
||||
rk = r[k]
|
||||
if math.isclose(rk, 0):
|
||||
yield distance
|
||||
else:
|
||||
yield math.asin(distance / 2.0 / rk) * 2.0 * rk
|
||||
|
||||
|
||||
def estimate_tangents(
|
||||
points: list[Vec3], method: str = "5-points", normalize=True
|
||||
) -> list[Vec3]:
|
||||
"""Estimate tangents for curve defined by given fit points.
|
||||
Calculated tangents are normalized (unit-vectors).
|
||||
|
||||
Available tangent estimation methods:
|
||||
|
||||
- "3-points": 3 point interpolation
|
||||
- "5-points": 5 point interpolation
|
||||
- "bezier": tangents from an interpolated cubic bezier curve
|
||||
- "diff": finite difference
|
||||
|
||||
Args:
|
||||
points: start-, end- and passing points of curve
|
||||
method: tangent estimation method
|
||||
normalize: normalize tangents if ``True``
|
||||
|
||||
Returns:
|
||||
tangents as list of :class:`Vec3` objects
|
||||
|
||||
"""
|
||||
method = method.lower()
|
||||
if method.startswith("bez"):
|
||||
return tangents_cubic_bezier_interpolation(points, normalize=normalize)
|
||||
elif method.startswith("3-p"):
|
||||
return tangents_3_point_interpolation(points, normalize=normalize)
|
||||
elif method.startswith("5-p"):
|
||||
return tangents_5_point_interpolation(points, normalize=normalize)
|
||||
elif method.startswith("dif"):
|
||||
return finite_difference_interpolation(points, normalize=normalize)
|
||||
else:
|
||||
raise ValueError(f"Unknown method: {method}")
|
||||
|
||||
|
||||
def estimate_end_tangent_magnitude(
|
||||
points: list[Vec3], method: str = "chord"
|
||||
) -> tuple[float, float]:
|
||||
"""Estimate tangent magnitude of start- and end tangents.
|
||||
|
||||
Available estimation methods:
|
||||
|
||||
- "chord": total chord length, curve approximation by straight segments
|
||||
- "arc": total arc length, curve approximation by arcs
|
||||
- "bezier-n": total length from cubic bezier curve approximation, n
|
||||
segments per section
|
||||
|
||||
Args:
|
||||
points: start-, end- and passing points of curve
|
||||
method: tangent magnitude estimation method
|
||||
|
||||
"""
|
||||
if method == "chord":
|
||||
total_length = sum(p0.distance(p1) for p0, p1 in zip(points, points[1:]))
|
||||
return total_length, total_length
|
||||
elif method == "arc":
|
||||
total_length = sum(arc_distances(points))
|
||||
return total_length, total_length
|
||||
elif method.startswith("bezier-"):
|
||||
count = int(method[7:])
|
||||
s = 0.0
|
||||
for curve in cubic_bezier_interpolation(points):
|
||||
s += sum(linear_distances(curve.approximate(count)))
|
||||
return s, s
|
||||
else:
|
||||
raise ValueError(f"Unknown tangent magnitude calculation method: {method}")
|
||||
|
||||
|
||||
def tangents_3_point_interpolation(
|
||||
fit_points: list[Vec3], method: str = "chord", normalize=True
|
||||
) -> list[Vec3]:
|
||||
"""Returns from 3 points interpolated and optional normalized tangent
|
||||
vectors.
|
||||
"""
|
||||
q = [Q1 - Q0 for Q0, Q1 in zip(fit_points, fit_points[1:])]
|
||||
t = list(create_t_vector(fit_points, method))
|
||||
delta_t = [t1 - t0 for t0, t1 in zip(t, t[1:])]
|
||||
d = [qk / dtk for qk, dtk in zip(q, delta_t)]
|
||||
alpha = [dt0 / (dt0 + dt1) for dt0, dt1 in zip(delta_t, delta_t[1:])]
|
||||
tangents: list[Vec3] = [Vec3()] # placeholder
|
||||
tangents.extend(
|
||||
[(1.0 - alpha[k]) * d[k] + alpha[k] * d[k + 1] for k in range(len(d) - 1)]
|
||||
)
|
||||
tangents[0] = 2.0 * d[0] - tangents[1]
|
||||
tangents.append(2.0 * d[-1] - tangents[-1])
|
||||
if normalize:
|
||||
tangents = [v.normalize() for v in tangents]
|
||||
return tangents
|
||||
|
||||
|
||||
def tangents_5_point_interpolation(
|
||||
fit_points: list[Vec3], normalize=True
|
||||
) -> list[Vec3]:
|
||||
"""Returns from 5 points interpolated and optional normalized tangent
|
||||
vectors.
|
||||
"""
|
||||
n = len(fit_points)
|
||||
q = _delta_q(fit_points)
|
||||
|
||||
alpha = list()
|
||||
for k in range(n):
|
||||
v1 = (q[k - 1].cross(q[k])).magnitude
|
||||
v2 = (q[k + 1].cross(q[k + 2])).magnitude
|
||||
alpha.append(v1 / (v1 + v2))
|
||||
|
||||
tangents = []
|
||||
for k in range(n):
|
||||
vk = (1.0 - alpha[k]) * q[k] + alpha[k] * q[k + 1]
|
||||
tangents.append(vk)
|
||||
if normalize:
|
||||
tangents = [v.normalize() for v in tangents]
|
||||
return tangents
|
||||
|
||||
|
||||
def _delta_q(points: list[Vec3]) -> list[Vec3]:
|
||||
n = len(points)
|
||||
q = [Vec3()] # placeholder
|
||||
q.extend([points[k + 1] - points[k] for k in range(n - 1)])
|
||||
q[0] = 2.0 * q[1] - q[2]
|
||||
q.append(2.0 * q[n - 1] - q[n - 2]) # q[n]
|
||||
q.append(2.0 * q[n] - q[n - 1]) # q[n+1]
|
||||
q.append(2.0 * q[0] - q[1]) # q[-1]
|
||||
return q
|
||||
|
||||
|
||||
def finite_difference_interpolation(
|
||||
fit_points: list[Vec3], normalize=True
|
||||
) -> list[Vec3]:
|
||||
f = 2.0
|
||||
p = fit_points
|
||||
|
||||
t = [(p[1] - p[0]) / f]
|
||||
for k in range(1, len(fit_points) - 1):
|
||||
t.append((p[k] - p[k - 1]) / f + (p[k + 1] - p[k]) / f)
|
||||
t.append((p[-1] - p[-2]) / f)
|
||||
if normalize:
|
||||
t = [v.normalize() for v in t]
|
||||
return t
|
||||
|
||||
|
||||
def cardinal_interpolation(fit_points: list[Vec3], tension: float) -> list[Vec3]:
|
||||
# https://en.wikipedia.org/wiki/Cubic_Hermite_spline
|
||||
def tangent(p0, p1):
|
||||
return (p0 - p1).normalize(1.0 - tension)
|
||||
|
||||
t = [tangent(fit_points[0], fit_points[1])]
|
||||
for k in range(1, len(fit_points) - 1):
|
||||
t.append(tangent(fit_points[k + 1], fit_points[k - 1]))
|
||||
t.append(tangent(fit_points[-1], fit_points[-2]))
|
||||
return t
|
||||
Reference in New Issue
Block a user