234 lines
8.8 KiB
Python
234 lines
8.8 KiB
Python
import numpy as np
|
|
import pygarment as pyg
|
|
|
|
from assets.garment_programs.base_classes import StackableSkirtComponent
|
|
|
|
|
|
class CircleArcPanel(pyg.Panel):
|
|
"""One panel circle skirt"""
|
|
|
|
def __init__(self,
|
|
name,
|
|
top_rad, length, angle,
|
|
match_top_int_proportion : bool = None,
|
|
match_bottom_int_proportion=None
|
|
) -> None:
|
|
super().__init__(name)
|
|
|
|
halfarc = angle / 2
|
|
|
|
dist_w = 2 * top_rad * np.sin(halfarc)
|
|
dist_out = 2 * (top_rad + length) * np.sin(halfarc)
|
|
|
|
vert_len = length * np.cos(halfarc)
|
|
|
|
# top
|
|
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
|
|
[-dist_w/2, 0], [dist_w/2, 0],
|
|
radius=top_rad, large_arc=halfarc > np.pi / 2))
|
|
|
|
self.edges.append(pyg.Edge(
|
|
self.edges[-1].end, [dist_out / 2, -vert_len]))
|
|
|
|
# Bottom
|
|
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
|
|
self.edges[-1].end, [- dist_out / 2, -vert_len],
|
|
radius=top_rad + length,
|
|
large_arc=halfarc > np.pi / 2, right=False))
|
|
|
|
self.edges.close_loop()
|
|
|
|
# Interfaces
|
|
self.interfaces = {
|
|
'top': pyg.Interface(self, self.edges[0],
|
|
ruffle=self.edges[0].length() / match_top_int_proportion if match_top_int_proportion is not None else 1.
|
|
).reverse(True),
|
|
'bottom': pyg.Interface(self, self.edges[2],
|
|
ruffle=self.edges[2].length() / match_bottom_int_proportion if match_bottom_int_proportion is not None else 1.
|
|
),
|
|
'left': pyg.Interface(self, self.edges[1]),
|
|
'right': pyg.Interface(self, self.edges[3])
|
|
}
|
|
|
|
def length(self, *args):
|
|
return self.interfaces['right'].edges.length()
|
|
|
|
@staticmethod
|
|
def from_w_length_suns(name, length, top_width, sun_fraction, **kwargs):
|
|
arc = sun_fraction * 2 * np.pi
|
|
rad = top_width / arc
|
|
|
|
return CircleArcPanel(name, rad, length, arc, **kwargs)
|
|
|
|
@staticmethod
|
|
def from_all_length(name, length, top_width, bottom_width, **kwargs):
|
|
|
|
diff = bottom_width - top_width
|
|
arc = diff / length
|
|
rad = top_width / arc
|
|
|
|
return CircleArcPanel(name, rad, length, arc, **kwargs)
|
|
|
|
@staticmethod
|
|
def from_length_rad(name, length, top_width, rad, **kwargs):
|
|
|
|
arc = top_width / rad
|
|
|
|
return CircleArcPanel(name, rad, length, arc, **kwargs)
|
|
|
|
class AsymHalfCirclePanel(pyg.Panel):
|
|
"""Panel for a asymmetrci circle skirt"""
|
|
|
|
def __init__(self,
|
|
name,
|
|
top_rad, length_f, length_s,
|
|
match_top_int_proportion=None,
|
|
match_bottom_int_proportion=None
|
|
) -> None:
|
|
""" Half a shifted arc section
|
|
"""
|
|
super().__init__(name)
|
|
|
|
dist_w = 2 * top_rad
|
|
dist_out = 2 * (top_rad + length_s)
|
|
|
|
# top
|
|
self.edges.append(pyg.CircleEdgeFactory.from_points_radius(
|
|
[-dist_w/2, 0], [dist_w/2, 0],
|
|
radius=top_rad, large_arc=False))
|
|
|
|
self.edges.append(pyg.Edge(
|
|
self.edges[-1].end, [dist_out / 2, 0]))
|
|
|
|
# Bottom
|
|
self.edges.append(
|
|
pyg.CircleEdgeFactory.from_three_points(
|
|
self.edges[-1].end, [- dist_out / 2, 0],
|
|
point_on_arc=[0, -(top_rad + length_f)]
|
|
)
|
|
)
|
|
|
|
self.edges.close_loop()
|
|
|
|
# Interfaces
|
|
self.interfaces = {
|
|
'top': pyg.Interface(self, self.edges[0],
|
|
ruffle=self.edges[0].length() / match_top_int_proportion if match_top_int_proportion is not None else 1.
|
|
).reverse(True),
|
|
'bottom': pyg.Interface(self, self.edges[2],
|
|
ruffle=self.edges[2].length() / match_bottom_int_proportion if match_bottom_int_proportion is not None else 1.
|
|
),
|
|
'left': pyg.Interface(self, self.edges[1]),
|
|
'right': pyg.Interface(self, self.edges[3])
|
|
}
|
|
|
|
def length(self, *args):
|
|
return self.interfaces['right'].edges.length()
|
|
|
|
class SkirtCircle(StackableSkirtComponent):
|
|
"""Simple circle skirt"""
|
|
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, asymm=False, min_len=5, **kwargs) -> None:
|
|
super().__init__(body, design, tag)
|
|
|
|
design = design['flare-skirt']
|
|
suns = design['suns']['v']
|
|
self.rise = design['rise']['v'] if rise is None else rise
|
|
waist, hips_depth, _ = self.eval_rise(self.rise)
|
|
|
|
if length is None: # take from design parameters
|
|
length = hips_depth + design['length']['v'] * body['_leg_length']
|
|
|
|
# NOTE: with some combinations of rise and length parameters length may become too small/negative
|
|
# Hence putting a min positive value here
|
|
length = max(length, min_len)
|
|
|
|
# panels
|
|
if not asymm: # Typical symmetric skirt
|
|
self.front = CircleArcPanel.from_w_length_suns(
|
|
f'skirt_front_{tag}' if tag else 'skirt_front',
|
|
length, waist / 2, suns / 2,
|
|
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
|
|
).translate_by([0, body['_waist_level'], 15])
|
|
|
|
self.back = CircleArcPanel.from_w_length_suns(
|
|
f'skirt_back_{tag}' if tag else 'skirt_back',
|
|
length, waist / 2, suns / 2,
|
|
match_top_int_proportion=self.body['waist_back_width'],
|
|
).translate_by([0, body['_waist_level'], -15])
|
|
else:
|
|
# NOTE: Asymmetic front/back is only defined on full skirt (1 sun)
|
|
w_rad = waist / 2 / np.pi
|
|
f_length = design['asymm']['front_length']['v'] * length
|
|
tot_len = w_rad * 2 + length + f_length
|
|
del_r = tot_len / 2 - f_length - w_rad
|
|
s_length = np.sqrt((tot_len / 2)**2 - del_r**2) - w_rad
|
|
|
|
self.front = AsymHalfCirclePanel(
|
|
f'skirt_front_{tag}' if tag else 'skirt_front',
|
|
w_rad, f_length, s_length,
|
|
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
|
|
).translate_by([0, body['_waist_level'], 15])
|
|
|
|
self.back = AsymHalfCirclePanel(
|
|
f'skirt_back_{tag}' if tag else 'skirt_back',
|
|
w_rad, length, s_length,
|
|
match_top_int_proportion=self.body['waist_back_width'],
|
|
).translate_by([0, body['_waist_level'], -15])
|
|
|
|
# Add a cut
|
|
if design['cut']['add']['v'] and slit:
|
|
self.add_cut(
|
|
self.front if design['cut']['place']['v'] > 0 else self.back,
|
|
design, length)
|
|
|
|
# Stitches
|
|
self.stitching_rules = pyg.Stitches(
|
|
(self.front.interfaces['right'], self.back.interfaces['right']),
|
|
(self.front.interfaces['left'], self.back.interfaces['left'])
|
|
)
|
|
|
|
# Interfaces
|
|
self.interfaces = {
|
|
'top': pyg.Interface.from_multiple(self.front.interfaces['top'], self.back.interfaces['top']),
|
|
'bottom_f': self.front.interfaces['bottom'],
|
|
'bottom_b': self.back.interfaces['bottom'],
|
|
'bottom': pyg.Interface.from_multiple(self.front.interfaces['bottom'], self.back.interfaces['bottom'])
|
|
}
|
|
|
|
def add_cut(self, panel, design, sk_length):
|
|
"""Add a cut to the skirt"""
|
|
width, depth = design['cut']['width']['v'] * sk_length, design['cut']['depth']['v'] * sk_length
|
|
|
|
target_edge = panel.interfaces['bottom'].edges[0]
|
|
t_len = target_edge.length()
|
|
offset = abs(design['cut']['place']['v'] * t_len)
|
|
|
|
# Respect the placement boundaries
|
|
offset = max(offset, width / 2)
|
|
offset = min(offset, t_len - width / 2)
|
|
|
|
# NOTE: heuristic is specific for the panels that we use
|
|
right = target_edge.start[0] > target_edge.end[0]
|
|
|
|
# Make a cut
|
|
cut_shape = pyg.EdgeSeqFactory.dart_shape(width, depth=depth)
|
|
|
|
new_edges, _, interf_edges = pyg.ops.cut_into_edge(
|
|
cut_shape, target_edge,
|
|
offset=offset,
|
|
right=right
|
|
)
|
|
|
|
panel.edges.substitute(target_edge, new_edges)
|
|
panel.interfaces['bottom'].substitute(
|
|
target_edge, interf_edges,
|
|
[panel for _ in range(len(interf_edges))])
|
|
|
|
def length(self, *args):
|
|
return self.front.length()
|
|
|
|
|
|
class AsymmSkirtCircle(SkirtCircle):
|
|
"""Front/back asymmetric skirt"""
|
|
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, **kwargs):
|
|
super().__init__(body, design, tag, length, rise, slit, asymm=True) |