Files

313 lines
12 KiB
Python
Raw Permalink Normal View History

2025-07-03 17:03:00 +08:00
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()