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()