513 lines
20 KiB
Python
513 lines
20 KiB
Python
|
|
import numpy as np
|
||
|
|
from scipy.spatial.transform import Rotation as R
|
||
|
|
|
||
|
|
import pygarment as pyg
|
||
|
|
from assets.garment_programs.base_classes import StackableSkirtComponent
|
||
|
|
from assets.garment_programs.base_classes import BaseBottoms
|
||
|
|
from assets.garment_programs import shapes
|
||
|
|
|
||
|
|
|
||
|
|
class SkirtPanel(pyg.Panel):
|
||
|
|
"""One panel of a panel skirt with ruffles on the waist"""
|
||
|
|
|
||
|
|
def __init__(self,
|
||
|
|
name,
|
||
|
|
waist_length=70, length=70,
|
||
|
|
ruffles=1,
|
||
|
|
match_top_int_to=None,
|
||
|
|
bottom_cut=0,
|
||
|
|
flare=0
|
||
|
|
) -> None:
|
||
|
|
super().__init__(name)
|
||
|
|
|
||
|
|
base_width = waist_length
|
||
|
|
top_width = base_width * ruffles
|
||
|
|
low_width = top_width + 2*flare
|
||
|
|
x_shift_top = (low_width - top_width) / 2 # to account for flare at the bottom
|
||
|
|
|
||
|
|
# define edge loop
|
||
|
|
self.right = pyg.EdgeSeqFactory.side_with_cut(
|
||
|
|
[0, 0],
|
||
|
|
[x_shift_top, length],
|
||
|
|
start_cut=bottom_cut / length) if bottom_cut else pyg.EdgeSequence(
|
||
|
|
pyg.Edge([0, 0], [x_shift_top, length]))
|
||
|
|
self.waist = pyg.Edge(
|
||
|
|
self.right[-1].end, [x_shift_top + top_width, length])
|
||
|
|
self.left = pyg.EdgeSeqFactory.side_with_cut(
|
||
|
|
self.waist.end, [low_width, 0],
|
||
|
|
end_cut=bottom_cut / length) if bottom_cut else pyg.EdgeSequence(
|
||
|
|
pyg.Edge(self.waist.end, [low_width, 0]))
|
||
|
|
self.bottom = pyg.Edge(self.left[-1].end, self.right[0].start)
|
||
|
|
|
||
|
|
# define interface
|
||
|
|
self.interfaces = {
|
||
|
|
'right': pyg.Interface(self, self.right[-1]),
|
||
|
|
'top': pyg.Interface(self, self.waist,
|
||
|
|
ruffle=self.waist.length() / match_top_int_to if match_top_int_to is not None else ruffles
|
||
|
|
).reverse(True),
|
||
|
|
'left': pyg.Interface(self, self.left[0]),
|
||
|
|
'bottom': pyg.Interface(self, self.bottom)
|
||
|
|
}
|
||
|
|
# Single sequence for correct assembly
|
||
|
|
self.edges = self.right
|
||
|
|
self.edges.append(self.waist) # on the waist
|
||
|
|
self.edges.append(self.left)
|
||
|
|
self.edges.append(self.bottom)
|
||
|
|
|
||
|
|
# default placement
|
||
|
|
self.top_center_pivot()
|
||
|
|
self.center_x() # Already know that this panel should be centered over Y
|
||
|
|
|
||
|
|
|
||
|
|
class ThinSkirtPanel(pyg.Panel):
|
||
|
|
"""One panel of a panel skirt"""
|
||
|
|
|
||
|
|
def __init__(self, name, top_width=10, bottom_width=20, length=70, b_curvature=0) -> None:
|
||
|
|
super().__init__(name)
|
||
|
|
|
||
|
|
# define edge loop
|
||
|
|
self.flare = (bottom_width - top_width) / 2
|
||
|
|
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||
|
|
[0, 0], [self.flare, length], [self.flare + top_width, length],
|
||
|
|
[self.flare * 2 + top_width, 0])
|
||
|
|
|
||
|
|
if pyg.utils.close_enough(b_curvature, 0):
|
||
|
|
self.edges.close_loop()
|
||
|
|
else:
|
||
|
|
self.edges.append(
|
||
|
|
pyg.CircleEdgeFactory.from_three_points(
|
||
|
|
self.edges[-1].end,
|
||
|
|
self.edges[0].start,
|
||
|
|
[0.5, b_curvature],
|
||
|
|
relative=True
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# w.r.t. top left point
|
||
|
|
self.set_pivot(self.edges[0].end)
|
||
|
|
|
||
|
|
self.interfaces = {
|
||
|
|
'right': pyg.Interface(self, self.edges[0]),
|
||
|
|
'top': pyg.Interface(self, self.edges[1]),
|
||
|
|
'left': pyg.Interface(self, self.edges[2]),
|
||
|
|
'bottom': pyg.Interface(self, self.edges[-1])
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class FittedSkirtPanel(pyg.Panel):
|
||
|
|
"""Fitted panel for a pencil skirt"""
|
||
|
|
def __init__(
|
||
|
|
self, name, body, design,
|
||
|
|
waist, hips, hips_depth, # TODOLOW Half measurement instead of a quarter
|
||
|
|
length,
|
||
|
|
hipline_ext=1,
|
||
|
|
dart_position=None, dart_frac=0.5, double_dart=False,
|
||
|
|
match_top_int_to=None,
|
||
|
|
slit=0, left_slit=0, right_slit=0,
|
||
|
|
side_cut=None, flip_side_cut=False
|
||
|
|
) -> None:
|
||
|
|
""" Fitted panel for a pencil skirt
|
||
|
|
|
||
|
|
Body/design values that differ between front and back panels are supplied as parameters,
|
||
|
|
the rest are taken from the body and design dictionaries
|
||
|
|
"""
|
||
|
|
super().__init__(name)
|
||
|
|
|
||
|
|
# Shared params
|
||
|
|
low_angle = design['low_angle']['v']
|
||
|
|
hip_side_incl = np.deg2rad(body['_hip_inclination'])
|
||
|
|
flare = design['flare']['v']
|
||
|
|
low_width = body['hips'] * (flare - 1) / 4 + hips # Distribute the difference equally
|
||
|
|
# between front and back
|
||
|
|
# adjust for a rise
|
||
|
|
adj_hips_depth = hips_depth * hipline_ext
|
||
|
|
dart_depth = hips_depth * dart_frac
|
||
|
|
dart_depth = max(dart_depth - (hips_depth - adj_hips_depth), 0)
|
||
|
|
|
||
|
|
# amount of extra fabric
|
||
|
|
w_diff = hips - waist # Assume its positive since waist is smaller then hips
|
||
|
|
# We distribute w_diff among the side angle and a dart
|
||
|
|
hw_shift = np.tan(hip_side_incl) * adj_hips_depth
|
||
|
|
# Small difference
|
||
|
|
if hw_shift > w_diff:
|
||
|
|
hw_shift = w_diff
|
||
|
|
|
||
|
|
# Adjust the bottom edge to the desired angle
|
||
|
|
angle_shift = np.tan(np.deg2rad(low_angle)) * low_width
|
||
|
|
|
||
|
|
# --- Edges definition ---
|
||
|
|
# Right
|
||
|
|
if pyg.utils.close_enough(flare, 1): # skip optimization
|
||
|
|
right_bottom = pyg.Edge(
|
||
|
|
[hips - low_width, angle_shift],
|
||
|
|
[0, length]
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
right_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
|
||
|
|
[hips - low_width, angle_shift],
|
||
|
|
[0, length],
|
||
|
|
target_tan1=np.array([0, 1]),
|
||
|
|
# initial guess places control point closer to the hips
|
||
|
|
initial_guess=[0.75, 0]
|
||
|
|
)
|
||
|
|
right_top = pyg.CurveEdgeFactory.curve_from_tangents(
|
||
|
|
right_bottom.end,
|
||
|
|
[hw_shift, length + adj_hips_depth],
|
||
|
|
target_tan0=np.array([0, 1]),
|
||
|
|
initial_guess=[0.5, 0]
|
||
|
|
)
|
||
|
|
right = pyg.EdgeSequence(right_bottom, right_top)
|
||
|
|
|
||
|
|
# top
|
||
|
|
top = pyg.Edge(right[-1].end, [hips * 2 - hw_shift, length + adj_hips_depth])
|
||
|
|
|
||
|
|
# left
|
||
|
|
left_top = pyg.CurveEdgeFactory.curve_from_tangents(
|
||
|
|
top.end,
|
||
|
|
[hips * 2, length],
|
||
|
|
target_tan1=np.array([0, -1]),
|
||
|
|
initial_guess=[0.5, 0]
|
||
|
|
)
|
||
|
|
if pyg.utils.close_enough(flare, 1): # skip optimization for straight skirt
|
||
|
|
left_bottom = pyg.Edge(
|
||
|
|
left_top.end,
|
||
|
|
[hips + low_width, -angle_shift],
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
left_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
|
||
|
|
left_top.end,
|
||
|
|
[hips + low_width, -angle_shift],
|
||
|
|
target_tan0=np.array([0, -1]),
|
||
|
|
# initial guess places control point closer to the hips
|
||
|
|
initial_guess=[0.25, 0]
|
||
|
|
)
|
||
|
|
left = pyg.EdgeSequence(left_top, left_bottom)
|
||
|
|
|
||
|
|
# fin
|
||
|
|
self.edges = pyg.EdgeSequence(right, top, left).close_loop()
|
||
|
|
bottom = self.edges[-1]
|
||
|
|
|
||
|
|
if slit: # add a slit
|
||
|
|
# Use long and thin disconnected dart for a cutout
|
||
|
|
new_edges, _, int_edges = pyg.ops.cut_into_edge(
|
||
|
|
pyg.EdgeSeqFactory.dart_shape(2, depth=slit * length), # a very thin cutout
|
||
|
|
bottom,
|
||
|
|
offset=bottom.length() / 2,
|
||
|
|
right=True)
|
||
|
|
|
||
|
|
self.edges.substitute(bottom, new_edges)
|
||
|
|
bottom = int_edges
|
||
|
|
|
||
|
|
if left_slit:
|
||
|
|
frac = left_slit
|
||
|
|
new_left_bottom = left_bottom.subdivide_len([1 - frac, frac])
|
||
|
|
left.substitute(left_bottom, new_left_bottom[0])
|
||
|
|
self.edges.substitute(left_bottom, new_left_bottom)
|
||
|
|
left_bottom = new_left_bottom[0]
|
||
|
|
|
||
|
|
if right_slit:
|
||
|
|
frac = right_slit
|
||
|
|
new_rbottom = right_bottom.subdivide_len([frac, 1 - frac])
|
||
|
|
right.substitute(right_bottom, new_rbottom[1])
|
||
|
|
self.edges.substitute(right_bottom, new_rbottom)
|
||
|
|
right_bottom = new_rbottom[1]
|
||
|
|
|
||
|
|
if side_cut is not None:
|
||
|
|
try:
|
||
|
|
# Add a stylistic cutout to the skirt
|
||
|
|
new_edges, _, int_edges = pyg.ops.cut_into_edge(
|
||
|
|
side_cut, left_bottom,
|
||
|
|
offset=left_bottom.length() / 2,
|
||
|
|
right=True, flip_target=flip_side_cut)
|
||
|
|
except:
|
||
|
|
# Skip adding the cut if it doesn't fit (e.g. because of the slit)
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
self.edges.substitute(left_bottom, new_edges)
|
||
|
|
left.substitute(left_bottom, new_edges)
|
||
|
|
|
||
|
|
# Default placement
|
||
|
|
self.top_center_pivot()
|
||
|
|
self.translation = [-hips / 2, 5, 0]
|
||
|
|
|
||
|
|
# Out interfaces (easier to define before adding a dart)
|
||
|
|
# Adding ruffle factor on the hip line extention (used in back panel)
|
||
|
|
self.interfaces = {
|
||
|
|
'bottom': pyg.Interface(self, bottom),
|
||
|
|
'right': pyg.Interface(self, right, [1] * (len(right) - 1) + [hipline_ext]),
|
||
|
|
'left': pyg.Interface(self, left, [hipline_ext] + [1] * (len(left) - 1)),
|
||
|
|
}
|
||
|
|
self.interfaces['left'].edges_flipping[0] = True
|
||
|
|
self.interfaces['right'].edges_flipping[-1] = True
|
||
|
|
|
||
|
|
# Add top darts
|
||
|
|
if w_diff > hw_shift:
|
||
|
|
dart_width = w_diff - hw_shift
|
||
|
|
top_edges, int_edges = self.add_darts(top, dart_width, dart_depth, dart_position, double_dart=double_dart)
|
||
|
|
|
||
|
|
self.interfaces['top'] = pyg.Interface(
|
||
|
|
self, int_edges,
|
||
|
|
ruffle=int_edges.length() / match_top_int_to if match_top_int_to is not None else 1.
|
||
|
|
)
|
||
|
|
self.edges.substitute(top, top_edges)
|
||
|
|
else:
|
||
|
|
self.interfaces['top'] = pyg.Interface(
|
||
|
|
self, top,
|
||
|
|
ruffle=top.length() / match_top_int_to if match_top_int_to is not None else 1.
|
||
|
|
)
|
||
|
|
|
||
|
|
def add_darts(self, top, dart_width, dart_depth, dart_position, double_dart=False):
|
||
|
|
top_edge_len = top.length()
|
||
|
|
if double_dart:
|
||
|
|
# TODOLOW Avoid hardcoding for matching with the top?
|
||
|
|
dist = dart_position * 0.5 # Dist between darts -> dist between centers
|
||
|
|
offsets_mid = [
|
||
|
|
- (dart_position + dist / 2 + dart_width / 2) - dart_width / 4,
|
||
|
|
- (dart_position - dist / 2) - dart_width / 4,
|
||
|
|
dart_position - dist / 2 + dart_width / 4,
|
||
|
|
dart_position + dist / 2 + dart_width / 2 + dart_width / 4,
|
||
|
|
]
|
||
|
|
|
||
|
|
# dart_shape = pyp.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
|
||
|
|
dart_shape_full = pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth)
|
||
|
|
dart_shape_small = pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth * 0.9)
|
||
|
|
darts = [
|
||
|
|
dart_shape_small,
|
||
|
|
dart_shape_full,
|
||
|
|
dart_shape_full,
|
||
|
|
dart_shape_small,
|
||
|
|
]
|
||
|
|
else:
|
||
|
|
offsets_mid = [
|
||
|
|
- dart_position - dart_width / 2,
|
||
|
|
dart_position + dart_width / 2,
|
||
|
|
]
|
||
|
|
|
||
|
|
dart_shape = pyg.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
|
||
|
|
darts = [
|
||
|
|
dart_shape,
|
||
|
|
dart_shape,
|
||
|
|
]
|
||
|
|
top_edges, int_edges = pyg.EdgeSequence(top), pyg.EdgeSequence(top)
|
||
|
|
|
||
|
|
for off, dart in zip(offsets_mid, darts):
|
||
|
|
left_edge_len = top_edges[-1].length()
|
||
|
|
top_edges, int_edges = self.add_dart(
|
||
|
|
dart,
|
||
|
|
top_edges[-1],
|
||
|
|
offset=(left_edge_len - top_edge_len / 2) + off,
|
||
|
|
edge_seq=top_edges,
|
||
|
|
int_edge_seq=int_edges
|
||
|
|
)
|
||
|
|
|
||
|
|
return top_edges, int_edges
|
||
|
|
|
||
|
|
|
||
|
|
# Full garments - Components
|
||
|
|
class PencilSkirt(StackableSkirtComponent):
|
||
|
|
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, **kwargs) -> None:
|
||
|
|
super().__init__(body, design, tag)
|
||
|
|
|
||
|
|
design = design['pencil-skirt']
|
||
|
|
self.design = design # Make accessible from outside
|
||
|
|
|
||
|
|
# condition
|
||
|
|
if design['style_side_cut']['v'] is not None:
|
||
|
|
depth = 0.7 * (body['hips'] / 4 - body['bust_points'] / 2)
|
||
|
|
shape_class = getattr(shapes, design['style_side_cut']['v'])
|
||
|
|
style_shape_l, style_shape_r = shape_class(
|
||
|
|
width=depth * 1.5,
|
||
|
|
depth=depth, n_rays=6, d_rays=depth*0.2,
|
||
|
|
filename=design['style_side_file']['v'] if 'style_side_file' in design else None
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
style_shape_l, style_shape_r = None, None
|
||
|
|
|
||
|
|
# Force from arguments if given
|
||
|
|
self.rise = design['rise']['v'] if rise is None else rise
|
||
|
|
waist, hips_depth, back_waist = self.eval_rise(self.rise)
|
||
|
|
if length is None:
|
||
|
|
length = design['length']['v'] * body['_leg_length'] # Depends on leg length
|
||
|
|
else:
|
||
|
|
length = length - hips_depth
|
||
|
|
|
||
|
|
self.front = FittedSkirtPanel(
|
||
|
|
'skirt_front',
|
||
|
|
body,
|
||
|
|
design,
|
||
|
|
(waist - back_waist) / 2,
|
||
|
|
(body['hips'] - body['hip_back_width']) / 2,
|
||
|
|
hips_depth=hips_depth,
|
||
|
|
length=length,
|
||
|
|
dart_position=body['bust_points'] / 2,
|
||
|
|
dart_frac=0.8, # Diff for front and back
|
||
|
|
match_top_int_to=(body['waist'] - body['waist_back_width']),
|
||
|
|
slit=design['front_slit']['v'] if slit else 0,
|
||
|
|
left_slit=design['left_slit']['v'] if slit else 0,
|
||
|
|
right_slit=design['right_slit']['v'] if slit else 0,
|
||
|
|
side_cut=style_shape_l
|
||
|
|
).translate_to([0, body['_waist_level'], 25])
|
||
|
|
|
||
|
|
self.back = FittedSkirtPanel(
|
||
|
|
'skirt_back',
|
||
|
|
body,
|
||
|
|
design,
|
||
|
|
back_waist / 2,
|
||
|
|
body['hip_back_width'] / 2,
|
||
|
|
length=length,
|
||
|
|
hips_depth=hips_depth,
|
||
|
|
hipline_ext=1.05,
|
||
|
|
dart_position=body['bum_points'] / 2,
|
||
|
|
dart_frac=0.85,
|
||
|
|
double_dart=True,
|
||
|
|
match_top_int_to=body['waist_back_width'],
|
||
|
|
slit=design['back_slit']['v'] if slit else 0,
|
||
|
|
left_slit=design['left_slit']['v'] if slit else 0,
|
||
|
|
right_slit=design['right_slit']['v'] if slit else 0,
|
||
|
|
side_cut=style_shape_r,
|
||
|
|
flip_side_cut=False,
|
||
|
|
).translate_to([0, body['_waist_level'], -20])
|
||
|
|
|
||
|
|
self.stitching_rules = pyg.Stitches(
|
||
|
|
(self.front.interfaces['right'], self.back.interfaces['right']),
|
||
|
|
(self.front.interfaces['left'], self.back.interfaces['left'])
|
||
|
|
)
|
||
|
|
|
||
|
|
# Reusing interfaces of sub-panels as interfaces of this component
|
||
|
|
self.interfaces = {
|
||
|
|
'top_f': self.front.interfaces['top'],
|
||
|
|
'top_b': self.back.interfaces['top'],
|
||
|
|
'top': pyg.Interface.from_multiple(
|
||
|
|
self.front.interfaces['top'].flip_edges(),
|
||
|
|
self.back.interfaces['top'].reverse(with_edge_dir_reverse=True)
|
||
|
|
),
|
||
|
|
'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 length(self):
|
||
|
|
return self.front.length()
|
||
|
|
|
||
|
|
class Skirt2(StackableSkirtComponent):
|
||
|
|
"""Simple 2 panel skirt"""
|
||
|
|
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, top_ruffles=True, min_len=5) -> None:
|
||
|
|
super().__init__(body, design, tag)
|
||
|
|
|
||
|
|
design = design['skirt']
|
||
|
|
|
||
|
|
self.rise = design['rise']['v'] if rise is None else rise
|
||
|
|
waist, hip_line, back_waist = self.eval_rise(self.rise)
|
||
|
|
|
||
|
|
# Force from arguments if given
|
||
|
|
if length is None:
|
||
|
|
length = hip_line + design['length']['v'] * body['_leg_length'] # Depends on 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)
|
||
|
|
|
||
|
|
self.front = SkirtPanel(
|
||
|
|
f'skirt_front_{tag}' if tag else 'skirt_front',
|
||
|
|
waist_length=waist - back_waist,
|
||
|
|
length=length,
|
||
|
|
ruffles=design['ruffle']['v'] if top_ruffles else 1, # Only if on waistband
|
||
|
|
flare=design['flare']['v'],
|
||
|
|
bottom_cut=design['bottom_cut']['v'] * design['length']['v'] if slit else 0,
|
||
|
|
match_top_int_to=(body['waist'] - body['waist_back_width'])
|
||
|
|
).translate_to([0, body['_waist_level'], 25])
|
||
|
|
self.back = SkirtPanel(
|
||
|
|
f'skirt_back_{tag}' if tag else 'skirt_back',
|
||
|
|
waist_length=back_waist,
|
||
|
|
length=length,
|
||
|
|
ruffles=design['ruffle']['v'] if top_ruffles else 1, # Only if on waistband
|
||
|
|
flare=design['flare']['v'],
|
||
|
|
bottom_cut=design['bottom_cut']['v'] * design['length']['v'] if slit else 0,
|
||
|
|
match_top_int_to=body['waist_back_width']
|
||
|
|
).translate_to([0, body['_waist_level'], -20])
|
||
|
|
|
||
|
|
self.stitching_rules = pyg.Stitches(
|
||
|
|
(self.front.interfaces['right'], self.back.interfaces['right']),
|
||
|
|
(self.front.interfaces['left'], self.back.interfaces['left'])
|
||
|
|
)
|
||
|
|
|
||
|
|
# Reusing interfaces of sub-panels as interfaces of this component
|
||
|
|
self.interfaces = {
|
||
|
|
'top_f': self.front.interfaces['top'],
|
||
|
|
'top_b': self.back.interfaces['top'],
|
||
|
|
'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 length(self):
|
||
|
|
return self.front.length()
|
||
|
|
|
||
|
|
|
||
|
|
class SkirtManyPanels(BaseBottoms):
|
||
|
|
"""Round Skirt with many panels"""
|
||
|
|
|
||
|
|
def __init__(self, body, design, tag='', rise=None, min_len=5) -> None:
|
||
|
|
tag_extra = str(design['flare-skirt']['skirt-many-panels']['n_panels']['v'])
|
||
|
|
tag = f'{tag}_{tag_extra}' if tag else tag_extra
|
||
|
|
super().__init__(body, design, tag=tag, rise=rise)
|
||
|
|
|
||
|
|
design = design['flare-skirt']
|
||
|
|
self.rise = design['rise']['v'] if rise is None else rise
|
||
|
|
waist, hip_line, _ = self.eval_rise(self.rise)
|
||
|
|
n_panels = design['skirt-many-panels']['n_panels']['v']
|
||
|
|
|
||
|
|
# Length is dependent on length of legs
|
||
|
|
length = hip_line + 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)
|
||
|
|
|
||
|
|
flare_coeff_pi = 1 + design['suns']['v'] * length * 2 * np.pi / waist
|
||
|
|
|
||
|
|
self.front = ThinSkirtPanel('front',
|
||
|
|
panel_w := waist / n_panels,
|
||
|
|
bottom_width=panel_w * flare_coeff_pi,
|
||
|
|
length=length,
|
||
|
|
b_curvature=design['skirt-many-panels']['panel_curve']['v'])
|
||
|
|
|
||
|
|
# Move far enough s.t. the widest part of the panels fit on the circle
|
||
|
|
dist = self.front.interfaces['bottom'].edges.length() / (2 * np.tan(np.pi / n_panels))
|
||
|
|
|
||
|
|
self.front.translate_to([-dist, body['_waist_level'], 0])
|
||
|
|
# Align orientation with a body
|
||
|
|
self.front.rotate_by(R.from_euler('XYZ', [0, -90, 0], degrees=True))
|
||
|
|
self.front.rotate_align([-dist, 0, panel_w / 2])
|
||
|
|
|
||
|
|
# Upd interface orientation
|
||
|
|
self.front.interfaces['top'].reverse(True)
|
||
|
|
|
||
|
|
# Create new panels
|
||
|
|
self.subs = pyg.ops.distribute_Y(self.front, n_panels, name_tag='skirt_panel')
|
||
|
|
|
||
|
|
# Stitch new components
|
||
|
|
for i in range(1, n_panels):
|
||
|
|
self.stitching_rules.append((self.subs[i - 1].interfaces['left'],
|
||
|
|
self.subs[i].interfaces['right']))
|
||
|
|
|
||
|
|
self.stitching_rules.append((self.subs[-1].interfaces['left'],
|
||
|
|
self.subs[0].interfaces['right']))
|
||
|
|
|
||
|
|
# Define the interface
|
||
|
|
self.interfaces = {
|
||
|
|
'top': pyg.Interface.from_multiple(*[sub.interfaces['top']
|
||
|
|
for sub in self.subs])
|
||
|
|
}
|
||
|
|
|
||
|
|
def length(self):
|
||
|
|
return self.front.length()
|
||
|
|
|