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