init_code
This commit is contained in:
164
pygarment/garmentcode/utils.py
Normal file
164
pygarment/garmentcode/utils.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from typing import TypeVar, Generic, Sequence, Callable
|
||||
|
||||
import numpy as np
|
||||
from numpy.linalg import norm
|
||||
from scipy.spatial.transform import Rotation
|
||||
import svgpathtools as svgpath
|
||||
|
||||
|
||||
|
||||
# proper inserstions by key with bicest module in python <3.10
|
||||
# https://stackoverflow.com/questions/27672494/how-to-use-bisect-insort-left-with-a-key
|
||||
|
||||
T = TypeVar('T')
|
||||
V = TypeVar('V')
|
||||
|
||||
|
||||
class KeyWrapper(Generic[T, V]):
|
||||
def __init__(self, iterable: Sequence[T], key: Callable[[T], V]):
|
||||
self.it = iterable
|
||||
self.key = key
|
||||
|
||||
def __getitem__(self, i: int) -> V:
|
||||
return self.key(self.it[i])
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.it)
|
||||
|
||||
|
||||
def vector_angle(v1, v2):
|
||||
"""Find an angle between two 2D vectors"""
|
||||
v1, v2 = np.asarray(v1), np.asarray(v2)
|
||||
cos = np.dot(v1, v2) / (norm(v1) * norm(v2))
|
||||
angle = np.arccos(cos)
|
||||
# Cross to indicate correct relative orienataion of v2 w.r.t. v1
|
||||
cross = np.cross(v1, v2)
|
||||
|
||||
if abs(cross) > 1e-5:
|
||||
angle *= np.sign(cross)
|
||||
return angle
|
||||
|
||||
|
||||
def R2D(angle):
|
||||
"""2D rotation matrix by an angle"""
|
||||
return np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
|
||||
|
||||
|
||||
def vector_align_3D(v1, v2):
|
||||
"""Find a rotation to align v1 with v2"""
|
||||
|
||||
v1, v2 = np.asarray(v1), np.asarray(v2)
|
||||
cos = np.dot(v1, v2) / (norm(v1) * norm(v2))
|
||||
cos = max(min(cos, 1), -1) # NOTE: getting rid of numbers like 1.000002 that appear due to numerical instability
|
||||
|
||||
angle = np.arccos(cos)
|
||||
|
||||
# Cross to get the axis of rotation
|
||||
cross = np.cross(v1, v2)
|
||||
cross = cross / norm(cross)
|
||||
|
||||
return Rotation.from_rotvec(cross * angle)
|
||||
|
||||
|
||||
def close_enough(f1, f2=0, tol=1e-4):
|
||||
"""Compare two floats correctly """
|
||||
return abs(f1 - f2) < tol
|
||||
|
||||
|
||||
def bbox_paths(paths):
|
||||
"""Bounding box of a list of paths/Edge Sequences"""
|
||||
|
||||
bboxes = np.array([p.bbox() for p in paths])
|
||||
return (min(bboxes[:, 0]), max(bboxes[:, 1]),
|
||||
min(bboxes[:, 2]), max(bboxes[:, 3]))
|
||||
|
||||
|
||||
def lin_interpolation(val1, val2, factor):
|
||||
"""Linear interpolation between val1 and val2 with factor [0, 1]
|
||||
|
||||
with factor == 0, output is val1
|
||||
with factor == 1, output is val2
|
||||
"""
|
||||
if factor < 0 or factor > 1:
|
||||
raise ValueError(f'lin_interpolation::ERROR::Expected a factor \in [0, 1], got {factor}')
|
||||
|
||||
return (1 - factor) * val1 + factor * val2
|
||||
|
||||
|
||||
# ---- Complex numbers converters -----
|
||||
def c_to_list(num):
|
||||
"""Convert complex number to a list of 2 elements
|
||||
Allows processing of lists of complex numbers
|
||||
"""
|
||||
|
||||
if isinstance(num, (list, tuple, set, np.ndarray)):
|
||||
return [c_to_list(n) for n in num]
|
||||
else:
|
||||
return [num.real, num.imag]
|
||||
|
||||
|
||||
def c_to_np(num):
|
||||
"""Convert complex number to a numpy array of 2 elements
|
||||
Allows processing of lists of complex numbers
|
||||
"""
|
||||
if isinstance(num, (list, tuple, set, np.ndarray)):
|
||||
return np.asarray([c_to_list(n) for n in num])
|
||||
else:
|
||||
return np.asarray([num.real, num.imag])
|
||||
|
||||
|
||||
def list_to_c(num):
|
||||
"""Convert 2D list or list of 2D lists into complex number/list of complex
|
||||
numbers"""
|
||||
if isinstance(num[0], (list, tuple, set, np.ndarray)):
|
||||
return [complex(n[0], n[1]) for n in num]
|
||||
else:
|
||||
return complex(num[0], num[1])
|
||||
|
||||
|
||||
# ---- Nested Dictionaries shortcuts ----
|
||||
# https://stackoverflow.com/a/37704379
|
||||
def nested_get(dic, keys):
|
||||
for key in keys:
|
||||
dic = dic[key]
|
||||
return dic
|
||||
|
||||
|
||||
def nested_set(dic, keys, value):
|
||||
for key in keys[:-1]:
|
||||
dic = dic.setdefault(key, {})
|
||||
dic[keys[-1]] = value
|
||||
|
||||
|
||||
def nested_del(dic, keys):
|
||||
for key in keys[:-1]:
|
||||
dic = dic[key]
|
||||
del dic[keys[-1]]
|
||||
|
||||
|
||||
# ----- Curves -----
|
||||
def curve_extreme_points(curve, on_x=False, on_y=True):
|
||||
"""Return extreme points of the current edge
|
||||
NOTE: this does NOT include the border vertices of an edge
|
||||
"""
|
||||
# Variation of https://github.com/mathandy/svgpathtools/blob/5c73056420386753890712170da602493aad1860/svgpathtools/bezier.py#L197
|
||||
poly = svgpath.bezier2polynomial(curve, return_poly1d=True)
|
||||
|
||||
x_extremizers, y_extremizers = [], []
|
||||
if on_y:
|
||||
y = svgpath.imag(poly)
|
||||
dy = y.deriv()
|
||||
|
||||
y_extremizers = svgpath.polyroots(dy, realroots=True,
|
||||
condition=lambda r: 0 < r < 1)
|
||||
if on_x:
|
||||
x = svgpath.real(poly)
|
||||
dx = x.deriv()
|
||||
x_extremizers = svgpath.polyroots(dx, realroots=True,
|
||||
condition=lambda r: 0 < r < 1)
|
||||
all_extremizers = x_extremizers + y_extremizers
|
||||
|
||||
extreme_points = np.array([c_to_list(curve.point(t))
|
||||
for t in all_extremizers])
|
||||
|
||||
return extreme_points
|
||||
Reference in New Issue
Block a user