init_code
This commit is contained in:
262
assets/garment_programs/bands.py
Normal file
262
assets/garment_programs/bands.py
Normal file
@@ -0,0 +1,262 @@
|
||||
import pygarment as pyg
|
||||
from assets.garment_programs.circle_skirt import CircleArcPanel
|
||||
from assets.garment_programs import skirt_paneled
|
||||
from assets.garment_programs.base_classes import BaseBand
|
||||
|
||||
class StraightBandPanel(pyg.Panel):
|
||||
"""One panel for a panel skirt"""
|
||||
|
||||
def __init__(self, name, width, depth, match_int_proportion=None) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
# define edge loop
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0], [0, depth], [width, depth], [width, 0], loop=True)
|
||||
|
||||
# define interface
|
||||
self.interfaces = {
|
||||
'right': pyg.Interface(self, self.edges[0]),
|
||||
'top': pyg.Interface(self,
|
||||
self.edges[1],
|
||||
ruffle=width / match_int_proportion if match_int_proportion is not None else 1.
|
||||
).reverse(True),
|
||||
'left': pyg.Interface(self, self.edges[2]),
|
||||
'bottom': pyg.Interface(self,
|
||||
self.edges[3],
|
||||
ruffle=width / match_int_proportion if match_int_proportion is not None else 1.
|
||||
)
|
||||
}
|
||||
|
||||
# Default translation
|
||||
self.top_center_pivot()
|
||||
self.center_x()
|
||||
|
||||
|
||||
class StraightWB(BaseBand):
|
||||
"""Simple 2 panel waistband"""
|
||||
def __init__(self, body, design, rise=1.) -> None:
|
||||
"""Simple 2 panel waistband
|
||||
|
||||
* rise -- the rise value of the bottoms that the WB is attached to
|
||||
Adapts the shape of the waistband to sit tight on top
|
||||
of the given rise level (top measurement). If 1. or anything less than waistband width,
|
||||
the rise is ignored and the StraightWB is created to sit well on the waist
|
||||
|
||||
"""
|
||||
super().__init__(body, design, rise=rise)
|
||||
|
||||
# Measurements
|
||||
self.waist = design['waistband']['waist']['v'] * body['waist']
|
||||
self.waist_back_frac = body['waist_back_width'] / body['waist']
|
||||
self.hips = body['hips'] * design['waistband']['waist']['v']
|
||||
self.hips_back_frac = body['hip_back_width'] / body['hips']
|
||||
|
||||
# Params
|
||||
self.width = design['waistband']['width']['v']
|
||||
self.rise = rise
|
||||
# Check correct values
|
||||
if self.rise + self.width > 1:
|
||||
self.rise = 1 - self.width
|
||||
|
||||
self.top_width = pyg.utils.lin_interpolation(
|
||||
self.hips, self.waist, self.rise + self.width)
|
||||
self.top_back_fraction = pyg.utils.lin_interpolation(
|
||||
self.hips_back_frac, self.waist_back_frac, self.rise + self.width)
|
||||
|
||||
self.width = self.width * body['hips_line']
|
||||
|
||||
self.define_panels()
|
||||
|
||||
self.front.translate_by([0, body['_waist_level'] + 10, 20])
|
||||
self.back.translate_by([0, body['_waist_level'] + 10, -20])
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.front.interfaces['right'], self.back.interfaces['right']),
|
||||
(self.front.interfaces['left'], self.back.interfaces['left'])
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'bottom_f': self.front.interfaces['bottom'],
|
||||
'bottom_b': self.back.interfaces['bottom'],
|
||||
|
||||
'top_f': self.front.interfaces['top'],
|
||||
'top_b': self.back.interfaces['top'],
|
||||
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['bottom'],
|
||||
self.back.interfaces['bottom']),
|
||||
'top': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['top'],
|
||||
self.back.interfaces['top']),
|
||||
}
|
||||
|
||||
def define_panels(self):
|
||||
back_width = self.top_width * self.top_back_fraction
|
||||
|
||||
self.front = StraightBandPanel(
|
||||
'wb_front',
|
||||
self.top_width - back_width,
|
||||
self.width,
|
||||
match_int_proportion=self.body['waist'] - self.body['waist_back_width']
|
||||
)
|
||||
|
||||
self.back = StraightBandPanel(
|
||||
'wb_back',
|
||||
back_width,
|
||||
self.width,
|
||||
match_int_proportion=self.body['waist_back_width']
|
||||
)
|
||||
|
||||
|
||||
class FittedWB(StraightWB):
|
||||
"""Also known as Yoke: a waistband that ~follows the body curvature,
|
||||
and hence sits tight
|
||||
Made out of two circular arc panels
|
||||
"""
|
||||
def __init__(self, body, design, rise=1.) -> None:
|
||||
"""A waistband that ~follows the body curvature, and hence sits tight
|
||||
|
||||
* rise -- the rise value of the bottoms that the WB is attached to
|
||||
Adapts the shape of the waistband to sit tight on top
|
||||
of the given rise level. If 1. or anything less than waistband width,
|
||||
the rise is ignored and the FittedWB is created to sit well on the waist
|
||||
"""
|
||||
self.bottom_width = None
|
||||
self.bottom_back_fraction = None
|
||||
super().__init__(body, design, rise)
|
||||
|
||||
def define_panels(self):
|
||||
self.bottom_width = pyg.utils.lin_interpolation(
|
||||
self.hips, self.waist, self.rise)
|
||||
self.bottom_back_fraction = pyg.utils.lin_interpolation(
|
||||
self.hips_back_frac, self.waist_back_frac, self.rise)
|
||||
|
||||
self.front = CircleArcPanel.from_all_length(
|
||||
'wb_front',
|
||||
self.width,
|
||||
self.top_width * (1 - self.top_back_fraction),
|
||||
self.bottom_width * (1 - self.bottom_back_fraction),
|
||||
match_top_int_proportion=self.body['waist'] - self.body['waist_back_width'],
|
||||
match_bottom_int_proportion=self.body['waist'] - self.body['waist_back_width']
|
||||
)
|
||||
|
||||
self.back = CircleArcPanel.from_all_length(
|
||||
'wb_back',
|
||||
self.width,
|
||||
self.top_width * self.top_back_fraction,
|
||||
self.bottom_width * self.bottom_back_fraction,
|
||||
match_top_int_proportion=self.body['waist_back_width'],
|
||||
match_bottom_int_proportion=self.body['waist_back_width']
|
||||
)
|
||||
|
||||
|
||||
class CuffBand(BaseBand):
|
||||
""" Cuff class for sleeves or pants
|
||||
band-like piece of fabric with optional "skirt"
|
||||
"""
|
||||
def __init__(self, tag, design, length=None) -> None:
|
||||
super().__init__(body=None, design=design, tag=tag)
|
||||
|
||||
self.design = design['cuff']
|
||||
|
||||
if length is None:
|
||||
length = self.design['cuff_len']['v']
|
||||
|
||||
self.front = StraightBandPanel(
|
||||
f'{tag}_cuff_f', self.design['b_width']['v'] / 2, length)
|
||||
self.front.translate_by([0, 0, 15])
|
||||
self.back = StraightBandPanel(
|
||||
f'{tag}_cuff_b', self.design['b_width']['v'] / 2, length)
|
||||
self.back.translate_by([0, 0, -15])
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.front.interfaces['right'], self.back.interfaces['right']),
|
||||
(self.front.interfaces['left'], self.back.interfaces['left'])
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['bottom'],
|
||||
self.back.interfaces['bottom']),
|
||||
'top_front': self.front.interfaces['top'],
|
||||
'top_back': self.back.interfaces['top'],
|
||||
'top': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['top'],
|
||||
self.back.interfaces['top']),
|
||||
}
|
||||
|
||||
|
||||
class CuffSkirt(BaseBand):
|
||||
"""A skirt-like flared cuff """
|
||||
|
||||
def __init__(self, tag, design, length=None) -> None:
|
||||
super().__init__(body=None, design=design, tag=tag)
|
||||
|
||||
self.design = design['cuff']
|
||||
width = self.design['b_width']['v']
|
||||
flare_diff = (self.design['skirt_flare']['v'] - 1) * width / 2
|
||||
|
||||
if length is None:
|
||||
length = self.design['cuff_len']['v']
|
||||
|
||||
self.front = skirt_paneled.SkirtPanel(
|
||||
f'{tag}_cuff_skirt_f', ruffles=self.design['skirt_ruffle']['v'],
|
||||
waist_length=width / 2, length=length,
|
||||
flare=flare_diff)
|
||||
self.front.translate_by([0, 0, 15])
|
||||
self.back = skirt_paneled.SkirtPanel(
|
||||
f'{tag}_cuff_skirt_b', ruffles=self.design['skirt_ruffle']['v'],
|
||||
waist_length=width / 2, length=length,
|
||||
flare=flare_diff)
|
||||
self.back.translate_by([0, 0, -15])
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.front.interfaces['right'], self.back.interfaces['right']),
|
||||
(self.front.interfaces['left'], self.back.interfaces['left'])
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'top': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['top'], self.back.interfaces['top']),
|
||||
'top_front': self.front.interfaces['top'],
|
||||
'top_back': self.back.interfaces['top'],
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['bottom'],
|
||||
self.back.interfaces['bottom']),
|
||||
}
|
||||
|
||||
|
||||
class CuffBandSkirt(pyg.Component):
|
||||
""" Cuff class for sleeves or pants
|
||||
band-like piece of fabric with optional "skirt"
|
||||
"""
|
||||
def __init__(self, tag, design) -> None:
|
||||
super().__init__(self.__class__.__name__)
|
||||
|
||||
self.cuff = CuffBand(
|
||||
tag,
|
||||
design,
|
||||
length=design['cuff']['cuff_len']['v'] * (1 - design['cuff']['skirt_fraction']['v'])
|
||||
)
|
||||
self.skirt = CuffSkirt(
|
||||
tag,
|
||||
design,
|
||||
length=design['cuff']['cuff_len']['v'] * design['cuff']['skirt_fraction']['v']
|
||||
)
|
||||
|
||||
# Align
|
||||
self.skirt.place_below(self.cuff)
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.cuff.interfaces['bottom'], self.skirt.interfaces['top']),
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'top': self.cuff.interfaces['top'],
|
||||
'top_front': self.cuff.interfaces['top_front'],
|
||||
'top_back': self.cuff.interfaces['top_back'],
|
||||
'bottom': self.skirt.interfaces['bottom']
|
||||
}
|
||||
|
||||
def length(self):
|
||||
return self.cuff.length() + self.skirt.length()
|
||||
122
assets/garment_programs/base_classes.py
Normal file
122
assets/garment_programs/base_classes.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import pygarment as pyg
|
||||
|
||||
class BaseBodicePanel(pyg.Panel):
|
||||
"""Base class for bodice panels that defines expected interfaces and common functions"""
|
||||
def __init__(self, name, body, design) -> None:
|
||||
super().__init__(name)
|
||||
self.body = body
|
||||
self.design = design
|
||||
|
||||
self.interfaces = {
|
||||
'outside': object(),
|
||||
'inside': object(),
|
||||
'shoulder': object(),
|
||||
'bottom': object(),
|
||||
|
||||
'shoulder_corner': object(),
|
||||
'collar_corner': object(),
|
||||
}
|
||||
|
||||
def get_width(self, level):
|
||||
"""Return the panel width at a given level (excluding darts)
|
||||
* Level is counted from the top of the panel
|
||||
|
||||
NOTE: for fitted bodice, the request is only valid for values between 0 and bust_level
|
||||
"""
|
||||
# NOTE: this evaluation assumes that the top edge width is the same as bodice shoulder width
|
||||
side_edge = self.interfaces['outside'].edges[-1]
|
||||
|
||||
x = side_edge.end[0] - side_edge.start[0]
|
||||
y = side_edge.end[1] - side_edge.start[1]
|
||||
|
||||
# If the orientation of the edge is "looking down"
|
||||
# instead of "looking up" as calculations above expect, flip the values
|
||||
if y < 0:
|
||||
x, y = -x, -y
|
||||
|
||||
return (level * x / y) + self.body['shoulder_w'] / 2
|
||||
|
||||
|
||||
class BaseBottoms(pyg.Component):
|
||||
"""A base class for all the bottom components.
|
||||
Defines common elements:
|
||||
* List of interfaces
|
||||
* Presence of the rise value
|
||||
"""
|
||||
def __init__(self, body, design, tag='', rise=None) -> None:
|
||||
"""Base bottoms initialization
|
||||
"""
|
||||
super().__init__(
|
||||
self.__class__.__name__ if not tag else f'{self.__class__.__name__}_{tag}')
|
||||
|
||||
self.body = body
|
||||
self.design = design
|
||||
self.rise = rise
|
||||
|
||||
# Set of interfaces that need to be implemented
|
||||
self.interfaces = {
|
||||
'top': object()
|
||||
}
|
||||
|
||||
def get_rise(self):
|
||||
"""Return a rise value for a given component"""
|
||||
return self.rise
|
||||
|
||||
def eval_rise(self, rise):
|
||||
"""Evaluate updated hip and waist-related measurements,
|
||||
corresponding to the provided rise value
|
||||
"""
|
||||
waist, hips = self.body['waist'], self.body['hips']
|
||||
hips_level = self.body['hips_line']
|
||||
self.adj_hips_depth = rise * hips_level
|
||||
self.adj_waist = pyg.utils.lin_interpolation(hips, waist, rise)
|
||||
|
||||
self_adj_back_waist = pyg.utils.lin_interpolation(
|
||||
self.body['hip_back_width'], self.body['waist_back_width'], rise)
|
||||
|
||||
return self.adj_waist, self.adj_hips_depth, self_adj_back_waist
|
||||
|
||||
class StackableSkirtComponent(BaseBottoms):
|
||||
"""
|
||||
Abstract definition of a skirt that can be stacked with other stackable skirts
|
||||
(connecting bottom to another StackableSkirtComponent())
|
||||
"""
|
||||
|
||||
def __init__(self, body, design, tag='', length=None, rise=None, slit=True, top_ruffles=True) -> None:
|
||||
"""Skirt initialization
|
||||
|
||||
Extra parameters (length, sleets, top_ruffles)
|
||||
can be used to overwrite parameters in design dictionary
|
||||
"""
|
||||
super().__init__(body, design, tag, rise=rise)
|
||||
|
||||
pass
|
||||
|
||||
# Set of interfaces that need to be implemented
|
||||
self.interfaces = {
|
||||
'top': object(),
|
||||
'bottom_f': object(),
|
||||
'bottom_b': object(),
|
||||
'bottom': object()
|
||||
}
|
||||
|
||||
|
||||
class BaseBand(pyg.Component):
|
||||
def __init__(self, body, design, tag='', rise=None) -> None:
|
||||
"""Base band initialization
|
||||
"""
|
||||
super().__init__(
|
||||
self.__class__.__name__ if not tag else f'{self.__class__.__name__}_{tag}')
|
||||
self.body = body
|
||||
self.design = design
|
||||
self.rise = rise
|
||||
|
||||
# Set of interfaces that need to be implemented
|
||||
self.interfaces = {
|
||||
'top': object(),
|
||||
'bottom': object()
|
||||
}
|
||||
|
||||
def length(self):
|
||||
"""Base length == Length of a first panel"""
|
||||
return self._get_subcomponents()[0].length()
|
||||
674
assets/garment_programs/bodice.py
Normal file
674
assets/garment_programs/bodice.py
Normal file
@@ -0,0 +1,674 @@
|
||||
from copy import deepcopy
|
||||
import numpy as np
|
||||
|
||||
import pygarment as pyg
|
||||
|
||||
from assets.garment_programs.base_classes import BaseBodicePanel
|
||||
from assets.garment_programs import sleeves
|
||||
from assets.garment_programs import collars
|
||||
from assets.garment_programs import tee
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
class BodiceFrontHalf(BaseBodicePanel):
|
||||
def __init__(self, name, body, design) -> None:
|
||||
super().__init__(name, body, design)
|
||||
|
||||
m_bust = body['bust']
|
||||
m_waist = body['waist']
|
||||
|
||||
# sizes
|
||||
bust_point = body['bust_points'] / 2
|
||||
front_frac = (body['bust'] - body['back_width']) / 2 / body['bust']
|
||||
|
||||
self.width = front_frac * m_bust
|
||||
waist = (m_waist - body['waist_back_width']) / 2
|
||||
sh_tan = np.tan(np.deg2rad(body['_shoulder_incl']))
|
||||
shoulder_incl = sh_tan * self.width
|
||||
bottom_d_width = (self.width - waist) * 2 / 3
|
||||
|
||||
adjustment = sh_tan * (self.width - body['shoulder_w'] / 2)
|
||||
max_len = body['waist_over_bust_line'] - adjustment
|
||||
|
||||
# side length is adjusted due to shoulder inclination
|
||||
# for the correct sleeve fitting
|
||||
fb_diff = (front_frac - (0.5 - front_frac)) * body['bust']
|
||||
back_adjustment = sh_tan * (body['back_width'] / 2 - body['shoulder_w'] / 2)
|
||||
side_len = body['waist_line'] - back_adjustment - sh_tan * fb_diff
|
||||
|
||||
self.edges.append(pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0],
|
||||
[-self.width, 0],
|
||||
[-self.width, max_len],
|
||||
[0, max_len + shoulder_incl]
|
||||
))
|
||||
self.edges.close_loop()
|
||||
|
||||
# Side dart
|
||||
bust_line = body['waist_line'] - body['_bust_line']
|
||||
side_d_depth = 0.75 * (self.width - bust_point) # NOTE: calculated value
|
||||
side_d_width = max_len - side_len
|
||||
s_edge, side_interface = self.add_dart(
|
||||
pyg.EdgeSeqFactory.dart_shape(side_d_width, side_d_depth),
|
||||
self.edges[1],
|
||||
offset=bust_line + side_d_width / 2)
|
||||
self.edges.substitute(1, s_edge)
|
||||
|
||||
# Take some fabric from the top to match the shoulder width
|
||||
s_edge[-1].end[0] += (x_upd:=self.width - body['shoulder_w'] / 2)
|
||||
s_edge[-1].end[1] += (sh_tan * x_upd)
|
||||
|
||||
# Bottom dart
|
||||
b_edge, b_interface = self.add_dart(
|
||||
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, 0.9 * bust_line),
|
||||
self.edges[0],
|
||||
offset=bust_point + bottom_d_width / 2
|
||||
)
|
||||
self.edges.substitute(0, b_edge)
|
||||
# Take some fabric from side in the bottom (!: after side dart insertion)
|
||||
b_edge[-1].end[0] = - (waist + bottom_d_width)
|
||||
|
||||
# Interfaces
|
||||
self.interfaces = {
|
||||
'outside': pyg.Interface(self, side_interface), # side_interface, # pyp.Interface(self, [side_interface]), #, self.edges[-3]]),
|
||||
'inside': pyg.Interface(self, self.edges[-1]),
|
||||
'shoulder': pyg.Interface(self, self.edges[-2]),
|
||||
'bottom': pyg.Interface(self, b_interface),
|
||||
|
||||
# Reference to the corner for sleeve and collar projections
|
||||
'shoulder_corner': pyg.Interface(
|
||||
self, [self.edges[-3], self.edges[-2]]),
|
||||
'collar_corner': pyg.Interface(
|
||||
self, [self.edges[-2], self.edges[-1]])
|
||||
}
|
||||
|
||||
# default placement
|
||||
self.translate_by([0, body['height'] - body['head_l'] - max_len - shoulder_incl, 0])
|
||||
|
||||
|
||||
class BodiceBackHalf(BaseBodicePanel):
|
||||
"""Panel for the back of basic fitted bodice block"""
|
||||
|
||||
def __init__(self, name, body, design) -> None:
|
||||
super().__init__(name, body, design)
|
||||
|
||||
# Overall measurements
|
||||
self.width = body['back_width'] / 2
|
||||
waist = body['waist_back_width'] / 2
|
||||
# NOTE: no inclination on the side, since there is not much to begin with
|
||||
waist_width = self.width if waist < self.width else waist
|
||||
shoulder_incl = (sh_tan:=np.tan(np.deg2rad(body['_shoulder_incl']))) * self.width
|
||||
|
||||
# Adjust to make sure length is measured from the shoulder
|
||||
# and not the de-fact side of the garment
|
||||
back_adjustment = sh_tan * (self.width - body['shoulder_w'] / 2)
|
||||
length = body['waist_line'] - back_adjustment
|
||||
|
||||
# Base edge loop
|
||||
edge_0 = pyg.CurveEdgeFactory.curve_from_tangents(
|
||||
start=[0, shoulder_incl / 4], # back a little shorter
|
||||
end=[-waist_width, 0],
|
||||
target_tan0=[-1, 0]
|
||||
)
|
||||
self.edges.append(edge_0)
|
||||
self.edges.append(pyg.EdgeSeqFactory.from_verts(
|
||||
edge_0.end,
|
||||
[-self.width, body['waist_line'] - body['_bust_line']], # from the bottom
|
||||
[-self.width, length],
|
||||
[0, length + shoulder_incl], # Add some fabric for the neck (inclination of shoulders)
|
||||
))
|
||||
self.edges.close_loop()
|
||||
|
||||
# Take some fabric from the top to match the shoulder width
|
||||
self.interfaces = {
|
||||
'outside': pyg.Interface(
|
||||
self, [self.edges[1], self.edges[2]]),
|
||||
'inside': pyg.Interface(self, self.edges[-1]),
|
||||
'shoulder': pyg.Interface(self, self.edges[-2]),
|
||||
'bottom': pyg.Interface(self, self.edges[0]),
|
||||
# Reference to the corners for sleeve and collar projections
|
||||
'shoulder_corner': pyg.Interface(
|
||||
self, pyg.EdgeSequence(self.edges[-3], self.edges[-2])),
|
||||
'collar_corner': pyg.Interface(
|
||||
self, pyg.EdgeSequence(self.edges[-2], self.edges[-1]))
|
||||
}
|
||||
|
||||
# Bottom dart as cutout -- for straight line
|
||||
if waist < self.get_width(self.edges[2].end[1] - self.edges[2].start[1]):
|
||||
w_diff = waist_width - waist
|
||||
side_adj = 0 if w_diff < 4 else w_diff / 6 # NOTE: don't take from sides if the difference is too small
|
||||
bottom_d_width = w_diff - side_adj
|
||||
bottom_d_width /= 2 # double darts
|
||||
bottom_d_depth = 1. * (length - body['_bust_line']) # calculated value
|
||||
bottom_d_position = body['bum_points'] / 2
|
||||
|
||||
# TODOLOW Avoid hardcoding for matching with the bottoms?
|
||||
dist = bottom_d_position * 0.5 # Dist between darts -> dist between centers
|
||||
b_edge, b_interface = self.add_dart(
|
||||
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, 0.9 * bottom_d_depth),
|
||||
self.edges[0],
|
||||
offset=bottom_d_position + dist / 2 + bottom_d_width + bottom_d_width / 2,
|
||||
)
|
||||
b_edge, b_interface = self.add_dart(
|
||||
pyg.EdgeSeqFactory.dart_shape(bottom_d_width, bottom_d_depth),
|
||||
b_edge[0],
|
||||
offset=bottom_d_position - dist / 2 + bottom_d_width / 2,
|
||||
edge_seq=b_edge,
|
||||
int_edge_seq=b_interface,
|
||||
)
|
||||
|
||||
self.edges.substitute(0, b_edge)
|
||||
self.interfaces['bottom'] = pyg.Interface(self, b_interface)
|
||||
|
||||
# Remove fabric from the sides if the diff is big enough
|
||||
b_edge[-1].end[0] += side_adj
|
||||
|
||||
# default placement
|
||||
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
|
||||
|
||||
def get_width(self, level):
|
||||
return self.width
|
||||
|
||||
class BodiceHalf(pyg.Component):
|
||||
"""Definition of a half of an upper garment with sleeves and collars"""
|
||||
|
||||
def __init__(self, name, body, design, fitted=True) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
design = deepcopy(design) # Recalculate freely!
|
||||
|
||||
# Torso
|
||||
if fitted:
|
||||
self.ftorso = BodiceFrontHalf(
|
||||
f'{name}_ftorso', body, design).translate_by([0, 0, 30])
|
||||
self.btorso = BodiceBackHalf(
|
||||
f'{name}_btorso', body, design).translate_by([0, 0, -25])
|
||||
else:
|
||||
self.ftorso = tee.TorsoFrontHalfPanel(
|
||||
f'{name}_ftorso', body, design).translate_by([0, 0, 30])
|
||||
self.btorso = tee.TorsoBackHalfPanel(
|
||||
f'{name}_btorso', body, design).translate_by([0, 0, -25])
|
||||
|
||||
# Interfaces
|
||||
self.interfaces.update({
|
||||
'f_bottom': self.ftorso.interfaces['bottom'],
|
||||
'b_bottom': self.btorso.interfaces['bottom'],
|
||||
'front_in': self.ftorso.interfaces['inside'],
|
||||
'back_in': self.btorso.interfaces['inside']
|
||||
})
|
||||
|
||||
# Sleeves/collar cuts
|
||||
self.sleeve = None
|
||||
self.collar_comp = None
|
||||
self.eval_dep_params(body, design)
|
||||
|
||||
if design['fitted_shirt']['strapless']['v'] and fitted: # NOTE: Strapless design only for fitted tops
|
||||
self.make_strapless(body, design)
|
||||
else:
|
||||
# Sleeves and collars
|
||||
self.add_sleeves(name, body, design)
|
||||
self.add_collars(name, body, design)
|
||||
self.stitching_rules.append((
|
||||
self.ftorso.interfaces['shoulder'],
|
||||
self.btorso.interfaces['shoulder']
|
||||
)) # tops
|
||||
|
||||
# Main connectivity
|
||||
self.stitching_rules.append((
|
||||
self.ftorso.interfaces['outside'], self.btorso.interfaces['outside'])) # sides
|
||||
|
||||
def eval_dep_params(self, body, design):
|
||||
|
||||
# Sleeves
|
||||
# NOTE assuming the vertical side is the first argument
|
||||
max_cwidth = self.ftorso.interfaces['shoulder_corner'].edges[0].length() - 1 # cm
|
||||
min_cwidth = body['_armscye_depth']
|
||||
v = design['sleeve']['connecting_width']['v']
|
||||
design['sleeve']['connecting_width']['v'] = min(min_cwidth + min_cwidth * v, max_cwidth)
|
||||
|
||||
# Collars
|
||||
# NOTE: Assuming the first is the top edge
|
||||
# Width
|
||||
# TODOLOW What if sleeve inclination is variable?
|
||||
# NOTE: Back panel is more narrow, so using it
|
||||
max_w = body['_base_sleeve_balance'] - 2 # 1 cm from default sleeve
|
||||
min_w = body['neck_w']
|
||||
|
||||
if design['collar']['width']['v'] >= 0:
|
||||
design['collar']['width']['v'] = width = pyg.utils.lin_interpolation(min_w, max_w, design['collar']['width']['v'])
|
||||
else:
|
||||
design['collar']['width']['v'] = width = pyg.utils.lin_interpolation(0, min_w, 1 + design['collar']['width']['v'])
|
||||
|
||||
# Depth
|
||||
# Collar depth is given w.r.t. length.
|
||||
# adjust for the shoulder inclination
|
||||
tg = np.tan(np.deg2rad(body['_shoulder_incl']))
|
||||
f_depth_adj = tg * (self.ftorso.get_width(0) - width / 2)
|
||||
b_depth_adj = tg * (self.btorso.get_width(0) - width / 2)
|
||||
|
||||
max_f_len = self.ftorso.interfaces['collar_corner'].edges[1].length() - tg * self.ftorso.get_width(0) - 1 # cm
|
||||
max_b_len = self.btorso.interfaces['collar_corner'].edges[1].length() - tg * self.btorso.get_width(0) - 1 # cm
|
||||
|
||||
design['collar']['f_strapless_depth'] = {}
|
||||
design['collar']['f_strapless_depth']['v'] = min(
|
||||
design['collar']['fc_depth']['v'] * body['_bust_line'],
|
||||
max_f_len)
|
||||
design['collar']['fc_depth']['v'] = design['collar']['f_strapless_depth']['v'] + f_depth_adj
|
||||
|
||||
|
||||
design['collar']['b_strapless_depth'] = {}
|
||||
design['collar']['b_strapless_depth']['v'] = min(
|
||||
design['collar']['bc_depth']['v'] * body['_bust_line'],
|
||||
max_b_len)
|
||||
design['collar']['bc_depth']['v'] = design['collar']['b_strapless_depth']['v'] + b_depth_adj
|
||||
|
||||
def add_sleeves(self, name, body, design):
|
||||
self.sleeve = sleeves.Sleeve(
|
||||
name, body, design,
|
||||
front_w=self.ftorso.get_width,
|
||||
back_w=self.btorso.get_width
|
||||
)
|
||||
# self.sleeve = sleeves.OnePieceSleeve(
|
||||
# name, body, design,
|
||||
# front_w=self.ftorso.get_width,
|
||||
# back_w=self.btorso.get_width
|
||||
# )
|
||||
|
||||
_, f_sleeve_int = pyg.ops.cut_corner(
|
||||
self.sleeve.interfaces['in_front_shape'].edges,
|
||||
self.ftorso.interfaces['shoulder_corner'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
_, b_sleeve_int = pyg.ops.cut_corner(
|
||||
self.sleeve.interfaces['in_back_shape'].edges,
|
||||
self.btorso.interfaces['shoulder_corner'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
if not design['sleeve']['sleeveless']['v']:
|
||||
# Ordering
|
||||
bodice_sleeve_int = pyg.Interface.from_multiple(
|
||||
f_sleeve_int.reverse(with_edge_dir_reverse=True),
|
||||
b_sleeve_int.reverse(),
|
||||
)
|
||||
self.stitching_rules.append((
|
||||
self.sleeve.interfaces['in'],
|
||||
bodice_sleeve_int
|
||||
))
|
||||
|
||||
# NOTE: This is a heuristic tuned for arm poses 30 deg-60 deg
|
||||
# used in the dataset
|
||||
# FIXME Needs a better general solution
|
||||
gap = -1 - body['arm_pose_angle'] / 10
|
||||
self.sleeve.place_by_interface(
|
||||
self.sleeve.interfaces['in'],
|
||||
bodice_sleeve_int,
|
||||
gap=gap,
|
||||
alignment='top',
|
||||
)
|
||||
|
||||
# Add edge labels
|
||||
f_sleeve_int.edges.propagate_label(f'{self.name}_armhole')
|
||||
b_sleeve_int.edges.propagate_label(f'{self.name}_armhole')
|
||||
|
||||
def add_collars(self, name, body, design):
|
||||
# Front
|
||||
collar_type = getattr(
|
||||
collars,
|
||||
str(design['collar']['component']['style']['v']),
|
||||
collars.NoPanelsCollar
|
||||
)
|
||||
|
||||
self.collar_comp = collar_type(name, body, design)
|
||||
|
||||
# Project shape
|
||||
_, fc_interface = pyg.ops.cut_corner(
|
||||
self.collar_comp.interfaces['front_proj'].edges,
|
||||
self.ftorso.interfaces['collar_corner'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
_, bc_interface = pyg.ops.cut_corner(
|
||||
self.collar_comp.interfaces['back_proj'].edges,
|
||||
self.btorso.interfaces['collar_corner'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
# Add stitches/interfaces
|
||||
if 'bottom' in self.collar_comp.interfaces:
|
||||
self.stitching_rules.append((
|
||||
pyg.Interface.from_multiple(fc_interface, bc_interface),
|
||||
self.collar_comp.interfaces['bottom']
|
||||
))
|
||||
|
||||
# Upd front interfaces accordingly
|
||||
if 'front' in self.collar_comp.interfaces:
|
||||
self.interfaces['front_collar'] = self.collar_comp.interfaces['front']
|
||||
self.interfaces['front_in'] = pyg.Interface.from_multiple(
|
||||
self.ftorso.interfaces['inside'], self.interfaces['front_collar']
|
||||
)
|
||||
if 'back' in self.collar_comp.interfaces:
|
||||
self.interfaces['back_collar'] = self.collar_comp.interfaces['back']
|
||||
self.interfaces['back_in'] = pyg.Interface.from_multiple(
|
||||
self.btorso.interfaces['inside'], self.interfaces['back_collar']
|
||||
)
|
||||
|
||||
# Add edge labels
|
||||
fc_interface.edges.propagate_label(f'{self.name}_collar')
|
||||
bc_interface.edges.propagate_label(f'{self.name}_collar')
|
||||
|
||||
def make_strapless(self, body, design):
|
||||
|
||||
out_depth = design['sleeve']['connecting_width']['v']
|
||||
f_in_depth = design['collar']['f_strapless_depth']['v']
|
||||
b_in_depth = design['collar']['b_strapless_depth']['v']
|
||||
|
||||
# Shoulder adjustment for the back
|
||||
# TODOLOW Shoulder adj evaluation should be a function
|
||||
shoulder_angle = np.deg2rad(body['_shoulder_incl'])
|
||||
sleeve_balance = body['_base_sleeve_balance'] / 2
|
||||
back_w = self.btorso.get_width(0)
|
||||
shoulder_adj = np.tan(shoulder_angle) * (back_w - sleeve_balance)
|
||||
out_depth -= shoulder_adj
|
||||
|
||||
# Upd back
|
||||
self._adjust_top_level(self.btorso, out_depth, b_in_depth)
|
||||
|
||||
# Front depth determined by ~compensating for lenght difference
|
||||
len_back = self.btorso.interfaces['outside'].edges.length()
|
||||
len_front = self.ftorso.interfaces['outside'].edges.length()
|
||||
self._adjust_top_level(self.ftorso, out_depth, f_in_depth, target_remove=(len_front - len_back))
|
||||
|
||||
# Placement
|
||||
# NOTE: The commented line places the top a bit higher, increasing the chanced of correct drape
|
||||
# Surcumvented by attachment constraint, so removed for nicer alignment in asymmetric garments
|
||||
# self.translate_by([0, out_depth - body['_armscye_depth'] * 0.75, 0]) # adjust for better localisation
|
||||
|
||||
# Add a label
|
||||
self.ftorso.interfaces['shoulder'].edges.propagate_label('strapless_top')
|
||||
self.btorso.interfaces['shoulder'].edges.propagate_label('strapless_top')
|
||||
|
||||
|
||||
def _adjust_top_level(self, panel, out_level, in_level, target_remove=None):
|
||||
"""Crops the top of the bodice front/back panel for strapless style
|
||||
|
||||
* out_length_diff -- if set, determined the length difference that should be compensates
|
||||
after cutting the depth
|
||||
"""
|
||||
# TODOLOW Should this be the panel's function?
|
||||
|
||||
panel_top = panel.interfaces['shoulder'].edges[0]
|
||||
min_y = min(panel_top.start[1], panel_top.end[1])
|
||||
|
||||
# Order vertices
|
||||
ins, out = panel_top.start, panel_top.end
|
||||
if panel_top.start[1] < panel_top.end[1]:
|
||||
ins, out = out, ins
|
||||
|
||||
# Inside is a simple vertical line and can be adjusted by chaning Y value
|
||||
ins[1] = min_y - in_level
|
||||
|
||||
# Outside could be inclined, so needs further calculations
|
||||
outside_edge = panel.interfaces['outside'].edges[-1]
|
||||
bot, top = outside_edge.start, outside_edge.end
|
||||
if bot is out:
|
||||
bot, top = top, bot
|
||||
|
||||
if target_remove is not None:
|
||||
# Adjust the depth to remove this length exactly
|
||||
angle_sin = abs(out[1] - bot[1]) / outside_edge.length()
|
||||
curr_remove = out_level / angle_sin
|
||||
length_diff = target_remove - curr_remove
|
||||
adjustment = length_diff * angle_sin
|
||||
out_level += adjustment
|
||||
|
||||
angle_cotan = abs(out[0] - bot[0]) / abs(out[1] - bot[1])
|
||||
out[0] -= out_level * angle_cotan
|
||||
out[1] = min_y - out_level
|
||||
|
||||
|
||||
def length(self):
|
||||
return self.btorso.length()
|
||||
|
||||
class Shirt(pyg.Component):
|
||||
"""Panel for the front of upper garments with darts to properly fit it to
|
||||
the shape"""
|
||||
|
||||
def __init__(self, body, design, fitted=False) -> None:
|
||||
name_with_params = f"{self.__class__.__name__}"
|
||||
super().__init__(name_with_params)
|
||||
|
||||
design = self.eval_dep_params(design)
|
||||
|
||||
self.right = BodiceHalf(f'right', body, design, fitted=fitted)
|
||||
self.left = BodiceHalf(
|
||||
f'left', body,
|
||||
design['left'] if design['left']['enable_asym']['v'] else design,
|
||||
fitted=fitted).mirror()
|
||||
|
||||
self.stitching_rules.append((self.right.interfaces['front_in'],
|
||||
self.left.interfaces['front_in']))
|
||||
self.stitching_rules.append((self.right.interfaces['back_in'],
|
||||
self.left.interfaces['back_in']))
|
||||
|
||||
# Adjust interface ordering for correct connectivity
|
||||
self.interfaces = { # Bottom connection
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.right.interfaces['f_bottom'].reverse(),
|
||||
self.left.interfaces['f_bottom'],
|
||||
self.left.interfaces['b_bottom'].reverse(),
|
||||
self.right.interfaces['b_bottom'],)
|
||||
}
|
||||
|
||||
def eval_dep_params(self, design):
|
||||
# NOTE: Support for full collars with partially strapless top
|
||||
# or combination of paneled collar styles
|
||||
# requres further development
|
||||
# TODOLOW enable this one to work
|
||||
if design['left']['enable_asym']['v']:
|
||||
# Force no collars since they are not compatible with each other
|
||||
design = deepcopy(design)
|
||||
design['collar']['component']['style']['v'] = None
|
||||
design['left']['collar']['component'] = dict(style=dict(v=None))
|
||||
|
||||
# Left-right design compatibility
|
||||
design['left']['shirt'].update(length={})
|
||||
design['left']['shirt']['length']['v'] = design['shirt']['length']['v']
|
||||
|
||||
design['left']['collar'].update(fc_depth={}, bc_depth={})
|
||||
design['left']['collar']['fc_depth']['v'] = design['collar']['fc_depth']['v']
|
||||
design['left']['collar']['bc_depth']['v'] = design['collar']['bc_depth']['v']
|
||||
|
||||
return design
|
||||
|
||||
def length(self):
|
||||
return self.right.length()
|
||||
|
||||
class FittedShirt(Shirt):
|
||||
"""Creates fitted shirt
|
||||
|
||||
NOTE: Separate class is used for selection convenience.
|
||||
Even though most of the processing is the same
|
||||
(hence implemented with the same components except for panels),
|
||||
design parametrization differs significantly.
|
||||
With that, we decided to separate the top level names
|
||||
"""
|
||||
def __init__(self, body, design) -> None:
|
||||
super().__init__(body, design, fitted=True)
|
||||
|
||||
|
||||
class PrincessSeamShirt(pyg.Component):
|
||||
"""Class representing a shirt with princess seams."""
|
||||
|
||||
def __init__(self, body, design):
|
||||
# Initialize the base Component class
|
||||
super().__init__("PrincessSeamShirt")
|
||||
|
||||
# Evaluate design parameters
|
||||
self.eval_dep_params(design)
|
||||
|
||||
# Create front and back panels
|
||||
self.front_panel = self.create_front_panel(body, design)
|
||||
self.back_panel = self.create_back_panel(body, design)
|
||||
|
||||
# Add panels as components
|
||||
self.add_component(self.front_panel)
|
||||
self.add_component(self.back_panel)
|
||||
|
||||
# Define stitching rules
|
||||
self.stitching_rules = [
|
||||
# Side seams
|
||||
(self.front_panel.interfaces['side_seam'], self.back_panel.interfaces['side_seam']),
|
||||
# Shoulder seams
|
||||
(self.front_panel.interfaces['shoulder_seam'], self.back_panel.interfaces['shoulder_seam']),
|
||||
# Front princess seams
|
||||
(self.front_panel.interfaces['princess_seam_left'], self.front_panel.left_princess_panel.interfaces['princess_seam']),
|
||||
(self.front_panel.interfaces['princess_seam_right'], self.front_panel.right_princess_panel.interfaces['princess_seam']),
|
||||
# Back princess seams
|
||||
(self.back_panel.interfaces['princess_seam_left'], self.back_panel.left_princess_panel.interfaces['princess_seam']),
|
||||
(self.back_panel.interfaces['princess_seam_right'], self.back_panel.right_princess_panel.interfaces['princess_seam']),
|
||||
]
|
||||
|
||||
def eval_dep_params(self, design):
|
||||
# Placeholder for evaluating dependent design parameters
|
||||
pass
|
||||
|
||||
def create_front_panel(self, body, design):
|
||||
# Create the front panel
|
||||
front_panel = pyg.Panel("FrontPanel")
|
||||
|
||||
# Body measurements
|
||||
bust = body['bust']
|
||||
waist = body['waist']
|
||||
hips = body['hips']
|
||||
shoulder_width = body['shoulder_width']
|
||||
neck_width = body['neck_width']
|
||||
neck_depth = body['front_neck_depth']
|
||||
|
||||
# Calculate panel dimensions
|
||||
panel_width = bust / 2
|
||||
princess_seam_pos = bust / 8
|
||||
|
||||
# Define key points
|
||||
points = [
|
||||
[0, 0], # Bottom left
|
||||
[0, hips], # Hip point
|
||||
[0, waist], # Waist point
|
||||
[princess_seam_pos, bust], # Left bust point
|
||||
[shoulder_width / 2, bust + neck_depth], # Left shoulder
|
||||
[panel_width - shoulder_width / 2, bust + neck_depth], # Right shoulder
|
||||
[panel_width - princess_seam_pos, bust], # Right bust point
|
||||
[panel_width, waist], # Right waist point
|
||||
[panel_width, hips], # Right hip point
|
||||
[panel_width, 0], # Bottom right
|
||||
]
|
||||
|
||||
# Create edges
|
||||
edges = pyg.EdgeSequence()
|
||||
for i in range(len(points) - 1):
|
||||
edges.append(pyg.Edge(points[i], points[i + 1]))
|
||||
edges.append(pyg.Edge(points[-1], points[0])) # Close shape
|
||||
|
||||
# Assign edges to panel
|
||||
front_panel.edges = edges
|
||||
|
||||
# Define interfaces
|
||||
front_panel.interfaces = {
|
||||
'side_seam': pyg.Interface(front_panel, [edges[0], edges[8]]),
|
||||
'shoulder_seam': pyg.Interface(front_panel, [edges[4], edges[5]]),
|
||||
'princess_seam_left': pyg.Interface(front_panel, [edges[2]]),
|
||||
'princess_seam_right': pyg.Interface(front_panel, [edges[7]]),
|
||||
}
|
||||
|
||||
# Create princess panels
|
||||
front_panel.left_princess_panel = self.create_princess_panel(front_panel, edges[2], "FrontLeftPrincessPanel")
|
||||
front_panel.right_princess_panel = self.create_princess_panel(front_panel, edges[7], "FrontRightPrincessPanel")
|
||||
|
||||
return front_panel
|
||||
|
||||
def create_back_panel(self, body, design):
|
||||
# Create the back panel
|
||||
back_panel = pyg.Panel("BackPanel")
|
||||
|
||||
# Body measurements
|
||||
bust = body['bust']
|
||||
waist = body['waist']
|
||||
hips = body['hips']
|
||||
shoulder_width = body['shoulder_width']
|
||||
neck_width = body['neck_width']
|
||||
neck_depth = body['back_neck_depth']
|
||||
|
||||
# Calculate panel dimensions
|
||||
panel_width = bust / 2
|
||||
princess_seam_pos = bust / 8
|
||||
|
||||
# Define key points
|
||||
points = [
|
||||
[0, 0], # Bottom left
|
||||
[0, hips], # Hip point
|
||||
[0, waist], # Waist point
|
||||
[princess_seam_pos, bust], # Left bust point
|
||||
[shoulder_width / 2, bust + neck_depth], # Left shoulder
|
||||
[panel_width - shoulder_width / 2, bust + neck_depth], # Right shoulder
|
||||
[panel_width - princess_seam_pos, bust], # Right bust point
|
||||
[panel_width, waist], # Right waist point
|
||||
[panel_width, hips], # Right hip point
|
||||
[panel_width, 0], # Bottom right
|
||||
]
|
||||
|
||||
# Create edges
|
||||
edges = pyg.EdgeSequence()
|
||||
for i in range(len(points) - 1):
|
||||
edges.append(pyg.Edge(points[i], points[i + 1]))
|
||||
edges.append(pyg.Edge(points[-1], points[0])) # Close shape
|
||||
|
||||
# Assign edges to panel
|
||||
back_panel.edges = edges
|
||||
|
||||
# Define interfaces
|
||||
back_panel.interfaces = {
|
||||
'side_seam': pyg.Interface(back_panel, [edges[0], edges[8]]),
|
||||
'shoulder_seam': pyg.Interface(back_panel, [edges[4], edges[5]]),
|
||||
'princess_seam_left': pyg.Interface(back_panel, [edges[2]]),
|
||||
'princess_seam_right': pyg.Interface(back_panel, [edges[7]]),
|
||||
}
|
||||
|
||||
# Create princess panels
|
||||
back_panel.left_princess_panel = self.create_princess_panel(back_panel, edges[2], "BackLeftPrincessPanel")
|
||||
back_panel.right_princess_panel = self.create_princess_panel(back_panel, edges[7], "BackRightPrincessPanel")
|
||||
|
||||
return back_panel
|
||||
|
||||
def create_princess_panel(self, parent_panel, seam_edge, panel_name):
|
||||
# Create a princess panel along the seam
|
||||
princess_panel = pyg.Panel(panel_name)
|
||||
|
||||
# Seam edge points
|
||||
start_point = seam_edge.start
|
||||
end_point = seam_edge.end
|
||||
|
||||
# Panel offset
|
||||
offset = 5 # Width of the princess panel
|
||||
|
||||
# Calculate new points
|
||||
offset_start = [start_point[0] + offset, start_point[1]]
|
||||
offset_end = [end_point[0] + offset, end_point[1]]
|
||||
|
||||
# Create edges
|
||||
edges = pyg.EdgeSequence()
|
||||
edges.append(pyg.Edge(start_point, end_point)) # Seam edge
|
||||
edges.append(pyg.Edge(end_point, offset_end)) # Top edge
|
||||
edges.append(pyg.Edge(offset_end, offset_start)) # Outer edge
|
||||
edges.append(pyg.Edge(offset_start, start_point)) # Bottom edge
|
||||
|
||||
# Close shape
|
||||
edges.close_loop()
|
||||
|
||||
# Assign edges to panel
|
||||
princess_panel.edges = edges
|
||||
|
||||
# Define interfaces
|
||||
princess_panel.interfaces = {
|
||||
'princess_seam': pyg.Interface(princess_panel, [edges[0]]),
|
||||
'outer_seam': pyg.Interface(princess_panel, [edges[2]]),
|
||||
}
|
||||
|
||||
return princess_panel
|
||||
234
assets/garment_programs/circle_skirt.py
Normal file
234
assets/garment_programs/circle_skirt.py
Normal file
@@ -0,0 +1,234 @@
|
||||
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)
|
||||
370
assets/garment_programs/collars.py
Normal file
370
assets/garment_programs/collars.py
Normal file
@@ -0,0 +1,370 @@
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
import pygarment as pyg
|
||||
|
||||
from assets.garment_programs.bands import StraightBandPanel
|
||||
from assets.garment_programs.circle_skirt import CircleArcPanel
|
||||
|
||||
|
||||
# # ------ Collar shapes withough extra panels ------
|
||||
|
||||
def VNeckHalf(depth, width, **kwargs):
|
||||
"""Simple VNeck design"""
|
||||
|
||||
edges = pyg.EdgeSequence(pyg.Edge([0, 0], [width / 2, -depth]))
|
||||
return edges
|
||||
|
||||
def SquareNeckHalf(depth, width, **kwargs):
|
||||
"""Square design"""
|
||||
|
||||
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [0, -depth], [width / 2, -depth])
|
||||
return edges
|
||||
|
||||
def TrapezoidNeckHalf(depth, width, angle=90, verbose=True, **kwargs):
|
||||
"""Trapesoid neck design"""
|
||||
|
||||
# Special case when angle = 180 (sin = 0)
|
||||
if (pyg.utils.close_enough(angle, 180, tol=1)
|
||||
or pyg.utils.close_enough(angle, 0, tol=1)):
|
||||
# degrades into VNeck
|
||||
return VNeckHalf(depth, width)
|
||||
|
||||
rad_angle = np.deg2rad(angle)
|
||||
|
||||
bottom_x = -depth * np.cos(rad_angle) / np.sin(rad_angle)
|
||||
if bottom_x > width / 2: # Invalid angle/depth/width combination resulted in invalid shape
|
||||
if verbose:
|
||||
print('TrapezoidNeckHalf::WARNING::Parameters are invalid and create overlap: '
|
||||
f'{bottom_x} > {width / 2}. '
|
||||
'The collar is reverted to VNeck')
|
||||
|
||||
return VNeckHalf(depth, width)
|
||||
|
||||
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [bottom_x, -depth], [width / 2, -depth])
|
||||
return edges
|
||||
|
||||
def CurvyNeckHalf(depth, width, flip=False, **kwargs):
|
||||
"""Testing Curvy Collar design"""
|
||||
|
||||
sign = -1 if flip else 1
|
||||
edges = pyg.EdgeSequence(pyg.CurveEdge(
|
||||
[0, 0], [width / 2,-depth],
|
||||
[[0.4, sign * 0.3], [0.8, sign * -0.3]]))
|
||||
|
||||
return edges
|
||||
|
||||
def CircleArcNeckHalf(depth, width, angle=90, flip=False, **kwargs):
|
||||
"""Collar with a side represented by a circle arc"""
|
||||
# 1/4 of a circle
|
||||
edges = pyg.EdgeSequence(pyg.CircleEdgeFactory.from_points_angle(
|
||||
[0, 0], [width / 2,-depth], arc_angle=np.deg2rad(angle),
|
||||
right=(not flip)
|
||||
))
|
||||
|
||||
return edges
|
||||
|
||||
|
||||
def CircleNeckHalf(depth, width, **kwargs):
|
||||
"""Collar that forms a perfect circle arc when halfs are stitched"""
|
||||
|
||||
# Take a full desired arc and half it!
|
||||
circle = pyg.CircleEdgeFactory.from_three_points(
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width / 2, -depth])
|
||||
subdiv = circle.subdivide_len([0.5, 0.5])
|
||||
return pyg.EdgeSequence(subdiv[0])
|
||||
|
||||
def Bezier2NeckHalf(depth, width, flip=False, x=0.5, y=0.3, **kwargs):
|
||||
"""2d degree Bezier curve as neckline"""
|
||||
|
||||
sign = 1 if flip else -1
|
||||
edges = pyg.EdgeSequence(pyg.CurveEdge(
|
||||
[0, 0], [width / 2,-depth],
|
||||
[[x, sign*y]]))
|
||||
|
||||
return edges
|
||||
|
||||
# # ------ Collars with panels ------
|
||||
|
||||
class NoPanelsCollar(pyg.Component):
|
||||
"""Face collar class that only forms the projected shapes """
|
||||
|
||||
def __init__(self, name, body, design) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
# Front
|
||||
collar_type = globals()[design['collar']['f_collar']['v']]
|
||||
f_collar = collar_type(
|
||||
design['collar']['fc_depth']['v'],
|
||||
design['collar']['width']['v'],
|
||||
angle=design['collar']['fc_angle']['v'],
|
||||
flip=design['collar']['f_flip_curve']['v'],
|
||||
x=design['collar']['f_bezier_x']['v'],
|
||||
y=design['collar']['f_bezier_y']['v'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
# Back
|
||||
collar_type = globals()[design['collar']['b_collar']['v']]
|
||||
b_collar = collar_type(
|
||||
design['collar']['bc_depth']['v'],
|
||||
design['collar']['width']['v'],
|
||||
angle=design['collar']['bc_angle']['v'],
|
||||
flip=design['collar']['b_flip_curve']['v'],
|
||||
x=design['collar']['b_bezier_x']['v'],
|
||||
y=design['collar']['b_bezier_y']['v'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'front_proj': pyg.Interface(self, f_collar),
|
||||
'back_proj': pyg.Interface(self, b_collar)
|
||||
}
|
||||
|
||||
def length(self):
|
||||
return 0
|
||||
|
||||
|
||||
class Turtle(pyg.Component):
|
||||
|
||||
def __init__(self, tag, body, design) -> None:
|
||||
super().__init__(f'Turtle_{tag}')
|
||||
|
||||
depth = design['collar']['component']['depth']['v']
|
||||
|
||||
# --Projecting shapes--
|
||||
f_collar = CircleNeckHalf(
|
||||
design['collar']['fc_depth']['v'],
|
||||
design['collar']['width']['v'])
|
||||
b_collar = CircleNeckHalf(
|
||||
design['collar']['bc_depth']['v'],
|
||||
design['collar']['width']['v'])
|
||||
|
||||
self.interfaces = {
|
||||
'front_proj': pyg.Interface(self, f_collar),
|
||||
'back_proj': pyg.Interface(self, b_collar)
|
||||
}
|
||||
|
||||
# -- Panels --
|
||||
length_f, length_b = f_collar.length(), b_collar.length()
|
||||
height_p = body['height'] - body['head_l'] + depth
|
||||
|
||||
self.front = StraightBandPanel(
|
||||
f'{tag}_turtle_front', length_f, depth).translate_by(
|
||||
[-length_f / 2, height_p, 10])
|
||||
self.back = StraightBandPanel(
|
||||
f'{tag}_turtle_back', length_b, depth).translate_by(
|
||||
[-length_b / 2, height_p, -10])
|
||||
|
||||
self.stitching_rules.append((
|
||||
self.front.interfaces['right'],
|
||||
self.back.interfaces['right']
|
||||
))
|
||||
|
||||
self.interfaces.update({
|
||||
'front': self.front.interfaces['left'],
|
||||
'back': self.back.interfaces['left'],
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['bottom'],
|
||||
self.back.interfaces['bottom']
|
||||
)
|
||||
})
|
||||
|
||||
def length(self):
|
||||
return self.interfaces['back'].edges.length()
|
||||
|
||||
|
||||
class SimpleLapelPanel(pyg.Panel):
|
||||
"""A panel for the front part of simple Lapel"""
|
||||
def __init__(self, name, length, max_depth) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0], [max_depth, 0], [max_depth, -length]
|
||||
)
|
||||
|
||||
self.edges.append(
|
||||
pyg.CurveEdge(
|
||||
self.edges[-1].end,
|
||||
self.edges[0].start,
|
||||
[[0.7, 0.2]]
|
||||
)
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'to_collar': pyg.Interface(self, self.edges[0]),
|
||||
'to_bodice': pyg.Interface(self, self.edges[1])
|
||||
}
|
||||
|
||||
|
||||
class SimpleLapel(pyg.Component):
|
||||
|
||||
def __init__(self, tag, body, design) -> None:
|
||||
super().__init__(f'Turtle_{tag}')
|
||||
|
||||
depth = design['collar']['component']['depth']['v']
|
||||
standing = design['collar']['component']['lapel_standing']['v']
|
||||
|
||||
# --Projecting shapes--
|
||||
# Any front one!
|
||||
collar_type = globals()[design['collar']['f_collar']['v']]
|
||||
f_collar = collar_type(
|
||||
design['collar']['fc_depth']['v'],
|
||||
design['collar']['width']['v'],
|
||||
angle=design['collar']['fc_angle']['v'],
|
||||
flip=design['collar']['f_flip_curve']['v'])
|
||||
|
||||
b_collar = CircleNeckHalf(
|
||||
design['collar']['bc_depth']['v'],
|
||||
design['collar']['width']['v'])
|
||||
|
||||
self.interfaces = {
|
||||
'front_proj': pyg.Interface(self, f_collar),
|
||||
'back_proj': pyg.Interface(self, b_collar)
|
||||
}
|
||||
|
||||
# -- Panels --
|
||||
length_f, length_b = f_collar.length(), b_collar.length()
|
||||
height_p = body['height'] - body['head_l'] + depth * 2
|
||||
|
||||
self.front = SimpleLapelPanel(
|
||||
f'{tag}_lapel_front', length_f, depth).translate_by(
|
||||
[-depth * 2, height_p, 35]) # TODOLOW This should be related with the bodice panels' placement
|
||||
|
||||
if standing:
|
||||
self.back = StraightBandPanel(
|
||||
f'{tag}_lapel_back', length_b, depth).translate_by(
|
||||
[-length_b / 2, height_p, -10])
|
||||
else:
|
||||
# A curved back panel that follows the collar opening
|
||||
rad, angle, _ = b_collar[0].as_radius_angle()
|
||||
self.back = CircleArcPanel(
|
||||
f'{tag}_lapel_back', rad, depth, angle
|
||||
).translate_by([-length_b, height_p, -10])
|
||||
self.back.rotate_by(R.from_euler('XYZ', [90, 45, 0], degrees=True))
|
||||
|
||||
if standing:
|
||||
self.back.interfaces['right'].set_right_wrong(True)
|
||||
|
||||
self.stitching_rules.append((
|
||||
self.front.interfaces['to_collar'],
|
||||
self.back.interfaces['right']
|
||||
))
|
||||
|
||||
self.interfaces.update({
|
||||
#'front': NOTE: no front interface here
|
||||
'back': self.back.interfaces['left'],
|
||||
'bottom': pyg.Interface.from_multiple(
|
||||
self.front.interfaces['to_bodice'].set_right_wrong(True),
|
||||
self.back.interfaces['bottom'] if standing else self.back.interfaces['top'].set_right_wrong(True),
|
||||
)
|
||||
})
|
||||
|
||||
def length(self):
|
||||
return self.interfaces['back'].edges.length()
|
||||
|
||||
class HoodPanel(pyg.Panel):
|
||||
"""A panel for the side of the hood"""
|
||||
def __init__(self, name, f_depth, b_depth, f_length, b_length, width, in_length, depth) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
width = width / 2 # Panel covers one half only
|
||||
length = in_length + width / 2
|
||||
|
||||
# Bottom-back
|
||||
bottom_back_in = pyg.CurveEdge(
|
||||
[-width, -b_depth],
|
||||
[0, 0],
|
||||
[[0.3, -0.2], [0.6, 0.2]]
|
||||
)
|
||||
bottom_back = pyg.ops.curve_match_tangents(
|
||||
bottom_back_in.as_curve(),
|
||||
[1, 0], # Full opening is vertically aligned
|
||||
[1, 0],
|
||||
target_len=b_length,
|
||||
return_as_edge=True,
|
||||
verbose=self.verbose
|
||||
)
|
||||
self.edges.append(bottom_back)
|
||||
|
||||
# Bottom front
|
||||
bottom_front_in = pyg.CurveEdge(
|
||||
self.edges[-1].end,
|
||||
[width, -f_depth],
|
||||
[[0.3, 0.2], [0.6, -0.2]]
|
||||
)
|
||||
bottom_front = pyg.ops.curve_match_tangents(
|
||||
bottom_front_in.as_curve(),
|
||||
[1, 0], # Full opening is vertically aligned
|
||||
[1, 0],
|
||||
target_len=f_length,
|
||||
return_as_edge=True,
|
||||
verbose=self.verbose
|
||||
)
|
||||
self.edges.append(bottom_front)
|
||||
|
||||
# Front-top straight section
|
||||
self.edges.append(pyg.EdgeSeqFactory.from_verts(
|
||||
self.edges[-1].end,
|
||||
[width * 1.2, length], [width * 1.2 - depth, length]
|
||||
))
|
||||
# Back of the hood
|
||||
self.edges.append(
|
||||
pyg.CurveEdge(
|
||||
self.edges[-1].end,
|
||||
self.edges[0].start,
|
||||
[[0.2, -0.5]]
|
||||
)
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'to_other_side': pyg.Interface(self, self.edges[-2:]),
|
||||
'to_bodice': pyg.Interface(self, self.edges[0:2]).reverse()
|
||||
}
|
||||
|
||||
self.rotate_by(R.from_euler('XYZ', [0, -90, 0], degrees=True))
|
||||
self.translate_by([-width*2, 0, 0])
|
||||
|
||||
class Hood2Panels(pyg.Component):
|
||||
|
||||
def __init__(self, tag, body, design) -> None:
|
||||
super().__init__(f'Hood_{tag}')
|
||||
|
||||
# --Projecting shapes--
|
||||
width = design['collar']['width']['v']
|
||||
f_collar = CircleNeckHalf(
|
||||
design['collar']['fc_depth']['v'],
|
||||
design['collar']['width']['v'])
|
||||
b_collar = CircleNeckHalf(
|
||||
design['collar']['bc_depth']['v'],
|
||||
design['collar']['width']['v'])
|
||||
|
||||
self.interfaces = {
|
||||
'front_proj': pyg.Interface(self, f_collar),
|
||||
'back_proj': pyg.Interface(self, b_collar)
|
||||
}
|
||||
|
||||
# -- Panel --
|
||||
self.panel = HoodPanel(
|
||||
f'{tag}_hood',
|
||||
design['collar']['fc_depth']['v'],
|
||||
design['collar']['bc_depth']['v'],
|
||||
f_length=f_collar.length(),
|
||||
b_length=b_collar.length(),
|
||||
width=width,
|
||||
in_length=body['head_l'] * design['collar']['component']['hood_length']['v'],
|
||||
depth=width / 2 * design['collar']['component']['hood_depth']['v']
|
||||
).translate_by(
|
||||
[0, body['height'] - body['head_l'] + 10, 0])
|
||||
|
||||
self.interfaces.update({
|
||||
#'front': NOTE: no front interface here
|
||||
'back': self.panel.interfaces['to_other_side'],
|
||||
'bottom': self.panel.interfaces['to_bodice']
|
||||
})
|
||||
|
||||
def length(self):
|
||||
return self.panel.length()
|
||||
|
||||
121
assets/garment_programs/godet.py
Normal file
121
assets/garment_programs/godet.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
import pygarment as pyg
|
||||
|
||||
from assets.garment_programs.base_classes import BaseBottoms
|
||||
from assets.garment_programs import skirt_paneled as skirts
|
||||
|
||||
|
||||
class Insert(pyg.Panel):
|
||||
def __init__(self, id, width=30, depth=30) -> None:
|
||||
super().__init__(f'Insert_{id}')
|
||||
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0],
|
||||
[width/2, depth],
|
||||
[width, 0], loop=True)
|
||||
|
||||
self.interfaces = [
|
||||
pyg.Interface(self, self.edges[:2])
|
||||
]
|
||||
self.top_center_pivot()
|
||||
self.center_x()
|
||||
|
||||
|
||||
class GodetSkirt(BaseBottoms):
|
||||
|
||||
def __init__(self, body, design, rise=None) -> None:
|
||||
super().__init__(body, design, rise=rise)
|
||||
|
||||
gdesign = design['godet-skirt']
|
||||
ins_w = gdesign['insert_w']['v']
|
||||
ins_depth = gdesign['insert_depth']['v']
|
||||
|
||||
base_skirt = getattr(skirts, gdesign['base']['v'])
|
||||
# NOTE: godets currently don't like slits on the front/back
|
||||
# of the base skirt => Forcing to remove any slits
|
||||
self.base = base_skirt(body, design, rise=rise, slit=False)
|
||||
|
||||
bintr = self.base.interfaces['bottom']
|
||||
for edge, panel in zip(bintr.edges, bintr.panel):
|
||||
self.inserts(
|
||||
edge, panel, ins_w, ins_depth,
|
||||
num_inserts=gdesign['num_inserts']['v'] / len(bintr),
|
||||
cuts_dist=gdesign['cuts_distance']['v'])
|
||||
|
||||
self.interfaces = {
|
||||
'top': self.base.interfaces['top']
|
||||
}
|
||||
|
||||
def inserts(
|
||||
self, bottom_edge, panel, ins_w, ins_depth,
|
||||
num_inserts=3, cuts_dist=0):
|
||||
"""Create insert panels, add cuts to the skirt panel,
|
||||
and connect created insert panels with them
|
||||
"""
|
||||
|
||||
num_inserts = int(num_inserts)
|
||||
bottom_len = bottom_edge.length()
|
||||
|
||||
pbbox = panel.bbox3D()
|
||||
z_transl = panel.translation[-1] + np.sign(panel.translation[-1]) * 5
|
||||
y_base = pbbox[0][1] # min Y
|
||||
x_shift = (pbbox[0][0] + pbbox[1][0]) / 2
|
||||
|
||||
cut_width = (bottom_len - cuts_dist * num_inserts) / num_inserts
|
||||
if cut_width < 1:
|
||||
cut_width = 1 # 1 cm
|
||||
cuts_dist_req = cuts_dist
|
||||
cuts_dist = (bottom_len - cut_width * num_inserts) / num_inserts
|
||||
if self.verbose:
|
||||
print(f'{self.__class__.__name__}::WARNING:: Cannot place {num_inserts} cuts '
|
||||
f'with requested distance between cuts ({cuts_dist_req}). '
|
||||
f'Using the maximum possible distance ({cuts_dist})')
|
||||
|
||||
# Insert panels
|
||||
insert = Insert(0, width=ins_w, depth=ins_depth).translate_by([
|
||||
x_shift - num_inserts * ins_w / 2 + ins_w / 2, y_base + ins_depth, z_transl])
|
||||
self.subs += pyg.ops.distribute_horisontally(
|
||||
insert, num_inserts, -ins_w, 'ins_' + panel.name)
|
||||
|
||||
# make appropriate cuts and stitches
|
||||
side_len = math.sqrt((ins_w / 2)**2 + ins_depth**2) # should be the same on the skirt and the insert
|
||||
|
||||
if side_len > cut_width / 2: # Normal case
|
||||
cut_depth = math.sqrt(side_len**2 - (cut_width / 2)**2)
|
||||
else:
|
||||
old_cut_width = cut_width
|
||||
cut_depth = 1
|
||||
cut_width = 2 * math.sqrt(side_len**2 - cut_depth**2)
|
||||
if self.verbose:
|
||||
print(f'{self.__class__.__name__}::WARNING::Requested cut_width ({old_cut_width:.2f}) '
|
||||
'is too wide for given inserts. '
|
||||
f'Using the maximum possible width ({cut_width:.2f})')
|
||||
|
||||
cut_shape = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0], [cut_width / 2, cut_depth], [cut_width, 0])
|
||||
|
||||
right = z_transl < 0 # NOTE: heuristic corresponding to skirts in our collection
|
||||
|
||||
for i in range(num_inserts):
|
||||
offset = cut_width / 2 + (cuts_dist / 2 if i == 0 else cuts_dist) # start_offest + i * stride
|
||||
|
||||
new_bottom, cutted, _ = pyg.ops.cut_into_edge(
|
||||
cut_shape, bottom_edge, offset=offset, right=right)
|
||||
panel.edges.substitute(bottom_edge, new_bottom)
|
||||
bottom_edge = new_bottom[-1] # New edge that needs to be cutted -- on the next step
|
||||
|
||||
cut_interface = pyg.Interface(panel, cutted)
|
||||
if right:
|
||||
cut_interface.reverse()
|
||||
|
||||
self.stitching_rules.append(
|
||||
(self.subs[-1-i if right else -(num_inserts-i)].interfaces[0],
|
||||
cut_interface))
|
||||
|
||||
def get_rise(self):
|
||||
return self.base.get_rise()
|
||||
|
||||
def length(self):
|
||||
return self.base.length()
|
||||
195
assets/garment_programs/meta_garment.py
Normal file
195
assets/garment_programs/meta_garment.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from assets.garment_programs.tee import *
|
||||
from assets.garment_programs.godet import *
|
||||
from assets.garment_programs.bodice import *
|
||||
from assets.garment_programs.pants import *
|
||||
from assets.garment_programs.bands import *
|
||||
from assets.garment_programs.skirt_paneled import *
|
||||
from assets.garment_programs.skirt_levels import *
|
||||
from assets.garment_programs.circle_skirt import *
|
||||
from assets.garment_programs.sleeves import *
|
||||
import yaml
|
||||
class TotalLengthError(BaseException):
|
||||
"""Error indicating that the total length of a garment goes beyond
|
||||
the floor length for a given person"""
|
||||
pass
|
||||
|
||||
class IncorrectElementConfiguration(BaseException):
|
||||
"""Error indicating that given pattern is an empty garment"""
|
||||
pass
|
||||
|
||||
class MetaGarment(pyg.Component):
|
||||
"""Meta garment component
|
||||
Depending on parameter values it can generate sewing patterns
|
||||
for various dresses and jumpsuit styles and fit them to the body
|
||||
measurements
|
||||
"""
|
||||
|
||||
def __init__(self, name, body, design) -> None:
|
||||
super().__init__(name)
|
||||
self.body = body
|
||||
self.design = design
|
||||
# with open('assets/bodies/mean_all_full.yaml', 'w') as f:
|
||||
# yaml.dump(body, f, default_flow_style=False)
|
||||
# Elements
|
||||
self.upper_name = design['meta']['upper']['v']
|
||||
self.lower_name = design['meta']['bottom']['v']
|
||||
self.belt_name = design['meta']['wb']['v']
|
||||
|
||||
# Upper garment
|
||||
if self.upper_name:
|
||||
upper = globals()[self.upper_name]
|
||||
self.subs = [upper(body, design)]
|
||||
|
||||
# Set a label
|
||||
self.subs[-1].set_panel_label('body', overwrite=False)
|
||||
|
||||
#Pan up a bit:
|
||||
self.subs[-1].translate_by((0, 5, 0))
|
||||
|
||||
# Here are all the garments that are not connected to have a belt
|
||||
if self.belt_name == None:
|
||||
if design['meta']['connected']['v'] == False and design['meta']['bottom']['v'] != None:
|
||||
if design['meta']['bottom']['v'] != 'Pants':
|
||||
design['meta']['wb']['v'] = "FittedWB"
|
||||
design['waistband']['waist']['v'] = 0.7
|
||||
design['waistband']['width']['v'] = 0.1
|
||||
if design['meta']['bottom']['v'] == 'Pants':
|
||||
design['meta']['wb']['v'] = 'FittedWB'
|
||||
design['waistband']['waist']['v'] = 1
|
||||
design['waistband']['width']['v'] = 0.2
|
||||
self.belt_name = design['meta']['wb']['v']
|
||||
|
||||
# Define Lower garment
|
||||
if self.lower_name:
|
||||
Lower_class = globals()[self.lower_name]
|
||||
# NOTE: full rise for fitted tops
|
||||
Lower = Lower_class(body, design, rise=1. if self.upper_name and 'Fitted' in self.upper_name else None)
|
||||
else:
|
||||
Lower = None
|
||||
# Belt (or not)
|
||||
# TODO Adapt the rise of the lower garment to the width of the belt for correct matching
|
||||
if self.belt_name:
|
||||
Belt_class = globals()[self.belt_name]
|
||||
|
||||
# Adjust rise to match the Lower garment if needed
|
||||
Belt = Belt_class(body, design, Lower.get_rise() if Lower else 1.)
|
||||
|
||||
self.subs.append(Belt)
|
||||
# Place below the upper garment
|
||||
if len(self.subs) > 1:
|
||||
self.subs[-1].place_by_interface(
|
||||
self.subs[-1].interfaces['top'],
|
||||
self.subs[-2].interfaces['bottom'],
|
||||
gap=5
|
||||
)
|
||||
if design['meta']['connected']['v'] : # If you need to connect, you need to connect the belt to the top
|
||||
self.stitching_rules.append(
|
||||
(self.subs[-2].interfaces['bottom'],
|
||||
self.subs[-1].interfaces['top']))
|
||||
# Add waist label
|
||||
self.subs[-1].interfaces['top'].edges.propagate_label('lower_interface')
|
||||
# Set panel segmentation labels
|
||||
self.subs[-1].set_panel_label('body', overwrite=False)
|
||||
if self.lower_name:
|
||||
self.subs.append(Lower)
|
||||
# Place below the upper garment or self.wb
|
||||
if len(self.subs) > 1:
|
||||
self.subs[-1].place_by_interface(
|
||||
self.subs[-1].interfaces['top'],
|
||||
self.subs[-2].interfaces['bottom'],
|
||||
gap=5
|
||||
)
|
||||
#There must be a belt now, so here's how to connect the belt to the bottom
|
||||
self.stitching_rules.append(
|
||||
(self.subs[-2].interfaces['bottom'],
|
||||
self.subs[-1].interfaces['top']))
|
||||
#To deal with the simulation impact caused by the lack of connection, the main thing is to adjust the position and all the next translationby is to simulate better and reduce the problem situation.
|
||||
# The specific tranlateby values are mainly tried
|
||||
if not design['meta']['connected']['v']:
|
||||
self.handle_disconnected_position_influence(design)
|
||||
# Add waist label
|
||||
if not self.belt_name:
|
||||
self.subs[-1].interfaces['top'].edges.propagate_label('lower_interface')
|
||||
# Set panel segmentation labels
|
||||
self.subs[-1].set_panel_label('leg', overwrite=False)
|
||||
|
||||
|
||||
|
||||
def handle_disconnected_position_influence(self, design):
|
||||
'''deal with the influence of disconnected garments on the simulation by translateby .
|
||||
which is value is tried out'''
|
||||
self.subs[-1].translate_by((0, 6, 0))
|
||||
# Gets a specific clothing object
|
||||
pant_flag = False
|
||||
pant = None
|
||||
circleskirt = None
|
||||
circleskirt_flag = False
|
||||
shirt = None
|
||||
shirt_flag = False
|
||||
for sub in self.subs:
|
||||
if isinstance(sub, Pants):
|
||||
pant_flag = True
|
||||
pant = sub
|
||||
if isinstance(sub, SkirtCircle):
|
||||
circleskirt = sub
|
||||
circleskirt_flag = True
|
||||
if isinstance(sub, Shirt):
|
||||
shirt = sub
|
||||
shirt_flag = True
|
||||
if pant_flag and shirt_flag:
|
||||
|
||||
# If the top is short<=1.2, you need to pan the pants downward.
|
||||
if design['shirt']['length']['v'] <= 1.2:
|
||||
# pass
|
||||
pant.translate_by((0, -13, 0))
|
||||
# For clothes that are too long, pull them up
|
||||
if design['shirt']['length'][
|
||||
'v'] > 1.2:
|
||||
pant.translate_by((0, 25, 0))
|
||||
translate_d = 8
|
||||
shirt.right.ftorso.translate_by((0, 0, translate_d))
|
||||
shirt.right.btorso.translate_by((0, 0, -translate_d))
|
||||
shirt.left.ftorso.translate_by((0, 0, translate_d))
|
||||
shirt.left.btorso.translate_by((0, 0, -translate_d))
|
||||
# For the handling that is not underwear
|
||||
if design['meta']['bottom']['v'] is not None and design['meta']['bottom']['v'] != 'Pants':
|
||||
translate_d = 10
|
||||
bottom_garment = self.subs[-1]
|
||||
# if design['meta']['bottom']['v'] != "SkirtManyPanels":
|
||||
# bottom_garment.translate_by((0, 0, 0))
|
||||
# Deal with a single class first, for multiple skirts such as level, this is not processed, and later, at present, multi-layer group skirts do not need to be processed
|
||||
if (design['meta']['bottom']['v'] == "Skirt2 " or design['meta']['bottom']['v'] == "SkirtCircle"
|
||||
or design['meta']['bottom']['v'] == "AssymmSkirtCircle"
|
||||
or design['meta']['bottom']['v'] == "PencilSkirt"):
|
||||
bottom_garment.front.translate_by((0, 0, translate_d))
|
||||
bottom_garment.back.translate_by((0, 0, -translate_d))
|
||||
bottom_garment.translate_by((0, -8, 0))
|
||||
# For the handling of the pants
|
||||
if pant_flag:
|
||||
pant.translate_by((0, -8, 0))
|
||||
|
||||
|
||||
def assert_total_length(self, tol=1):
|
||||
"""Check the total length of components"""
|
||||
# Check that the total length of the components are less that body height
|
||||
length = self.length()
|
||||
floor = self.body['height'] - self.body['head_l']
|
||||
if length > floor + tol:
|
||||
raise TotalLengthError(f'{self.__class__.__name__}::{self.name}::ERROR:'
|
||||
f':Total length {length} exceeds the floor length {floor}')
|
||||
|
||||
# TODO these checks don't require initialization of the pattern!
|
||||
def assert_non_empty(self, filter_belts=True):
|
||||
"""Check that the garment is non-empty
|
||||
* filter_wb -- if set, then garments consisting only of waistbands are considered empty
|
||||
"""
|
||||
if not self.upper_name and not self.lower_name:
|
||||
if filter_belts or not self.belt_name:
|
||||
raise IncorrectElementConfiguration()
|
||||
|
||||
def assert_skirt_waistband(self):
|
||||
"""Check if a generated heavy skirt is created with a waistband"""
|
||||
|
||||
if self.lower_name and self.lower_name in ['SkirtCircle', 'AsymmSkirtCircle', 'SkirtManyPanels']:
|
||||
if not (self.belt_name or self.upper_name):
|
||||
raise IncorrectElementConfiguration()
|
||||
312
assets/garment_programs/pants.py
Normal file
312
assets/garment_programs/pants.py
Normal file
@@ -0,0 +1,312 @@
|
||||
from copy import deepcopy
|
||||
import numpy as np
|
||||
|
||||
import pygarment as pyg
|
||||
from assets.garment_programs.base_classes import BaseBottoms
|
||||
from assets.garment_programs import bands
|
||||
|
||||
|
||||
class PantPanel(pyg.Panel):
|
||||
def __init__(
|
||||
self, name, body, design,
|
||||
length,
|
||||
waist,
|
||||
hips,
|
||||
hips_depth,
|
||||
crotch_width,
|
||||
dart_position,
|
||||
match_top_int_to=None,
|
||||
hipline_ext=1,
|
||||
double_dart=False) -> None:
|
||||
"""
|
||||
Basic pant panel with option to be fitted (with darts)
|
||||
"""
|
||||
super().__init__(name)
|
||||
|
||||
flare = body['leg_circ'] * (design['flare']['v'] - 1) / 4
|
||||
hips_depth = hips_depth * hipline_ext
|
||||
|
||||
hip_side_incl = np.deg2rad(body['_hip_inclination'])
|
||||
dart_depth = hips_depth * 0.8
|
||||
|
||||
# Crotch cotrols
|
||||
crotch_depth_diff = body['crotch_hip_diff']
|
||||
crotch_extention = crotch_width
|
||||
|
||||
# eval pants shape
|
||||
# TODO Return ruffle opportunity?
|
||||
|
||||
# amount of extra fabric at waist
|
||||
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) * hips_depth
|
||||
# Small difference
|
||||
if hw_shift > w_diff:
|
||||
hw_shift = w_diff
|
||||
|
||||
# --- Edges definition ---
|
||||
# Right
|
||||
if pyg.utils.close_enough(design['flare']['v'], 1): # skip optimization
|
||||
right_bottom = pyg.Edge(
|
||||
[-flare, 0],
|
||||
[0, length]
|
||||
)
|
||||
else:
|
||||
right_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
|
||||
[-flare, 0],
|
||||
[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 + hips_depth],
|
||||
target_tan0=np.array([0, 1]),
|
||||
initial_guess=[0.5, 0]
|
||||
)
|
||||
|
||||
top = pyg.Edge(
|
||||
right_top.end,
|
||||
[w_diff + waist, length + hips_depth]
|
||||
)
|
||||
|
||||
crotch_top = pyg.Edge(
|
||||
top.end,
|
||||
[hips, length + 0.45 * hips_depth] # A bit higher than hip line
|
||||
# NOTE: The point should be lower than the minimum rise value (0.5)
|
||||
)
|
||||
crotch_bottom = pyg.CurveEdgeFactory.curve_from_tangents(
|
||||
crotch_top.end,
|
||||
[hips + crotch_extention, length - crotch_depth_diff],
|
||||
target_tan0=np.array([0, -1]),
|
||||
target_tan1=np.array([1, 0]),
|
||||
initial_guess=[0.5, -0.5]
|
||||
)
|
||||
|
||||
left = pyg.CurveEdgeFactory.curve_from_tangents(
|
||||
crotch_bottom.end,
|
||||
[
|
||||
# NOTE "Magic value" (-2 cm) which we use to define default width:
|
||||
# just a little behing the crotch point
|
||||
# NOTE: Ensuring same distance from the crotch point in both
|
||||
# front and back for matching curves
|
||||
crotch_bottom.end[0] - 2 + flare,
|
||||
# NOTE: The inside edge either matches the length of the outside (0, normal case)
|
||||
# or when the inteded length is smaller than crotch depth,
|
||||
# inside edge covers of the inside leg a bit below the crotch (panties-like shorts)
|
||||
y:=min(0, length - crotch_depth_diff * 1.5)
|
||||
],
|
||||
target_tan1=[flare, y - crotch_bottom.end[1]],
|
||||
initial_guess=[0.3, 0]
|
||||
)
|
||||
|
||||
self.edges = pyg.EdgeSequence(
|
||||
right_bottom, right_top, top, crotch_top, crotch_bottom, left
|
||||
).close_loop()
|
||||
bottom = self.edges[-1]
|
||||
|
||||
# Default placement
|
||||
self.set_pivot(crotch_bottom.end)
|
||||
self.translation = [-0.5, - hips_depth - crotch_depth_diff + 5, 0]
|
||||
|
||||
# Out interfaces (easier to define before adding a dart)
|
||||
self.interfaces = {
|
||||
'outside': pyg.Interface(
|
||||
self,
|
||||
pyg.EdgeSequence(right_bottom, right_top),
|
||||
ruffle=[1, hipline_ext]),
|
||||
'crotch': pyg.Interface(self, pyg.EdgeSequence(crotch_top, crotch_bottom)),
|
||||
'inside': pyg.Interface(self, left),
|
||||
'bottom': pyg.Interface(self, bottom)
|
||||
}
|
||||
|
||||
# Add top dart
|
||||
# NOTE: Ruffle indicator to match to waistline proportion for correct balance line matching
|
||||
dart_width = w_diff - hw_shift
|
||||
if 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=waist / 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=waist / 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):
|
||||
|
||||
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,
|
||||
]
|
||||
|
||||
darts = [
|
||||
pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth * 0.9), # smaller
|
||||
pyg.EdgeSeqFactory.dart_shape(dart_width / 2, dart_depth)
|
||||
]
|
||||
else:
|
||||
offsets_mid = [
|
||||
- dart_position - dart_width / 2,
|
||||
]
|
||||
darts = [
|
||||
pyg.EdgeSeqFactory.dart_shape(dart_width, dart_depth)
|
||||
]
|
||||
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 + off,
|
||||
edge_seq=top_edges,
|
||||
int_edge_seq=int_edges
|
||||
)
|
||||
|
||||
return top_edges, int_edges
|
||||
|
||||
|
||||
class PantsHalf(BaseBottoms):
|
||||
def __init__(self, tag, body, design, rise=None) -> None:
|
||||
super().__init__(body, design, tag, rise=rise)
|
||||
design = design['pants']
|
||||
self.rise = design['rise']['v'] if rise is None else rise
|
||||
waist, hips_depth, waist_back = self.eval_rise(self.rise)
|
||||
|
||||
# NOTE: min value = full sum > leg curcumference
|
||||
# Max: pant leg falls flat from the back
|
||||
# Mostly from the back side
|
||||
# => This controls the foundation width of the pant
|
||||
min_ext = body['leg_circ'] - body['hips'] / 2 + 5 # 2 inch ease: from pattern making book
|
||||
front_hip = (body['hips'] - body['hip_back_width']) / 2
|
||||
crotch_extention = min_ext * design['width']['v']
|
||||
front_extention = front_hip / 4 # From pattern making book
|
||||
back_extention = crotch_extention - front_extention
|
||||
|
||||
length, cuff_len = design['length']['v'], design['cuff']['cuff_len']['v']
|
||||
if design['cuff']['type']['v']:
|
||||
if length - cuff_len < design['length']['range'][0]: # Min length from paramss
|
||||
# Cannot be longer then a pant
|
||||
cuff_len = length - design['length']['range'][0]
|
||||
# Include the cuff into the overall length,
|
||||
# unless the requested length is too short to fit the cuff
|
||||
# (to avoid negative length)
|
||||
length -= cuff_len
|
||||
length *= body['_leg_length']
|
||||
cuff_len *= body['_leg_length']
|
||||
|
||||
self.front = PantPanel(
|
||||
f'pant_f_{tag}', body, design,
|
||||
length=length,
|
||||
waist=(waist - waist_back) / 2,
|
||||
hips=(body['hips'] - body['hip_back_width']) / 2,
|
||||
hips_depth=hips_depth,
|
||||
dart_position = body['bust_points'] / 2,
|
||||
crotch_width=front_extention,
|
||||
match_top_int_to=(body['waist'] - body['waist_back_width']) / 2
|
||||
).translate_by([0, body['_waist_level'] - 5, 25])
|
||||
self.back = PantPanel(
|
||||
f'pant_b_{tag}', body, design,
|
||||
length=length,
|
||||
waist=waist_back / 2,
|
||||
hips=body['hip_back_width'] / 2,
|
||||
hips_depth=hips_depth,
|
||||
hipline_ext=1.1,
|
||||
dart_position = body['bum_points'] / 2,
|
||||
crotch_width=back_extention,
|
||||
match_top_int_to=body['waist_back_width'] / 2,
|
||||
double_dart=True
|
||||
).translate_by([0, body['_waist_level'] - 5, -20])
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.front.interfaces['outside'], self.back.interfaces['outside']),
|
||||
(self.front.interfaces['inside'], self.back.interfaces['inside'])
|
||||
)
|
||||
|
||||
# add a cuff
|
||||
# TODOLOW This process is the same for sleeves -- make a function?
|
||||
if design['cuff']['type']['v']:
|
||||
|
||||
pant_bottom = pyg.Interface.from_multiple(
|
||||
self.front.interfaces['bottom'],
|
||||
self.back.interfaces['bottom'])
|
||||
|
||||
# Copy to avoid editing original design dict
|
||||
cdesign = deepcopy(design)
|
||||
cdesign['cuff']['b_width'] = {}
|
||||
cdesign['cuff']['b_width']['v'] = pant_bottom.edges.length() / design['cuff']['top_ruffle']['v']
|
||||
cdesign['cuff']['cuff_len']['v'] = cuff_len
|
||||
|
||||
# Init
|
||||
cuff_class = getattr(bands, cdesign['cuff']['type']['v'])
|
||||
self.cuff = cuff_class(f'pant_{tag}', cdesign)
|
||||
|
||||
# Position
|
||||
self.cuff.place_by_interface(
|
||||
self.cuff.interfaces['top'],
|
||||
pant_bottom,
|
||||
gap=5,
|
||||
alignment='left'
|
||||
)
|
||||
|
||||
# Stitch
|
||||
self.stitching_rules.append((
|
||||
pant_bottom,
|
||||
self.cuff.interfaces['top'])
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'crotch_f': self.front.interfaces['crotch'],
|
||||
'crotch_b': self.back.interfaces['crotch'],
|
||||
'top_f': self.front.interfaces['top'],
|
||||
'top_b': self.back.interfaces['top']
|
||||
}
|
||||
|
||||
def length(self):
|
||||
if self.design['pants']['cuff']['type']['v']:
|
||||
return self.front.length() + self.cuff.length()
|
||||
|
||||
return self.front.length()
|
||||
|
||||
class Pants(BaseBottoms):
|
||||
def __init__(self, body, design, rise=None) -> None:
|
||||
super().__init__(body, design)
|
||||
|
||||
self.right = PantsHalf('r', body, design, rise)
|
||||
self.left = PantsHalf('l', body, design, rise).mirror()
|
||||
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.right.interfaces['crotch_f'], self.left.interfaces['crotch_f']),
|
||||
(self.right.interfaces['crotch_b'], self.left.interfaces['crotch_b']),
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'top_f': pyg.Interface.from_multiple(
|
||||
self.right.interfaces['top_f'], self.left.interfaces['top_f']),
|
||||
'top_b': pyg.Interface.from_multiple(
|
||||
self.right.interfaces['top_b'], self.left.interfaces['top_b']),
|
||||
# Some are reversed for correct connection
|
||||
'top': pyg.Interface.from_multiple( # around the body starting from front right
|
||||
self.right.interfaces['top_f'].flip_edges(),
|
||||
self.left.interfaces['top_f'].reverse(with_edge_dir_reverse=True),
|
||||
self.left.interfaces['top_b'].flip_edges(),
|
||||
self.right.interfaces['top_b'].reverse(with_edge_dir_reverse=True), # Flips the edges and restores the direction
|
||||
)
|
||||
}
|
||||
|
||||
def get_rise(self):
|
||||
return self.right.get_rise()
|
||||
|
||||
def length(self):
|
||||
return self.right.length()
|
||||
|
||||
64
assets/garment_programs/shapes.py
Normal file
64
assets/garment_programs/shapes.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""A decorative shapes"""
|
||||
import pygarment as pyg
|
||||
|
||||
|
||||
def sample_arc(curve, length, stride, n_points, shift=0):
|
||||
ts = [(shift + i*stride) / length for i in range(n_points)]
|
||||
verts = [curve.point(t) for t in ts]
|
||||
|
||||
for i in range(len(verts)):
|
||||
verts[i] = [verts[i].real, verts[i].imag]
|
||||
|
||||
return verts
|
||||
|
||||
|
||||
def Sun(width, depth, n_rays=8, d_rays=5, **kwargs):
|
||||
"""Sun-like mark"""
|
||||
|
||||
# Outer arc
|
||||
out_arc = pyg.CircleEdgeFactory.from_three_points(
|
||||
[0, 0], [width, 0], [width/2, depth]
|
||||
)
|
||||
in_arc = pyg.CircleEdgeFactory.from_three_points(
|
||||
[d_rays, 0], [width - d_rays, 0], [width/2, depth - d_rays]
|
||||
)
|
||||
out_curve = out_arc.as_curve()
|
||||
in_curve = in_arc.as_curve()
|
||||
|
||||
# Sample with stride
|
||||
out_stride = out_arc.length() / n_rays
|
||||
in_stride = in_arc.length() / n_rays
|
||||
|
||||
out_verts = sample_arc(out_curve, out_arc.length(),
|
||||
out_stride, n_rays, out_stride / 2)
|
||||
in_verts = sample_arc(in_curve, in_arc.length(), in_stride, n_rays + 1, 0)
|
||||
|
||||
# Mix the vertices in the right order
|
||||
verts = out_verts
|
||||
for i in range(len(in_verts)):
|
||||
verts.insert(i*2, in_verts[i])
|
||||
|
||||
shape = pyg.EdgeSeqFactory.from_verts(*verts)
|
||||
return shape, shape
|
||||
|
||||
|
||||
def SIGGRAPH_logo(width, depth=None, **kwargs):
|
||||
"""Shape of SIGGRAPH Logo (split vertically)"""
|
||||
|
||||
filename='./assets/img/siggraph_logo_thick_connection.svg' # NOTE assumes the script is run from the root
|
||||
# TODOLOW path w.r.t. current file
|
||||
left_seq, right_seq = pyg.EdgeSeqFactory.halfs_from_svg(
|
||||
filename, target_height=width)
|
||||
|
||||
return left_seq, right_seq
|
||||
|
||||
|
||||
def SVGFile(width, filename, depth=None, **kwargs):
|
||||
"""Shape loaded from any svg file:
|
||||
The shape is expected to consist of non-nested loops
|
||||
each passing through OY once
|
||||
"""
|
||||
|
||||
left_seq, right_seq = pyg.EdgeSeqFactory.halfs_from_svg(
|
||||
filename, target_height=width)
|
||||
return left_seq, right_seq
|
||||
151
assets/garment_programs/skirt_levels.py
Normal file
151
assets/garment_programs/skirt_levels.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from assets.garment_programs.circle_skirt import *
|
||||
from assets.garment_programs.skirt_paneled import *
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class SkirtLevels(BaseBottoms):
|
||||
"""Skirt constiting of multuple stitched skirts"""
|
||||
|
||||
def __init__(self, body, design, rise=None) -> None:
|
||||
super().__init__(body, design, rise=rise)
|
||||
|
||||
ldesign = design['levels-skirt']
|
||||
lbody = deepcopy(body) # We will modify the values, so need a copy
|
||||
n_levels = ldesign['num_levels']['v']
|
||||
ruffle = ldesign['level_ruffle']['v']
|
||||
|
||||
# Adjust length to the common denominators
|
||||
self.eval_length(ldesign, body)
|
||||
|
||||
# Definitions
|
||||
self.rise = ldesign['rise']['v'] if rise is None else rise
|
||||
base_skirt_class = globals()[ldesign['base']['v']]
|
||||
self.subs.append(base_skirt_class(
|
||||
body,
|
||||
design,
|
||||
length=self.base_len,
|
||||
rise=self.rise,
|
||||
slit=False))
|
||||
|
||||
if (hasattr(base := self.subs[0], 'design')
|
||||
and 'low_angle' in base.design):
|
||||
self.angle = base.design['low_angle']['v']
|
||||
else:
|
||||
self.angle = 0
|
||||
|
||||
# Place the levels
|
||||
level_skirt_class = globals()[ldesign['level']['v']]
|
||||
for i in range(n_levels):
|
||||
# Adjust the mesurement to trick skirts into producing correct width
|
||||
# TODOLOW More elegant overwrite
|
||||
lbody['waist'] = ruffle * self.subs[-1].interfaces['bottom'].edges.length()
|
||||
lbody['waist_back_width'] = ruffle * self.subs[-1].interfaces['bottom_b'].edges.length()
|
||||
self.subs.append(level_skirt_class(
|
||||
lbody,
|
||||
design,
|
||||
tag=str(i),
|
||||
length=self.level_len,
|
||||
slit=False,
|
||||
top_ruffles=False))
|
||||
|
||||
# Placement
|
||||
# Rotation if base is assymetric
|
||||
self.subs[-1].rotate_by(R.from_euler(
|
||||
'XYZ', [0, 0, -self.angle], degrees=True))
|
||||
|
||||
self.subs[-1].place_by_interface(
|
||||
self.subs[-1].interfaces['top'],
|
||||
self.subs[-2].interfaces['bottom'],
|
||||
gap=5
|
||||
)
|
||||
# Stitch
|
||||
self.stitching_rules.append((
|
||||
self.subs[-2].interfaces['bottom'],
|
||||
self.subs[-1].interfaces['top']
|
||||
))
|
||||
|
||||
self.interfaces = {
|
||||
'top': self.subs[0].interfaces['top']
|
||||
}
|
||||
|
||||
def eval_length(self, ldesign, body):
|
||||
|
||||
# With convertion to absolute values
|
||||
total_length = ldesign['length']['v'] * body['_leg_length']
|
||||
self.base_len = total_length * ldesign['base_length_frac']['v']
|
||||
self.level_len = (total_length - self.base_len) / ldesign['num_levels']['v']
|
||||
|
||||
# Add hip_line (== zero length)
|
||||
self.base_len = body['hips_line'] * ldesign['rise']['v'] + self.base_len
|
||||
|
||||
|
||||
class SkirtLayers(BaseBottoms):
|
||||
"""Skirt consisting of multiple layered skirts stitched at the waistline"""
|
||||
|
||||
def __init__(self, body, design, rise=None):
|
||||
super().__init__(body, design, rise=rise)
|
||||
|
||||
ldesign = design['layers-skirt']
|
||||
lbody = deepcopy(body) # We will modify the values, so need a copy
|
||||
n_layers = ldesign['num_layers']['v']
|
||||
ruffle = ldesign['layer_ruffle']['v']
|
||||
|
||||
# Definitions
|
||||
self.rise = ldesign['rise']['v'] if rise is None else rise
|
||||
base_skirt_class = globals()[ldesign['base']['v']]
|
||||
|
||||
total_length = ldesign['length']['v'] * body['_leg_length']
|
||||
layer_lengths = self.eval_layer_lengths(ldesign, total_length, n_layers)
|
||||
# Place the levels
|
||||
self.layers = []
|
||||
for i in range(n_layers):
|
||||
# Adjust the measurements to produce correct width with ruffle
|
||||
waist_multiplier = 1 + ruffle * i
|
||||
lbody['waist'] = self.body['waist'] * waist_multiplier
|
||||
lbody['waist_back_width'] = self.body['waist_back_width'] * waist_multiplier
|
||||
layer_length = layer_lengths[i]
|
||||
skirt_layer = base_skirt_class(
|
||||
lbody,
|
||||
design,
|
||||
tag=str(i),
|
||||
length=layer_length,
|
||||
rise=self.rise,
|
||||
slit=False,
|
||||
top_ruffles=False)
|
||||
|
||||
# Place the layer at the waistline
|
||||
skirt_layer.translate_by([0, self.body['_waist_level'], 0])
|
||||
|
||||
self.layers.append(skirt_layer)
|
||||
|
||||
# Interfaces
|
||||
self.interfaces = {
|
||||
'top': self.layers[0].interfaces['top'],
|
||||
'bottom': self.layers[-1].interfaces['bottom']
|
||||
}
|
||||
|
||||
# Stitching rules: stitch each outer layer's top interface
|
||||
# to the 'top' interface of the component (waistline)
|
||||
|
||||
for layer in self.layers[1:]:
|
||||
self.stitching_rules.append(
|
||||
(self.interfaces['top'], layer.interfaces['top'])
|
||||
)
|
||||
|
||||
|
||||
# Add layers to subcomponents
|
||||
self.subs.extend(self.layers)
|
||||
for id,layer in enumerate(self.layers):
|
||||
layer.front.translate_by([0,0, 5*id])
|
||||
layer.back.translate_by([0, 0, -5*id])
|
||||
|
||||
def eval_layer_lengths(self, ldesign, total_length, n_layers):
|
||||
"""Calculate lengths for each layer"""
|
||||
if 'layer_lengths' in ldesign:
|
||||
# If specific lengths are provided for each layer
|
||||
layer_lengths = [ldesign['layer_lengths'][i]['v'] * total_length for i in range(n_layers)]
|
||||
else:
|
||||
# Distribute the total length among layers, possibly making outer layers longer
|
||||
base_length = total_length / n_layers
|
||||
layer_lengths = [base_length * (1 + 0.1 * i) for i in range(n_layers)]
|
||||
return layer_lengths
|
||||
512
assets/garment_programs/skirt_paneled.py
Normal file
512
assets/garment_programs/skirt_paneled.py
Normal file
@@ -0,0 +1,512 @@
|
||||
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()
|
||||
|
||||
373
assets/garment_programs/sleeves.py
Normal file
373
assets/garment_programs/sleeves.py
Normal file
@@ -0,0 +1,373 @@
|
||||
from copy import deepcopy
|
||||
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
from assets.garment_programs import bands
|
||||
import pygarment as pyg
|
||||
|
||||
|
||||
# ------ Armhole shapes ------
|
||||
def ArmholeSquare(incl, width, angle, invert=True, **kwargs):
|
||||
"""Simple square armhole cut-out
|
||||
Not recommended to use for sleeves, stitching in 3D might be hard
|
||||
|
||||
if angle is provided, it also calculated the shape of the sleeve interface to attach
|
||||
|
||||
returns edge sequence and part to be preserved inverted
|
||||
"""
|
||||
|
||||
edges = pyg.EdgeSeqFactory.from_verts([0, 0], [incl, 0], [incl, width])
|
||||
if not invert:
|
||||
return edges, None
|
||||
|
||||
sina, cosa = np.sin(angle), np.cos(angle)
|
||||
l = edges[0].length()
|
||||
sleeve_edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[incl + l*sina, - l*cosa],
|
||||
[incl, 0], [incl, width])
|
||||
|
||||
# TODOLOW Bend instead of rotating to avoid sharp connection
|
||||
sleeve_edges.rotate(angle=-angle)
|
||||
|
||||
return edges, sleeve_edges
|
||||
|
||||
|
||||
def ArmholeAngle(incl, width, angle, incl_coeff=0.2, w_coeff=0.2,
|
||||
invert=True, **kwargs):
|
||||
"""Piece-wise smooth armhole shape"""
|
||||
diff_incl = incl * (1 - incl_coeff)
|
||||
edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0], [diff_incl, w_coeff * width], [incl, width])
|
||||
if not invert:
|
||||
return edges, None
|
||||
|
||||
sina, cosa = np.sin(angle), np.cos(angle)
|
||||
l = edges[0].length()
|
||||
sleeve_edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[diff_incl + l*sina, w_coeff * width - l*cosa],
|
||||
[diff_incl, w_coeff * width], [incl, width])
|
||||
# TODOLOW Bend instead of rotating to avoid sharp connection
|
||||
sleeve_edges.rotate(angle=-angle)
|
||||
|
||||
return edges, sleeve_edges
|
||||
|
||||
|
||||
def ArmholeCurve(incl, width, angle, bottom_angle_mix=0, invert=True, verbose=False, **kwargs):
|
||||
""" Classic sleeve opening on Cubic Bezier curves
|
||||
"""
|
||||
# Curvature as parameters?
|
||||
cps = [[0.5, 0.2], [0.8, 0.35]]
|
||||
edge = pyg.CurveEdge([incl, width], [0, 0], cps)
|
||||
edge_as_seq = pyg.EdgeSequence(edge.reverse())
|
||||
|
||||
if not invert:
|
||||
return edge_as_seq, None
|
||||
|
||||
# Initialize inverse (initial guess)
|
||||
# Agle == 0
|
||||
down_direction = np.array([0, -1]) # Full opening is vertically aligned
|
||||
inv_cps = deepcopy(cps)
|
||||
inv_cps[-1][1] *= -1 # Invert the last
|
||||
inv_edge = pyg.CurveEdge(
|
||||
start=[incl, width],
|
||||
end=(np.array([incl, width]) + down_direction * edge._straight_len()).tolist(),
|
||||
control_points=inv_cps
|
||||
)
|
||||
|
||||
# Rotate by desired angle (usually desired sleeve rest angle)
|
||||
inv_edge.rotate(angle=-angle)
|
||||
|
||||
# Optimize the inverse shape to be nice
|
||||
shortcut = inv_edge.shortcut()
|
||||
rotated_direction = shortcut[-1] - shortcut[0]
|
||||
rotated_direction /= np.linalg.norm(rotated_direction)
|
||||
left_direction = np.array([-1, 0])
|
||||
mix_factor = bottom_angle_mix
|
||||
|
||||
dir = (1 - mix_factor) * rotated_direction + (
|
||||
mix_factor * down_direction if mix_factor > 0 else (- mix_factor * left_direction))
|
||||
|
||||
# TODOLOW Remember relative curvature results and reuse them? (speed)
|
||||
fin_inv_edge = pyg.ops.curve_match_tangents(
|
||||
inv_edge.as_curve(),
|
||||
down_direction, # Full opening is vertically aligned
|
||||
dir,
|
||||
target_len=edge.length(),
|
||||
return_as_edge=True,
|
||||
verbose=verbose
|
||||
)
|
||||
|
||||
return edge_as_seq, pyg.EdgeSequence(fin_inv_edge.reverse())
|
||||
|
||||
|
||||
# -------- New sleeve definitions -------
|
||||
|
||||
class SleevePanel(pyg.Panel):
|
||||
"""Trying proper sleeve panel"""
|
||||
|
||||
def __init__(self, name, body, design, open_shape, length_shift=0, _standing_margin=5):
|
||||
"""Define a standard sleeve panel (half a sleeve)
|
||||
* length_shift -- force upd sleeve length by this amount.
|
||||
Can be used to adjust length evaluation to fit the cuff
|
||||
"""
|
||||
super().__init__(name)
|
||||
MIN_LENGTH = 5 # Minimum sleeve length
|
||||
|
||||
shoulder_angle = np.deg2rad(body['_shoulder_incl'])
|
||||
rest_angle = max(np.deg2rad(design['sleeve_angle']['v']),
|
||||
shoulder_angle)
|
||||
standing = design['standing_shoulder']['v']
|
||||
|
||||
# Calculating extension size & end size before applying ruffles
|
||||
# Since ruffles add to pattern length & width, but not to de-facto
|
||||
# sleeve length in 3D
|
||||
end_width = design['end_width']['v'] * abs(open_shape[0].start[1] - open_shape[-1].end[1])
|
||||
# Ensure it fits regardless of parameters
|
||||
end_width = max(end_width, body['wrist'] / 2)
|
||||
|
||||
# Ruffles at opening
|
||||
if not pyg.utils.close_enough(design['connect_ruffle']['v'], 1):
|
||||
open_shape.extend(design['connect_ruffle']['v'])
|
||||
|
||||
# -- Main body of a sleeve --
|
||||
opening_length = abs(open_shape[0].start[0] - open_shape[-1].end[0])
|
||||
arm_width = abs(open_shape[0].start[1] - open_shape[-1].end[1])
|
||||
# Length from the border of the opening to the end of the sleeve
|
||||
length = design['length']['v'] * (body['arm_length'] - opening_length)
|
||||
# NOTE: Asked to reduce by too much: reduce as much as possible
|
||||
length = max(length + length_shift, MIN_LENGTH)
|
||||
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0], [0, -end_width], [length, -arm_width]
|
||||
)
|
||||
|
||||
# Align the opening
|
||||
open_shape.snap_to(self.edges[-1].end)
|
||||
open_shape[0].start = self.edges[-1].end # chain
|
||||
self.edges.append(open_shape)
|
||||
# Fin
|
||||
self.edges.close_loop()
|
||||
|
||||
if standing:
|
||||
if rest_angle > (shoulder_angle + np.deg2rad(_standing_margin)): # Add a "shelve" to create square shoulder appearance
|
||||
top_edge = self.edges[-1]
|
||||
start = top_edge.start
|
||||
len = design['standing_shoulder_len']['v']
|
||||
|
||||
x_shift = len * np.cos(rest_angle - shoulder_angle)
|
||||
y_shift = len * np.sin(rest_angle - shoulder_angle)
|
||||
|
||||
standing_edge = pyg.Edge(
|
||||
start=start,
|
||||
end=[start[0] - x_shift, start[1] + y_shift]
|
||||
)
|
||||
top_edge.start = standing_edge.end
|
||||
|
||||
self.edges.substitute(top_edge, [standing_edge, top_edge])
|
||||
else:
|
||||
if self.verbose:
|
||||
print(f'{self.__class__.__name__}::WARNING::'
|
||||
f'Sleeve rest angle {np.rad2deg(rest_angle):.3f} should be '
|
||||
f'larger than shoulder angle {body["_shoulder_incl"]} by '
|
||||
f'at least {_standing_margin} deg to enable '
|
||||
'standing shoulder. Standing shoulder ignored')
|
||||
standing = False
|
||||
|
||||
# Interfaces
|
||||
self.interfaces = {
|
||||
# NOTE: interface needs reversing because the open_shape was reversed for construction
|
||||
'in': pyg.Interface(self, open_shape, ruffle=design['connect_ruffle']['v']),
|
||||
'out': pyg.Interface(self, self.edges[0], ruffle=design['cuff']['top_ruffle']['v']),
|
||||
'top': pyg.Interface(self, self.edges[-2:] if standing else self.edges[-1]),
|
||||
'bottom': pyg.Interface(self, self.edges[1])
|
||||
}
|
||||
|
||||
# Default placement
|
||||
self.set_pivot(self.edges[-1].start)
|
||||
self.translate_to([
|
||||
- body['shoulder_w'] / 2,
|
||||
body['height'] - body['head_l'],
|
||||
0,
|
||||
])
|
||||
|
||||
def length(self, longest_dim=False):
|
||||
return self.interfaces['bottom'].edges.length()
|
||||
|
||||
|
||||
class Sleeve(pyg.Component):
|
||||
"""Trying to do a proper sleeve"""
|
||||
def __init__(self, tag, body, design, front_w, back_w):
|
||||
"""Defintion of a sleeve:
|
||||
* front_w, back_w: the width front and the back of the top
|
||||
the sleeve will attach to -- needed for correct share calculations
|
||||
They may be
|
||||
* Specified as scalar numbers
|
||||
* Specified as functions w.r.t. the requested vertical level (=>
|
||||
calculated width of a horizontal slice)
|
||||
"""
|
||||
super().__init__(f'{self.__class__.__name__}_{tag}')
|
||||
|
||||
design = design['sleeve']
|
||||
self.design = design
|
||||
self.body = body
|
||||
|
||||
sleeve_balance = body['_base_sleeve_balance'] / 2
|
||||
|
||||
rest_angle = max(np.deg2rad(design['sleeve_angle']['v']),
|
||||
np.deg2rad(body['_shoulder_incl']))
|
||||
|
||||
connecting_width = design['connecting_width']['v']
|
||||
smoothing_coeff = design['smoothing_coeff']['v']
|
||||
|
||||
front_w = front_w(connecting_width) if callable(front_w) else front_w
|
||||
back_w = back_w(connecting_width) if callable(back_w) else back_w
|
||||
|
||||
# --- Define sleeve opening shapes ----
|
||||
# NOTE: Non-trad armholes only for sleeveless styles due to
|
||||
# unclear inversion and stitching errors (see below)
|
||||
armhole = globals()[design['armhole_shape']['v']] if design['sleeveless']['v'] else ArmholeCurve
|
||||
front_project, front_opening = armhole(
|
||||
front_w - sleeve_balance,
|
||||
connecting_width,
|
||||
angle=rest_angle,
|
||||
incl_coeff=smoothing_coeff,
|
||||
w_coeff=smoothing_coeff,
|
||||
invert=not design['sleeveless']['v'],
|
||||
bottom_angle_mix=design['opening_dir_mix']['v'],
|
||||
verbose=self.verbose
|
||||
)
|
||||
back_project, back_opening = armhole(
|
||||
back_w - sleeve_balance,
|
||||
connecting_width,
|
||||
angle=rest_angle,
|
||||
incl_coeff=smoothing_coeff,
|
||||
w_coeff=smoothing_coeff,
|
||||
invert=not design['sleeveless']['v'],
|
||||
bottom_angle_mix=design['opening_dir_mix']['v']
|
||||
)
|
||||
|
||||
self.interfaces = {
|
||||
'in_front_shape': pyg.Interface(self, front_project),
|
||||
'in_back_shape': pyg.Interface(self, back_project)
|
||||
}
|
||||
|
||||
if design['sleeveless']['v']:
|
||||
# The rest is not needed!
|
||||
return
|
||||
|
||||
if front_w != back_w:
|
||||
front_opening, back_opening = pyg.ops.even_armhole_openings(
|
||||
front_opening, back_opening,
|
||||
tol=0.2 / front_opening.length(), # ~2mm tolerance as a fraction of length
|
||||
verbose=self.verbose
|
||||
)
|
||||
|
||||
# --- Eval length adjustment for cuffs (if any) ----
|
||||
cuff_len_adj = self._cuff_len_adj()
|
||||
|
||||
# # ----- Get sleeve panels -------
|
||||
self.f_sleeve = SleevePanel(
|
||||
f'{tag}_sleeve_f', body, design, front_opening,
|
||||
length_shift=-cuff_len_adj
|
||||
).translate_by([5, 0, 15])
|
||||
# self.f_sleeve = SleevePanel(
|
||||
# f'{tag}_sleeve_f', body, design, front_opening,
|
||||
# length_shift=-cuff_len_adj
|
||||
# )
|
||||
self.b_sleeve = SleevePanel(
|
||||
f'{tag}_sleeve_b', body, design, back_opening,
|
||||
length_shift=-cuff_len_adj
|
||||
).translate_by([5, 0, -15])
|
||||
# self.b_sleeve = SleevePanel(
|
||||
# f'{tag}_sleeve_b', body, design, back_opening,
|
||||
# length_shift=-cuff_len_adj
|
||||
# )
|
||||
|
||||
# Connect panels
|
||||
self.stitching_rules = pyg.Stitches(
|
||||
(self.f_sleeve.interfaces['top'],
|
||||
self.b_sleeve.interfaces['top']),
|
||||
(self.f_sleeve.interfaces['bottom'],
|
||||
self.b_sleeve.interfaces['bottom']),
|
||||
)
|
||||
|
||||
# Interfaces
|
||||
self.interfaces.update({
|
||||
'in': pyg.Interface.from_multiple(
|
||||
self.f_sleeve.interfaces['in'],
|
||||
self.b_sleeve.interfaces['in'].reverse(with_edge_dir_reverse=True)
|
||||
),
|
||||
'out': pyg.Interface.from_multiple(
|
||||
self.f_sleeve.interfaces['out'],
|
||||
self.b_sleeve.interfaces['out']
|
||||
),
|
||||
})
|
||||
|
||||
# Cuff
|
||||
if design['cuff']['type']['v']:
|
||||
# Class
|
||||
# Copy to avoid editing original design dict
|
||||
cdesign = deepcopy(design)
|
||||
cuff_circ = self.interfaces['out'].edges.length() / design['cuff']['top_ruffle']['v']
|
||||
# Ensure it fits regardless of parameters
|
||||
cuff_circ = max(cuff_circ, body['wrist'])
|
||||
cdesign['cuff']['b_width'] = dict(v=cuff_circ)
|
||||
cdesign['cuff']['cuff_len']['v'] = cuff_len_adj
|
||||
|
||||
cuff_class = getattr(bands, cdesign['cuff']['type']['v'])
|
||||
self.cuff = cuff_class(f'sl_{tag}', cdesign)
|
||||
|
||||
# Position
|
||||
self.cuff.rotate_by(
|
||||
R.from_euler(
|
||||
'XYZ',
|
||||
[0, 0, -90], # from -Ox direction
|
||||
degrees=True
|
||||
)
|
||||
)
|
||||
self.cuff.place_by_interface(
|
||||
self.cuff.interfaces['top'],
|
||||
self.interfaces['out'],
|
||||
gap=2,
|
||||
alignment='top'
|
||||
)
|
||||
|
||||
self.stitching_rules.append(
|
||||
(
|
||||
self.cuff.interfaces['top'],
|
||||
self.interfaces['out']
|
||||
)
|
||||
)
|
||||
|
||||
# UPD out interface!
|
||||
self.interfaces['out'] = self.cuff.interfaces['bottom']
|
||||
|
||||
# Final rotation of sleeve piece
|
||||
# print('*** arm_pose_angle', body['arm_pose_angle'])
|
||||
self.rotate_by(R.from_euler(
|
||||
'XYZ', [0, 0, body['arm_pose_angle']], degrees=True))
|
||||
|
||||
# Set label
|
||||
self.set_panel_label('arm')
|
||||
|
||||
def _cuff_len_adj(self):
|
||||
"""Eval sleeve length adjustment due to cuffs (if any)"""
|
||||
if not self.design['cuff']['type']['v']:
|
||||
return 0
|
||||
|
||||
cuff_len_adj = self.design['cuff']['cuff_len']['v'] * self.body['arm_length']
|
||||
max_len = self.design['length']['v'] * self.body['arm_length']
|
||||
if cuff_len_adj > max_len * 0.7:
|
||||
cuff_len_adj = max_len * 0.7
|
||||
|
||||
return cuff_len_adj
|
||||
|
||||
def length(self):
|
||||
if self.design['sleeveless']['v']:
|
||||
return 0
|
||||
|
||||
if self.design['cuff']['type']['v']:
|
||||
return self.f_sleeve.length() + self.cuff.length()
|
||||
|
||||
return self.f_sleeve.length()
|
||||
150
assets/garment_programs/stats_utils.py
Normal file
150
assets/garment_programs/stats_utils.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Design analysis routines to supply intresting stats"""
|
||||
|
||||
import pygarment.pattern.core as pattern
|
||||
import yaml
|
||||
|
||||
# panels
|
||||
def count_panels(pattern:pattern.BasicPattern, props, verbose=False):
|
||||
|
||||
n_panels = len(pattern.pattern['panels'].keys())
|
||||
if verbose:
|
||||
print(pattern.name, ' Panel count ', n_panels)
|
||||
|
||||
props['generator']['stats']['panel_count'][pattern.name] = n_panels
|
||||
|
||||
|
||||
# Type determination
|
||||
|
||||
def bottom_length(design):
|
||||
meta = design['meta']
|
||||
if meta['bottom']['v']:
|
||||
bottom_section = None
|
||||
if meta['bottom']['v'] in ['SkirtCircle', 'AsymmSkirtCircle', 'SkirtManyPanels']:
|
||||
bottom_section = 'flare-skirt'
|
||||
elif meta['bottom']['v'] == 'Pants':
|
||||
bottom_section = 'pants'
|
||||
elif meta['bottom']['v'] == 'Skirt2':
|
||||
bottom_section = 'skirt'
|
||||
elif meta['bottom']['v'] == 'PencilSkirt':
|
||||
bottom_section = 'pencil-skirt'
|
||||
elif meta['bottom']['v'] == 'SkirtLevels':
|
||||
bottom_section = 'levels-skirt'
|
||||
elif meta['bottom']['v'] == 'GodetSkirt':
|
||||
base = design['godet-skirt']['base']['v']
|
||||
if base == 'Skirt2':
|
||||
bottom_section = 'skirt'
|
||||
else: # One other option
|
||||
bottom_section = 'pencil-skirt'
|
||||
else:
|
||||
raise(ValueError(f'Unknown bottoms type {meta["bottom"]["v"]}'))
|
||||
|
||||
return design[bottom_section]['length']['v']
|
||||
else:
|
||||
return 0
|
||||
|
||||
def sleeve_length(design):
|
||||
# Sleeve length
|
||||
sleeve_len_r = design['sleeve']['length']['v'] if not design['sleeve']['sleeveless']['v'] else 0
|
||||
sleeve_len_l = design['left']['sleeve']['length']['v'] if design['left']['enable_asym']['v'] and not design['left']['sleeve']['sleeveless']['v'] else 0
|
||||
return max(sleeve_len_r, sleeve_len_l)
|
||||
|
||||
def top_length(design):
|
||||
if design['meta']['upper']['v'] == 'FittedShirt':
|
||||
return 1.
|
||||
elif design['meta']['upper']['v'] == 'Shirt':
|
||||
return design['shirt']['length']['v']
|
||||
else:
|
||||
return 0.
|
||||
|
||||
def vertical_len(design):
|
||||
# NOTE: this will give very approximate result since
|
||||
# the units of mesurement are slightly different
|
||||
wb_len = design['waistband']['width']['v'] if design['meta']['wb']['v'] else 0
|
||||
return top_length(design) + wb_len + bottom_length(design)
|
||||
|
||||
def garment_type(el_name, design, props, verbose=False):
|
||||
main_type = None
|
||||
add_types = []
|
||||
# Main:
|
||||
# + upper garment (short skirt - top or dress?)
|
||||
# + skirt
|
||||
# + pants
|
||||
# + dress
|
||||
# + jumpsuit
|
||||
# Additional labels:
|
||||
# + asymmetrical top
|
||||
# + Hoody?
|
||||
# + maxi/midi/mini
|
||||
# + sleeve/less
|
||||
# + long sleeve / short sleeve?
|
||||
meta = design['meta']
|
||||
if meta['upper']['v']:
|
||||
if meta['bottom']['v'] and 'Pants' in meta['bottom']['v']:
|
||||
main_type = 'jumpsuit'
|
||||
elif vertical_len(design) < 1.4: # NOTE: very approximate division
|
||||
main_type = 'upper_garment'
|
||||
else:
|
||||
main_type = 'dress'
|
||||
else:
|
||||
if 'Pants' in meta['bottom']['v']:
|
||||
main_type = 'pants'
|
||||
else:
|
||||
main_type = 'skirt'
|
||||
|
||||
# Additional types
|
||||
if meta['upper']['v']:
|
||||
if design['left']['enable_asym']['v']:
|
||||
add_types.append('asymmetric_top')
|
||||
if (not design['left']['enable_asym']['v']
|
||||
and design['collar']['component']['style']['v']
|
||||
and 'Hood' in design['collar']['component']['style']['v']):
|
||||
add_types.append('hoodie')
|
||||
|
||||
if (design['sleeve']['sleeveless']['v']
|
||||
and (design['left']['sleeve']['sleeveless']['v'] if design['left']['enable_asym']['v'] else True)):
|
||||
add_types.append('sleeveless')
|
||||
else:
|
||||
add_types.append('with_sleeves')
|
||||
|
||||
sleeve_len = sleeve_length(design)
|
||||
if sleeve_len < 0.5:
|
||||
add_types.append('short_sleeve')
|
||||
else:
|
||||
add_types.append('long_sleeve')
|
||||
|
||||
# Mini/Midi/Maxi
|
||||
if meta['bottom']['v']:
|
||||
length = bottom_length(design)
|
||||
if length < 0.3:
|
||||
add_types.append('mini')
|
||||
elif length < 0.5:
|
||||
add_types.append('knee_len')
|
||||
elif length < 0.65:
|
||||
add_types.append('midi')
|
||||
else:
|
||||
add_types.append('maxi')
|
||||
|
||||
|
||||
if verbose:
|
||||
print(el_name, ' types ', main_type, add_types)
|
||||
|
||||
props['generator']['stats']['garment_types'][el_name] = {
|
||||
'main': main_type,
|
||||
'styles': add_types
|
||||
}
|
||||
|
||||
# Summary
|
||||
summary = props['generator']['stats']['garment_types_summary']
|
||||
if main_type not in summary['main']:
|
||||
summary['main'][main_type] = 1
|
||||
else:
|
||||
summary['main'][main_type] += 1
|
||||
for style_t in add_types:
|
||||
if style_t not in summary['style']:
|
||||
summary['style'][style_t] = {'total': 0}
|
||||
summary['style'][style_t]['total'] += 1
|
||||
if main_type not in summary['style'][style_t]:
|
||||
summary['style'][style_t][main_type] = 1
|
||||
else:
|
||||
summary['style'][style_t][main_type] += 1
|
||||
|
||||
116
assets/garment_programs/tee.py
Normal file
116
assets/garment_programs/tee.py
Normal file
@@ -0,0 +1,116 @@
|
||||
""" Panels for a straight upper garment (T-shirt)
|
||||
Note that the code is very similar to Bodice.
|
||||
"""
|
||||
import numpy as np
|
||||
import pygarment as pyg
|
||||
|
||||
from assets.garment_programs.base_classes import BaseBodicePanel
|
||||
|
||||
|
||||
class TorsoFrontHalfPanel(BaseBodicePanel):
|
||||
"""Half of a simple non-fitted upper garment (e.g. T-Shirt)
|
||||
|
||||
Fits to the bust size
|
||||
"""
|
||||
def __init__(self, name, body, design) -> None:
|
||||
""" Front = True, provides the adjustments necessary for the front panel
|
||||
"""
|
||||
super().__init__(name, body, design)
|
||||
|
||||
design = design['shirt']
|
||||
|
||||
# width
|
||||
m_width = design['width']['v'] * body['bust']
|
||||
b_width = design['flare']['v'] * m_width
|
||||
|
||||
# sizes
|
||||
body_width = (body['bust'] - body['back_width']) / 2
|
||||
frac = body_width / body['bust']
|
||||
self.width = frac * m_width
|
||||
b_width = frac * b_width
|
||||
|
||||
sh_tan = np.tan(np.deg2rad(body['_shoulder_incl']))
|
||||
shoulder_incl = sh_tan * self.width
|
||||
length = design['length']['v'] * body['waist_line']
|
||||
|
||||
# length in the front panel is adjusted due to shoulder inclination
|
||||
# for the correct sleeve fitting
|
||||
fb_diff = (frac - (0.5 - frac)) * body['bust']
|
||||
length = length - sh_tan * fb_diff
|
||||
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0],
|
||||
[-b_width, 0],
|
||||
[-self.width, length],
|
||||
[0, length + shoulder_incl],
|
||||
loop=True
|
||||
)
|
||||
|
||||
# Interfaces
|
||||
self.interfaces = {
|
||||
'outside': pyg.Interface(self, self.edges[1]),
|
||||
'inside': pyg.Interface(self, self.edges[-1]),
|
||||
'shoulder': pyg.Interface(self, self.edges[-2]),
|
||||
'bottom': pyg.Interface(self, self.edges[0], ruffle=self.width / ((body['waist'] - body['waist_back_width']) / 2)),
|
||||
|
||||
# Reference to the corner for sleeve and collar projections
|
||||
'shoulder_corner': pyg.Interface(self, [self.edges[-3], self.edges[-2]]),
|
||||
'collar_corner': pyg.Interface(self, [self.edges[-2], self.edges[-1]])
|
||||
}
|
||||
|
||||
# default placement
|
||||
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
|
||||
|
||||
def get_width(self, level):
|
||||
return super().get_width(level) + self.width - self.body['shoulder_w'] / 2
|
||||
|
||||
|
||||
class TorsoBackHalfPanel(BaseBodicePanel):
|
||||
"""Half of a simple non-fitted upper garment (e.g. T-Shirt)
|
||||
|
||||
Fits to the bust size
|
||||
"""
|
||||
def __init__(self, name, body, design) -> None:
|
||||
""" Front = True, provides the adjustments necessary for the front panel
|
||||
"""
|
||||
super().__init__(name, body, design)
|
||||
|
||||
design = design['shirt']
|
||||
# account for ease in basic measurements
|
||||
m_width = design['width']['v'] * body['bust']
|
||||
b_width = design['flare']['v'] * m_width
|
||||
|
||||
# sizes
|
||||
body_width = body['back_width'] / 2
|
||||
frac = body_width / body['bust']
|
||||
self.width = frac * m_width
|
||||
b_width = frac * b_width
|
||||
|
||||
shoulder_incl = (np.tan(np.deg2rad(body['_shoulder_incl']))) * self.width
|
||||
length = design['length']['v'] * body['waist_line']
|
||||
|
||||
self.edges = pyg.EdgeSeqFactory.from_verts(
|
||||
[0, 0],
|
||||
[-b_width, 0],
|
||||
[-self.width, length],
|
||||
[0, length + shoulder_incl],
|
||||
loop=True
|
||||
)
|
||||
|
||||
# Interfaces
|
||||
self.interfaces = {
|
||||
'outside': pyg.Interface(self, self.edges[1]),
|
||||
'inside': pyg.Interface(self, self.edges[-1]),
|
||||
'shoulder': pyg.Interface(self, self.edges[-2]),
|
||||
'bottom': pyg.Interface(self, self.edges[0], ruffle=self.width / (body['waist_back_width'] / 2)),
|
||||
|
||||
# Reference to the corner for sleeve and collar projections
|
||||
'shoulder_corner': pyg.Interface(self, [self.edges[-3], self.edges[-2]]),
|
||||
'collar_corner': pyg.Interface(self, [self.edges[-2], self.edges[-1]])
|
||||
}
|
||||
|
||||
# default placement
|
||||
self.translate_by([0, body['height'] - body['head_l'] - length - shoulder_incl, 0])
|
||||
|
||||
def get_width(self, level):
|
||||
return super().get_width(level) + self.width - self.body['shoulder_w'] / 2
|
||||
Reference in New Issue
Block a user