vikashmakeit commited on
Commit
7690037
·
verified ·
1 Parent(s): e6366e1

Add 3D garment visualizer (Plotly)

Browse files
Files changed (1) hide show
  1. garment_3d.py +212 -0
garment_3d.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 3D Garment Visualizer
3
+ Generates a 3D garment mesh on a parametric body using Plotly.
4
+ """
5
+ import numpy as np
6
+ import plotly.graph_objects as go
7
+ from typing import Dict, Optional
8
+
9
+ def create_body_mesh(height=170, chest=88, waist=74, hip=96):
10
+ theta = np.linspace(0, 2*np.pi, 30)
11
+ z_levels = np.linspace(0, height, 40)
12
+ vertices = []
13
+ faces = []
14
+ for i, z in enumerate(z_levels):
15
+ t = z / height
16
+ if t < 0.12:
17
+ r = 8 + t * 40
18
+ elif t < 0.20:
19
+ r = chest/2 * 0.3 + (t-0.12)/0.08 * (chest/2*0.7-chest/2*0.3)
20
+ elif t < 0.45:
21
+ frac = (t-0.20)/0.25
22
+ r = chest/2 * (1 - 0.08*frac)
23
+ elif t < 0.55:
24
+ r = waist/2
25
+ elif t < 0.72:
26
+ frac = (t-0.55)/0.17
27
+ r = waist/2 + (hip/2 - waist/2) * frac
28
+ else:
29
+ frac = (t-0.72)/0.28
30
+ r = hip/2 * (1 - 0.5*frac)
31
+ for j, th in enumerate(theta):
32
+ x = r * np.cos(th) * 0.85
33
+ y = r * np.sin(th) * 0.5
34
+ vertices.append([x, y, z])
35
+ vertices = np.array(vertices)
36
+ n_theta = len(theta)
37
+ for i in range(len(z_levels)-1):
38
+ for j in range(n_theta-1):
39
+ v0 = i*n_theta + j
40
+ v1 = i*n_theta + j+1
41
+ v2 = (i+1)*n_theta + j
42
+ v3 = (i+1)*n_theta + j+1
43
+ faces.append([v0, v2, v1])
44
+ faces.append([v1, v2, v3])
45
+ return vertices, faces
46
+
47
+ def _garment_mesh_shirt(params, body_verts, n_theta, z_levels):
48
+ parts = []
49
+ bust = params.get('bust', 92)/2
50
+ waist = params.get('waist', 74)/2
51
+ shoulder = params.get('shoulder_width', 42)/2
52
+ body_len = params.get('bodice_length', 72)
53
+ sleeve_len = params.get('sleeve_length', 60)
54
+ sleeve_lo = params.get('bicep', 30)/2 + 2
55
+ sleeve_hi = params.get('wrist', 18)/2 + 1
56
+ # Front panel
57
+ front_z = np.linspace(0, body_len, 20)
58
+ front_x = []
59
+ front_y = []
60
+ for z in front_z:
61
+ t = z / body_len
62
+ if t < 0.3:
63
+ x = shoulder * (1 - 0.1*t)
64
+ elif t < 0.55:
65
+ x = shoulder - (shoulder - bust*0.85) * ((t-0.3)/0.25)
66
+ else:
67
+ x = bust*0.85 - (bust*0.85 - waist*0.9) * ((t-0.55)/0.45)
68
+ front_x.append(x)
69
+ front_y.append(0)
70
+ pts = list(zip(front_x, front_y, front_z))
71
+ pts.append((0, 0, front_z[-1]))
72
+ pts.insert(0, (0, 0, 0))
73
+ n = len(pts)
74
+ xs = [p[0] for p in pts]
75
+ ys = [p[1] for p in pts]
76
+ zs = [p[2] for p in pts]
77
+ parts.append(dict(x=xs, y=ys, z=zs, type='scatter3d', mode='lines',
78
+ line=dict(color='#4A90D9', width=4), name='Front'))
79
+ # Back panel (mirror x)
80
+ back_x = [-x for x in xs]
81
+ parts.append(dict(x=back_x, y=ys, z=zs, type='scatter3d', mode='lines',
82
+ line=dict(color='#6BB3E0', width=4), name='Back'))
83
+ # Sleeves
84
+ shoulder_pt = [shoulder*0.9, 0, body_len*0.92]
85
+ sl_z = np.linspace(0, sleeve_len, 10)
86
+ for side in [1, -1]:
87
+ sx = [side * (shoulder*0.9 + (sleeve_lo - shoulder*0.3)*0.3 + sl*0.08*side) for sl in sl_z]
88
+ sy = [sl*0.6 for sl in sl_z]
89
+ sz = [body_len*0.92 - sl*0.15 for sl in sl_z]
90
+ color = '#7BC8F6' if side > 0 else '#5BA3D9'
91
+ parts.append(dict(x=sx, y=sy, z=sz, type='scatter3d', mode='lines',
92
+ line=dict(color=color, width=4), name='Sleeve R' if side > 0 else 'Sleeve L'))
93
+ return parts
94
+
95
+ def _garment_mesh_pants(params, body_verts, n_theta, z_levels):
96
+ parts = []
97
+ waist_r = params.get('waist', 74)/2
98
+ hip_r = params.get('hip', 96)/2
99
+ pant_len = params.get('pant_length', 100)
100
+ crotch = params.get('crotch_depth', 27)
101
+ ankle = params.get('ankle', 24)/2 + 1
102
+ body_h = 170
103
+ for side in [1, -1]:
104
+ leg_x = [side * waist_r*0.5, side * hip_r*0.7, side * hip_r*0.9, side * ankle*1.5, side * ankle]
105
+ leg_y = [0, 0, 0, 0, 0]
106
+ leg_z = [body_h, body_h*0.6, body_h*0.55, body_h*0.55 - (pant_len-crotch)*0.5, body_h*0.55 - pant_len*0.7]
107
+ parts.append(dict(x=leg_x, y=leg_y, z=leg_z, type='scatter3d', mode='lines',
108
+ line=dict(color='#2C5F8A', width=5), name='Leg R' if side > 0 else 'Leg L'))
109
+ return parts
110
+
111
+ def _garment_mesh_skirt(params, body_verts, n_theta, z_levels):
112
+ parts = []
113
+ waist_r = params.get('waist', 74)/2
114
+ hip_r = params.get('hip', 96)/2
115
+ skirt_len = params.get('skirt_length', 55)
116
+ flare = params.get('flare', 5)
117
+ body_h = 170
118
+ waist_z = body_h * 0.58
119
+ hem_z = waist_z - skirt_len
120
+ hem_r = hip_r + flare
121
+ theta = np.linspace(0, 2*np.pi, 40)
122
+ for t_val, r, z_val, c, nm in [(0, waist_r, waist_z, '#D4A5C9', 'Waist'), (1, hip_r, waist_z - 20, '#C48BB8', 'Hip'), (2, hem_r, hem_z, '#E8C5E0', 'Hem')]:
123
+ xs = [r * np.cos(th)*0.85 for th in theta]
124
+ ys = [r * np.sin(th)*0.5 for th in theta]
125
+ zs = [z_val]*len(theta)
126
+ parts.append(dict(x=xs, y=ys, z=zs, type='scatter3d', mode='lines',
127
+ line=dict(color=c, width=3), name=nm))
128
+ return parts
129
+
130
+ def _garment_mesh_dress(params, body_verts, n_theta, z_levels):
131
+ parts = _garment_mesh_shirt(params, body_verts, n_theta, z_levels)
132
+ parts += _garment_mesh_skirt(params, body_verts, n_theta, z_levels)
133
+ return parts
134
+
135
+ def _garment_mesh_hoodie(params, body_verts, n_theta, z_levels):
136
+ parts = _garment_mesh_shirt(params, body_verts, n_theta, z_levels)
137
+ head_circ = params.get('head_circumference', 57)/2
138
+ body_len = params.get('bodice_length', 65)
139
+ body_h = 170
140
+ hood_z = [body_h*0.9, body_h*0.95, body_h*1.02, body_h*1.05, body_h*1.02, body_h*0.95]
141
+ hood_x = [head_circ*0.8, head_circ, head_circ*0.7, 0, -head_circ*0.7, -head_circ]
142
+ hood_y = [0, head_circ*0.3, head_circ*0.5, head_circ*0.6, head_circ*0.5, head_circ*0.3]
143
+ parts.append(dict(x=hood_x, y=hood_y, z=hood_z, type='scatter3d', mode='lines',
144
+ line=dict(color='#3A7BD5', width=5), name='Hood'))
145
+ return parts
146
+
147
+ def _garment_mesh_jacket(params, body_verts, n_theta, z_levels):
148
+ return _garment_mesh_shirt(params, body_verts, n_theta, z_levels)
149
+
150
+ def _garment_mesh_default(params, body_verts, n_theta, z_levels):
151
+ return _garment_mesh_shirt(params, body_verts, n_theta, z_levels)
152
+
153
+ GARMENT_MESH_FUNCS = {
154
+ 'shirt': _garment_mesh_shirt,
155
+ 'blouse': _garment_mesh_shirt,
156
+ 'top': _garment_mesh_shirt,
157
+ 't-shirt': _garment_mesh_shirt,
158
+ 'tee': _garment_mesh_shirt,
159
+ 'dress': _garment_mesh_dress,
160
+ 'skirt': _garment_mesh_skirt,
161
+ 'pants': _garment_mesh_pants,
162
+ 'trousers': _garment_mesh_pants,
163
+ 'jeans': _garment_mesh_pants,
164
+ 'jacket': _garment_mesh_jacket,
165
+ 'coat': _garment_mesh_jacket,
166
+ 'blazer': _garment_mesh_jacket,
167
+ 'hoodie': _garment_mesh_hoodie,
168
+ 'sweatshirt': _garment_mesh_hoodie,
169
+ 'vest': _garment_mesh_shirt,
170
+ }
171
+
172
+ def create_3d_figure(analysis: Dict) -> go.Figure:
173
+ """Create interactive 3D garment visualization."""
174
+ garment_type = analysis.get('garment_type', 'shirt').lower()
175
+ measurements = analysis.get('measurements', {})
176
+ features = analysis.get('features', {})
177
+ params = {**measurements, **features}
178
+ chest = params.get('bust', 92)
179
+ waist_m = params.get('waist', 74)
180
+ hip_m = params.get('hip', 96)
181
+ body_v, body_f = create_body_mesh(chest=chest, waist=waist_m, hip=hip_m)
182
+ fig = go.Figure()
183
+ # Body wireframe
184
+ n_t = 30
185
+ for i in range(0, len(body_v), n_t*3):
186
+ row = body_v[i:i+n_t]
187
+ fig.add_trace(go.Scatter3d(
188
+ x=row[:,0], y=row[:,1], z=row[:,2],
189
+ mode='lines', line=dict(color='#DDCCAA', width=1),
190
+ showlegend=False, hoverinfo='skip'))
191
+ # Garment
192
+ mesh_func = GARMENT_MESH_FUNCS.get(garment_type, _garment_mesh_default)
193
+ garment_traces = mesh_func(params, body_v, n_t, np.linspace(0, 170, 40))
194
+ for trace in garment_traces:
195
+ fig.add_trace(go.Scatter3d(
196
+ x=trace['x'], y=trace['y'], z=trace['z'],
197
+ mode=trace.get('mode', 'lines'),
198
+ line=trace.get('line', dict(width=3)),
199
+ name=trace.get('name', '')))
200
+ fig.update_layout(
201
+ scene=dict(
202
+ xaxis=dict(showgrid=False, showticklabels=False, title=''),
203
+ yaxis=dict(showgrid=False, showticklabels=False, title=''),
204
+ zaxis=dict(showgrid=False, showticklabels=False, title=''),
205
+ aspectmode='data',
206
+ camera=dict(eye=dict(x=1.5, y=1.5, z=0.8))),
207
+ margin=dict(l=0, r=0, t=30, b=0),
208
+ height=500,
209
+ title=dict(text=f'3D Preview: {garment_type.title()}', font=dict(size=14)),
210
+ paper_bgcolor='#fafafa',
211
+ )
212
+ return fig