init_code
This commit is contained in:
365
pygarment/garmentcode/interface.py
Normal file
365
pygarment/garmentcode/interface.py
Normal file
@@ -0,0 +1,365 @@
|
||||
from copy import copy
|
||||
|
||||
from numpy.linalg import norm
|
||||
import numpy as np
|
||||
|
||||
from pygarment.garmentcode.edge import EdgeSequence, Edge
|
||||
from pygarment.garmentcode.utils import close_enough
|
||||
|
||||
|
||||
class Interface:
|
||||
"""Description of an interface of a panel or component
|
||||
that can be used in stitches as a single unit
|
||||
"""
|
||||
def __init__(self, panel, edges, ruffle=1., right_wrong=False):
|
||||
"""
|
||||
Parameters:
|
||||
* panel - Panel object
|
||||
* edges - Edge or EdgeSequence -- edges in the panel that are
|
||||
allowed to connect to
|
||||
# TODO rename to something more generic/projection related?
|
||||
* ruffle - ruffle coefficient for a particular edge. Interface
|
||||
object will supply projecting_edges() shape
|
||||
s.t. the ruffles with the given rate are created. Default = 1.
|
||||
(no ruffles, smooth connection)
|
||||
* right_wrong -- control of stitch orientation -- indication if this interface's
|
||||
right side of the fabric should be connected to the wrong side of another interface.
|
||||
Default -- False -- connect right side of the fabric to the right side of the faric,
|
||||
sufficient in most cases.
|
||||
"""
|
||||
|
||||
self.edges = edges if isinstance(edges, EdgeSequence) else EdgeSequence(edges)
|
||||
self.panel = [panel for _ in range(len(self.edges))] # matches every edge
|
||||
self.right_wrong = [right_wrong for _ in range(len(self.edges))]
|
||||
|
||||
# Allow to enfoce change the direction of edge
|
||||
# (used in many-to-many stitches correspondance determination)
|
||||
self.edges_flipping = [False for _ in range(len(self.edges))]
|
||||
|
||||
# Ruffles are applied to sections
|
||||
# Since extending a chain of edges != extending each edge individually
|
||||
if isinstance(ruffle, list):
|
||||
assert len(ruffle) == len(edges), "Ruffles and Edges don't match"
|
||||
self.ruffle = []
|
||||
last_coef = None
|
||||
last_start = 0
|
||||
for i, coef in enumerate(ruffle):
|
||||
if coef == last_coef or last_coef is None:
|
||||
last_coef = coef # Making sure to overwrite None
|
||||
continue
|
||||
self.ruffle.append(dict(coeff=last_coef, sec=[last_start, i]))
|
||||
last_start, last_coef = i, coef
|
||||
|
||||
self.ruffle.append(dict(coeff=last_coef, sec=[last_start, len(ruffle)]))
|
||||
|
||||
else:
|
||||
self.ruffle = [dict(coeff=ruffle, sec=[0, len(self.edges)])]
|
||||
|
||||
def projecting_edges(self, on_oriented=False) -> EdgeSequence:
|
||||
"""Return edges shape that should be used when projecting interface
|
||||
onto another panel
|
||||
NOTE: reflects current state of the edge object. Call this function
|
||||
again if egdes change (e.g. their direction)
|
||||
# FIXME projection only works w.r.t. the line connecting the first and
|
||||
# the last vertex of the edge sequence -> use with cation
|
||||
"""
|
||||
# Per edge set ruffle application
|
||||
projected = self.edges.copy() if not on_oriented else self.oriented_edges()
|
||||
for r in self.ruffle:
|
||||
if not close_enough(r['coeff'], 1, 1e-3):
|
||||
if r['sec'][1] >= len(projected) or not projected[r['sec'][1] - 1:r['sec'][1] + 1].isChained():
|
||||
projected[r['sec'][0]:r['sec'][1]].extend(1 / r['coeff'])
|
||||
else:
|
||||
# Don't let extention to affect the rest of the sequence
|
||||
# Find the vert that separates the ruffle seqences
|
||||
prev_edge, next_edge = projected[r['sec'][1] - 1], projected[r['sec'][1]]
|
||||
|
||||
# Common vertex
|
||||
common_v = prev_edge.end if prev_edge.end is next_edge.end or prev_edge.end is next_edge.start else prev_edge.start
|
||||
|
||||
# Create copy and assign to next edge
|
||||
common_v_copy = common_v.copy()
|
||||
copy_to_end = False
|
||||
if common_v is next_edge.end:
|
||||
next_edge.end = common_v_copy
|
||||
copy_to_end = True
|
||||
else:
|
||||
next_edge.start = common_v_copy
|
||||
|
||||
# Extend the sequence
|
||||
projected[r['sec'][0]:r['sec'][1]].extend(1 / r['coeff'])
|
||||
|
||||
# move the next edges s.t. created vertex alignes with the original common vertex
|
||||
projected[r['sec'][1]:].translate_by([common_v[0] - common_v_copy[0], common_v[1] - common_v_copy[1]])
|
||||
|
||||
# re-chain the edges
|
||||
if copy_to_end:
|
||||
next_edge.end = common_v
|
||||
else:
|
||||
next_edge.start = common_v
|
||||
|
||||
return projected
|
||||
|
||||
# ANCHOR --- Projections for stitching -- to connect edges correctly and create correct ruffles
|
||||
def projecting_lengths(self):
|
||||
"""Desired projected length of the interface edges, as specified by ruffle coefficients"""
|
||||
projecting_lengths = []
|
||||
for r in self.ruffle:
|
||||
if not close_enough(r['coeff'], 1, 1e-3):
|
||||
projecting_lengths += [e.length() / r['coeff'] for e in self.edges[r['sec'][0]:r['sec'][1]]]
|
||||
else:
|
||||
projecting_lengths += [e.length() for e in self.edges[r['sec'][0]:r['sec'][1]]]
|
||||
|
||||
return np.array(projecting_lengths)
|
||||
|
||||
def projecting_fractions(self):
|
||||
"""Desired projected fractions of the interface edges, as specified by ruffle coefficients
|
||||
Fractions calculated w.r.t. to total projection lengths
|
||||
"""
|
||||
projecting_lengths = self.projecting_lengths()
|
||||
return projecting_lengths / projecting_lengths.sum()
|
||||
|
||||
def needsFlipping(self, i):
|
||||
""" Check if particular edge (i) should be re-oriented to follow the
|
||||
general direction of the interface
|
||||
* tol -- tolerance in distance differences that triggers flipping (in cm)
|
||||
|
||||
"""
|
||||
return self.edges_flipping[i]
|
||||
|
||||
# ANCHOR --- Info ----
|
||||
def oriented_edges(self):
|
||||
""" Orient the edges withing the interface sequence along the general
|
||||
direction of the interface
|
||||
|
||||
Creates a copy of the interface s.t. not to disturb the original
|
||||
edge objects
|
||||
"""
|
||||
# NOTE we cannot we do the same for the edge sub-sequences:
|
||||
# - midpoint of a sequence is less representative
|
||||
# - more likely to have weird relative 3D orientations
|
||||
# => heuristic won't work as well
|
||||
|
||||
oriented = self.edges.copy()
|
||||
|
||||
for i in range(len(self.edges)):
|
||||
if self.needsFlipping(i):
|
||||
oriented[i].reverse()
|
||||
oriented[i].flipped = True
|
||||
else:
|
||||
oriented[i].flipped = False
|
||||
return oriented
|
||||
|
||||
def verts_3d(self):
|
||||
"""Return 3D locations of all vertices that participate in the
|
||||
interface"""
|
||||
|
||||
verts_2d = []
|
||||
matching_panels = []
|
||||
for e, panel in zip(self.edges, self.panel):
|
||||
if all(e.start is not v for v in verts_2d): # Ensuring uniqueness
|
||||
verts_2d.append(e.start)
|
||||
matching_panels.append(panel)
|
||||
|
||||
if all(e.end is not v for v in verts_2d): # Ensuring uniqueness
|
||||
verts_2d.append(e.end)
|
||||
matching_panels.append(panel)
|
||||
|
||||
# To 3D
|
||||
verts_3d = []
|
||||
for v, panel in zip(verts_2d, matching_panels):
|
||||
verts_3d.append(panel.point_to_3D(v))
|
||||
|
||||
return np.asarray(verts_3d)
|
||||
|
||||
def bbox_3d(self):
|
||||
"""Return Interface bounding box"""
|
||||
|
||||
# NOTE: Vertex repetitions don't matter for bbox evaluation
|
||||
verts_3d = []
|
||||
for e, panel in zip(self.edges, self.panel):
|
||||
# Using curve linearization for more accurate approximation of bbox
|
||||
lin_edges = e.linearize()
|
||||
verts_2d = lin_edges.verts()
|
||||
verts_3d += [panel.point_to_3D(v) for v in verts_2d]
|
||||
verts_3d = np.asarray(verts_3d)
|
||||
|
||||
return verts_3d.min(axis=0), verts_3d.max(axis=0)
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.edges)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'Interface: {[p.name for p in self.panel]}: {str(self.oriented_edges())}'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
||||
def panel_names(self):
|
||||
return [p.name for p in self.panel]
|
||||
|
||||
# ANCHOR --- Interface Updates -----
|
||||
|
||||
def reverse(self, with_edge_dir_reverse=False):
|
||||
"""Reverse the order of edges in the interface
|
||||
(without updating the edge objects)
|
||||
|
||||
Reversal is useful for reordering interface edges for correct
|
||||
matching in the multi-stitches
|
||||
"""
|
||||
self.edges.edges.reverse()
|
||||
self.panel.reverse()
|
||||
self.edges_flipping.reverse()
|
||||
if with_edge_dir_reverse:
|
||||
self.edges_flipping = [not e for e in self.edges_flipping]
|
||||
|
||||
enum = len(self.edges)
|
||||
for r in self.ruffle:
|
||||
# Update ids
|
||||
r['sec'][0] = enum - r['sec'][0]
|
||||
r['sec'][1] = enum - r['sec'][1]
|
||||
# Swap
|
||||
r['sec'][0], r['sec'][1] = r['sec'][1], r['sec'][0]
|
||||
|
||||
return self
|
||||
|
||||
def flip_edges(self):
|
||||
"""Reverse the direction of edges in the interface
|
||||
(without updating the edge objects)
|
||||
|
||||
Reversal is useful for updating interface edges for correct
|
||||
matching in the multi-stitches
|
||||
"""
|
||||
self.edges_flipping = [not e for e in self.edges_flipping]
|
||||
|
||||
return self
|
||||
|
||||
# TODO Edge Sequence Function?
|
||||
def reorder(self, curr_edge_ids, projected_edge_ids):
|
||||
"""Change the order of edges from curr_edge_ids to projected_edge_ids
|
||||
in the interface
|
||||
|
||||
Note that the input should prescrive new ordering for all affected
|
||||
edges e.g. if moving 0 -> 1, specify the new location for 1 as well
|
||||
"""
|
||||
|
||||
for i, j in zip(curr_edge_ids, projected_edge_ids):
|
||||
for r in self.ruffle:
|
||||
if (i >= r['sec'][0] and i < r['sec'][1]
|
||||
and (j < r['sec'][0] or j >= r['sec'][1])):
|
||||
raise NotImplementedError(
|
||||
f'{self.__class__.__name__}::ERROR::reordering between panel-related sub-segments is not supported')
|
||||
|
||||
new_edges = EdgeSequence()
|
||||
new_panel_list = []
|
||||
new_flipping_info = []
|
||||
new_right_wrong = []
|
||||
for i in range(len(self.panel)):
|
||||
id = i if i not in curr_edge_ids else projected_edge_ids[curr_edge_ids.index(i)]
|
||||
# edges
|
||||
new_edges.append(self.edges[id])
|
||||
new_flipping_info.append(self.edges_flipping[id])
|
||||
# panels
|
||||
new_panel_list.append(self.panel[id])
|
||||
# connectivity indication
|
||||
new_right_wrong.append(self.right_wrong[id])
|
||||
|
||||
self.edges = new_edges
|
||||
self.panel = new_panel_list
|
||||
self.edges_flipping = new_flipping_info
|
||||
self.right_wrong = new_right_wrong
|
||||
|
||||
def substitute(self, orig, new_edges, new_panels):
|
||||
"""Update the interface edges with correct correction of panels
|
||||
* orig -- could be an edge object or the id of edges that need
|
||||
substitution
|
||||
* new_edges -- new edges to insert in place of orig
|
||||
* new_panels -- per-edge panel objects indicating where each of
|
||||
new_edges belong to
|
||||
|
||||
NOTE: the ruffle indicator for the new_edges is expected to be the
|
||||
same as for orig edge
|
||||
Specifying new indicators is not yet supported
|
||||
|
||||
"""
|
||||
if isinstance(orig, Edge):
|
||||
orig = self.edges.index(orig)
|
||||
if orig < 0:
|
||||
orig = len(self.edges) + orig
|
||||
self.edges.substitute(orig, new_edges)
|
||||
|
||||
# Update panels & flip info & right_wrong info
|
||||
self.panel.pop(orig)
|
||||
curr_edges_flip = self.edges_flipping.pop(orig)
|
||||
curr_right_wrong = self.right_wrong.pop(orig)
|
||||
if isinstance(new_panels, list) or isinstance(new_panels, tuple):
|
||||
for j in range(len(new_panels)):
|
||||
self.panel.insert(orig + j, new_panels[j])
|
||||
|
||||
# TODOLOW Note propagation of default values. Allow to specify them as func input!
|
||||
self.edges_flipping.insert(orig + j, curr_edges_flip)
|
||||
self.right_wrong.insert(orig + j, curr_right_wrong)
|
||||
else:
|
||||
self.panel.insert(orig, new_panels)
|
||||
self.edges_flipping.insert(orig, curr_edges_flip)
|
||||
self.right_wrong.insert(orig + j, curr_right_wrong)
|
||||
|
||||
# Propagate ruffle indicators
|
||||
ins_len = 1 if isinstance(new_edges, Edge) else len(new_edges)
|
||||
if ins_len > 1:
|
||||
for it in self.ruffle: # UPD ruffle indicators
|
||||
if it['sec'][0] > orig:
|
||||
it['sec'][0] += ins_len - 1
|
||||
if it['sec'][1] > orig:
|
||||
it['sec'][1] += ins_len - 1
|
||||
|
||||
return self
|
||||
|
||||
def set_right_wrong(self, right_wrong):
|
||||
"""Set all right_wrong values for the edges in current interface to the input value"""
|
||||
self.right_wrong = [right_wrong for _ in range(len(self.edges))]
|
||||
return self
|
||||
|
||||
# ANCHOR ----- Statics ----
|
||||
@staticmethod
|
||||
def from_multiple(*ints):
|
||||
"""Create interface from other interfaces:
|
||||
* Allows to use different panels in one interface
|
||||
* different ruffle values in one interface
|
||||
|
||||
# NOTE the relative order of edges is preserved from the
|
||||
original interfaces and the incoming interface sequence
|
||||
This order will then be used in the SrtitchingRule when
|
||||
determing connectivity between interfaces
|
||||
"""
|
||||
new_int = copy(ints[0]) # shallow copy -- don't create unnecessary objects
|
||||
new_int.edges = EdgeSequence()
|
||||
new_int.edges_flipping = []
|
||||
new_int.panel = []
|
||||
new_int.ruffle = []
|
||||
new_int.right_wrong = []
|
||||
|
||||
for elem in ints:
|
||||
shift = len(new_int.edges)
|
||||
new_int.ruffle += [copy(r) for r in elem.ruffle]
|
||||
for r in new_int.ruffle[-len(elem.ruffle):]:
|
||||
r.update(sec=[r['sec'][0] + shift, r['sec'][1] + shift])
|
||||
|
||||
new_int.edges.append(elem.edges)
|
||||
new_int.panel += elem.panel
|
||||
new_int.right_wrong += elem.right_wrong
|
||||
new_int.edges_flipping += elem.edges_flipping
|
||||
|
||||
return new_int
|
||||
|
||||
@staticmethod
|
||||
def _is_order_matching(panel_s, vert_s, panel_1, vert1, panel_2, vert2) -> bool:
|
||||
"""Check which of the two vertices vert1 (panel_1) or vert2 (panel_2)
|
||||
is closer to the vert_s
|
||||
from panel_s in 3D"""
|
||||
s_3d = panel_s.point_to_3D(vert_s)
|
||||
v1_3d = panel_1.point_to_3D(vert1)
|
||||
v2_3d = panel_2.point_to_3D(vert2)
|
||||
|
||||
return norm(v1_3d - s_3d) < norm(v2_3d - s_3d)
|
||||
Reference in New Issue
Block a user