Spaces:
Running
Running
Rewrite 3D visualizer with proper surface meshes (not wireframes)
Browse files- garment_3d.py +247 -181
garment_3d.py
CHANGED
|
@@ -1,212 +1,278 @@
|
|
| 1 |
"""
|
| 2 |
-
3D Garment Visualizer
|
| 3 |
-
|
| 4 |
"""
|
| 5 |
import numpy as np
|
| 6 |
import plotly.graph_objects as go
|
| 7 |
-
from typing import Dict
|
| 8 |
|
| 9 |
-
def
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
elif t < 0.55:
|
| 24 |
-
|
|
|
|
| 25 |
elif t < 0.72:
|
| 26 |
-
|
| 27 |
-
r = waist/2 + (hip/2 - waist/2)
|
| 28 |
else:
|
| 29 |
-
|
| 30 |
-
r = hip/2 * (1 - 0.
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 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
|
| 48 |
-
|
| 49 |
-
bust = params.get(
|
| 50 |
-
waist = params.get(
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
elif t < 0.55:
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
else:
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 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 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
body_h = 170
|
|
|
|
|
|
|
|
|
|
| 103 |
for side in [1, -1]:
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
-
def
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
skirt_len = params.get(
|
| 116 |
-
flare = params.get(
|
| 117 |
body_h = 170
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
waist_z = body_h * 0.58
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 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
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
body_h = 170
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
return parts
|
| 146 |
|
| 147 |
-
def
|
| 148 |
-
return
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 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(
|
| 175 |
-
measurements = analysis.get(
|
| 176 |
-
features = analysis.get(
|
| 177 |
params = {**measurements, **features}
|
| 178 |
-
chest = params.get(
|
| 179 |
-
waist_m = params.get(
|
| 180 |
-
hip_m = params.get(
|
| 181 |
-
body_v, body_f = create_body_mesh(chest=chest, waist=waist_m, hip=hip_m)
|
| 182 |
fig = go.Figure()
|
| 183 |
-
# Body
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
# Garment
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
for
|
| 195 |
-
fig.add_trace(go.
|
| 196 |
-
x=
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
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=
|
| 206 |
-
camera=dict(eye=dict(x=1.
|
| 207 |
margin=dict(l=0, r=0, t=30, b=0),
|
| 208 |
height=500,
|
| 209 |
-
title=dict(text=f
|
| 210 |
-
paper_bgcolor=
|
| 211 |
-
|
| 212 |
-
return fig
|
|
|
|
| 1 |
"""
|
| 2 |
+
3D Garment Visualizer - Surface Mesh Approach
|
| 3 |
+
Creates realistic 3D garment surfaces on a parametric body using Plotly.
|
| 4 |
"""
|
| 5 |
import numpy as np
|
| 6 |
import plotly.graph_objects as go
|
| 7 |
+
from typing import Dict
|
| 8 |
|
| 9 |
+
def _make_body_surface(height=170, chest=88, waist=74, hip=96):
|
| 10 |
+
"""Create a parametric human body as a mesh surface."""
|
| 11 |
+
n_theta = 36
|
| 12 |
+
n_z = 50
|
| 13 |
+
theta = np.linspace(0, 2*np.pi, n_theta)
|
| 14 |
+
z = np.linspace(0, height, n_z)
|
| 15 |
+
Theta, Z = np.meshgrid(theta, z)
|
| 16 |
+
R = np.zeros_like(Z)
|
| 17 |
+
for i in range(n_z):
|
| 18 |
+
t = Z[i,0] / height
|
| 19 |
+
if t < 0.08:
|
| 20 |
+
r = 6 + t*50
|
| 21 |
+
elif t < 0.15:
|
| 22 |
+
f = (t-0.08)/0.07
|
| 23 |
+
r = 10 + (chest/2*0.5 - 10)*f
|
| 24 |
+
elif t < 0.44:
|
| 25 |
+
f = (t-0.15)/0.29
|
| 26 |
+
r = chest/2 * (1 - 0.05*f)
|
| 27 |
elif t < 0.55:
|
| 28 |
+
f = (t-0.44)/0.11
|
| 29 |
+
r = chest/2*0.95 - (chest/2*0.95 - waist/2)*f
|
| 30 |
elif t < 0.72:
|
| 31 |
+
f = (t-0.55)/0.17
|
| 32 |
+
r = waist/2 + (hip/2 - waist/2)*f
|
| 33 |
else:
|
| 34 |
+
f = (t-0.72)/0.28
|
| 35 |
+
r = hip/2 * (1 - 0.6*f)
|
| 36 |
+
R[i,:] = r
|
| 37 |
+
X = R * np.cos(Theta) * 0.85
|
| 38 |
+
Y = R * np.sin(Theta) * 0.55
|
| 39 |
+
return X, Y, Z
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
def _garment_surface_shirt(params):
|
| 42 |
+
"""Create shirt/t-shirt/blouse as 3D surface mesh."""
|
| 43 |
+
bust = params.get("bust", 92)
|
| 44 |
+
waist = params.get("waist", 74)
|
| 45 |
+
hip = params.get("hip", 96)
|
| 46 |
+
shoulder = params.get("shoulder_width", 42)
|
| 47 |
+
body_len = params.get("bodice_length", 72)
|
| 48 |
+
sleeve_len = params.get("sleeve_length", 60)
|
| 49 |
+
bicep_r = params.get("bicep", 30) / 2 + 2
|
| 50 |
+
wrist_r = params.get("wrist", 18) / 2 + 1
|
| 51 |
+
flare = params.get("flare", 0)
|
| 52 |
+
fit = params.get("fit", "regular")
|
| 53 |
+
ease = {"fitted": 2, "regular": 4, "oversized": 10, "loose": 8}.get(fit, 4)
|
| 54 |
+
n_theta = 36
|
| 55 |
+
theta = np.linspace(0, 2*np.pi, n_theta)
|
| 56 |
+
surfaces = []
|
| 57 |
+
|
| 58 |
+
# Torso - main body of shirt
|
| 59 |
+
n_z_torso = 30
|
| 60 |
+
z_torso = np.linspace(0, body_len, n_z_torso)
|
| 61 |
+
Theta_t, Z_t = np.meshgrid(theta, z_torso)
|
| 62 |
+
R_t = np.zeros_like(Z_t)
|
| 63 |
+
for i in range(n_z_torso):
|
| 64 |
+
t = z_torso[i] / body_len
|
| 65 |
+
if t < 0.25:
|
| 66 |
+
r = (shoulder/2 + ease) * (1 - 0.15*t)
|
| 67 |
elif t < 0.55:
|
| 68 |
+
f = (t-0.25)/0.3
|
| 69 |
+
r_top = (shoulder/2 + ease) * 0.962
|
| 70 |
+
r_bot = (bust/2 + ease)
|
| 71 |
+
r = r_top + (r_bot - r_top) * f
|
| 72 |
+
elif t < 0.7:
|
| 73 |
+
f = (t-0.55)/0.15
|
| 74 |
+
r = (bust/2 + ease) * (1 - 0.05*f)
|
| 75 |
else:
|
| 76 |
+
f = (t-0.7)/0.3
|
| 77 |
+
r_bust = (bust/2 + ease) * 0.95
|
| 78 |
+
r_waist = (waist/2 + ease + flare)
|
| 79 |
+
r = r_bust + (r_waist - r_bust) * f
|
| 80 |
+
R_t[i,:] = r
|
| 81 |
+
X_t = R_t * np.cos(Theta_t) * 0.85
|
| 82 |
+
Y_t = R_t * np.sin(Theta_t) * 0.55
|
| 83 |
+
surfaces.append((X_t, Y_t, Z_t, "#5B9BD5", "Torso", 0.7))
|
| 84 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
# Sleeves
|
|
|
|
|
|
|
| 86 |
for side in [1, -1]:
|
| 87 |
+
n_sl = 15
|
| 88 |
+
z_sl = np.linspace(0, sleeve_len, n_sl)
|
| 89 |
+
n_th_sl = 16
|
| 90 |
+
th_sl = np.linspace(0, 2*np.pi, n_th_sl)
|
| 91 |
+
Theta_s, Z_s = np.meshgrid(th_sl, z_sl)
|
| 92 |
+
R_s = np.zeros_like(Z_s)
|
| 93 |
+
for j in range(n_sl):
|
| 94 |
+
t = z_sl[j] / sleeve_len
|
| 95 |
+
R_s[j,:] = bicep_r * (1-t) + wrist_r * t + ease*0.5
|
| 96 |
+
sx_center = side * (shoulder*0.85 + ease*0.3)
|
| 97 |
+
sy_center = 0
|
| 98 |
+
sz_top = body_len * 0.92
|
| 99 |
+
X_s = sx_center + R_s * np.cos(Theta_s) * 0.7 + side * np.linspace(0, sleeve_len*0.15, n_sl).reshape(-1,1)
|
| 100 |
+
Y_s = sy_center + R_s * np.sin(Theta_s) * 0.5 + np.linspace(0, -sleeve_len*0.4, n_sl).reshape(-1,1)
|
| 101 |
+
Z_s = sz_top - Z_s * 0.3
|
| 102 |
+
color = "#6BB3E0" if side > 0 else "#4A90D9"
|
| 103 |
+
surfaces.append((X_s, Y_s, Z_s, color, "Sleeve", 0.65))
|
| 104 |
|
| 105 |
+
# Collar
|
| 106 |
+
if params.get("has_collar", False):
|
| 107 |
+
collar_h = params.get("collar_height", 4)
|
| 108 |
+
neck_r = params.get("neckline_width", 7) + 1
|
| 109 |
+
n_c = 24
|
| 110 |
+
th_c = np.linspace(0, 2*np.pi, n_c)
|
| 111 |
+
n_zc = 5
|
| 112 |
+
z_c = np.linspace(0, collar_h, n_zc)
|
| 113 |
+
Theta_c, Z_c = np.meshgrid(th_c, z_c)
|
| 114 |
+
R_c = np.ones_like(Z_c) * neck_r
|
| 115 |
+
X_c = R_c * np.cos(Theta_c) * 0.85
|
| 116 |
+
Y_c = R_c * np.sin(Theta_c) * 0.55
|
| 117 |
+
Z_c = Z_c + body_len - 2
|
| 118 |
+
surfaces.append((X_c, Y_c, Z_c, "#FFFFFF", "Collar", 0.9))
|
| 119 |
+
|
| 120 |
+
return surfaces
|
| 121 |
+
def _garment_surface_pants(params):
|
| 122 |
+
"""Create pants/trousers as 3D surface mesh."""
|
| 123 |
+
waist = params.get("waist", 74)
|
| 124 |
+
hip = params.get("hip", 96)
|
| 125 |
+
thigh = params.get("thigh", 56)
|
| 126 |
+
ankle = params.get("ankle", 24)
|
| 127 |
+
pant_len = params.get("pant_length", 100)
|
| 128 |
+
crotch = params.get("crotch_depth", 27)
|
| 129 |
+
fit = params.get("fit", "regular")
|
| 130 |
+
ease = {"fitted":1,"regular":3,"oversized":6,"loose":5}.get(fit, 3)
|
| 131 |
body_h = 170
|
| 132 |
+
n_theta = 24
|
| 133 |
+
theta = np.linspace(0, 2*np.pi, n_theta)
|
| 134 |
+
surfaces = []
|
| 135 |
for side in [1, -1]:
|
| 136 |
+
n_z = 25
|
| 137 |
+
z = np.linspace(0, pant_len, n_z)
|
| 138 |
+
Theta, Z = np.meshgrid(theta, z)
|
| 139 |
+
R = np.zeros_like(Z)
|
| 140 |
+
for i in range(n_z):
|
| 141 |
+
t = z[i] / pant_len
|
| 142 |
+
if t < 0.2:
|
| 143 |
+
f = t/0.2
|
| 144 |
+
r = (hip/2+ease) * (1-f) + (thigh/2+ease)*f
|
| 145 |
+
elif t < 0.35:
|
| 146 |
+
r = thigh/2 + ease
|
| 147 |
+
else:
|
| 148 |
+
f = (t-0.35)/0.65
|
| 149 |
+
r = (thigh/2+ease) * (1-f) + (ankle/2+ease) * f
|
| 150 |
+
R[i,:] = r
|
| 151 |
+
cx = side * (hip/4 + ease*0.3)
|
| 152 |
+
X = cx + R * np.cos(Theta) * 0.8
|
| 153 |
+
Y = R * np.sin(Theta) * 0.55
|
| 154 |
+
Z = (body_h * 0.58 - crotch * 0.3) - Z * 0.3 + Z * 0.7
|
| 155 |
+
color = "#2C5F8A" if side > 0 else "#3A6FA0"
|
| 156 |
+
surfaces.append((X, Y, Z, color, "Leg", 0.7))
|
| 157 |
+
# Waistband
|
| 158 |
+
n_wb = 24
|
| 159 |
+
th_wb = np.linspace(0, 2*np.pi, n_wb)
|
| 160 |
+
n_zw = 4
|
| 161 |
+
z_wb = np.linspace(0, params.get("waistband_height", 4), n_zw)
|
| 162 |
+
Theta_w, Z_w = np.meshgrid(th_wb, z_wb)
|
| 163 |
+
R_w = np.ones_like(Z_w) * (waist/2 + ease)
|
| 164 |
+
X_w = R_w * np.cos(Theta_w) * 0.85
|
| 165 |
+
Y_w = R_w * np.sin(Theta_w) * 0.55
|
| 166 |
+
Z_w = Z_w + body_h * 0.58
|
| 167 |
+
surfaces.append((X_w, Y_w, Z_w, "#1A3D5C", "Waistband", 0.85))
|
| 168 |
+
return surfaces
|
| 169 |
|
| 170 |
+
def _garment_surface_skirt(params):
|
| 171 |
+
"""Create skirt as 3D surface mesh."""
|
| 172 |
+
waist = params.get("waist", 74)
|
| 173 |
+
hip = params.get("hip", 96)
|
| 174 |
+
skirt_len = params.get("skirt_length", 55)
|
| 175 |
+
flare = params.get("flare", 5)
|
| 176 |
body_h = 170
|
| 177 |
+
n_theta = 36
|
| 178 |
+
theta = np.linspace(0, 2*np.pi, n_theta)
|
| 179 |
+
n_z = 25
|
| 180 |
+
z = np.linspace(0, skirt_len, n_z)
|
| 181 |
+
Theta, Z = np.meshgrid(theta, z)
|
| 182 |
+
R = np.zeros_like(Z)
|
| 183 |
+
for i in range(n_z):
|
| 184 |
+
t = z[i] / skirt_len
|
| 185 |
+
if t < 0.3:
|
| 186 |
+
f = t / 0.3
|
| 187 |
+
r = (waist/2+2) * (1-f) + (hip/2+2) * f
|
| 188 |
+
else:
|
| 189 |
+
f = (t-0.3)/0.7
|
| 190 |
+
r = (hip/2+2) + flare * f * (1 + 0.5*np.sin(3*theta)**2)[np.newaxis,:]
|
| 191 |
+
R[i,:] = r
|
| 192 |
+
X = R * np.cos(Theta) * 0.85
|
| 193 |
+
Y = R * np.sin(Theta) * 0.55
|
| 194 |
waist_z = body_h * 0.58
|
| 195 |
+
Z = waist_z - Z
|
| 196 |
+
return [(X, Y, Z, "#C48BB8", "Skirt", 0.7)]
|
| 197 |
+
def _garment_surface_dress(params):
|
| 198 |
+
return _garment_surface_shirt(params) + _garment_surface_skirt(params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
+
def _garment_surface_hoodie(params):
|
| 201 |
+
surfaces = _garment_surface_shirt(params)
|
| 202 |
+
head_r = params.get("head_circumference", 57) / 2
|
| 203 |
+
n_th = 24
|
| 204 |
+
theta = np.linspace(0, 2*np.pi, n_th)
|
| 205 |
+
n_z = 15
|
| 206 |
+
z = np.linspace(0, head_r*1.3, n_z)
|
| 207 |
+
Theta, Z = np.meshgrid(theta, z)
|
| 208 |
+
R = np.zeros_like(Z)
|
| 209 |
+
for i in range(n_z):
|
| 210 |
+
t = z[i] / (head_r*1.3)
|
| 211 |
+
if t < 0.3:
|
| 212 |
+
R[i,:] = head_r * (0.8 + 0.2*t)
|
| 213 |
+
elif t < 0.7:
|
| 214 |
+
R[i,:] = head_r * (1.0 - 0.1*(t-0.3)/0.4)
|
| 215 |
+
else:
|
| 216 |
+
R[i,:] = head_r * 0.9 * (1 - 0.5*(t-0.7)/0.3)
|
| 217 |
+
body_len = params.get("bodice_length", 65)
|
| 218 |
body_h = 170
|
| 219 |
+
X = R * np.cos(Theta) * 0.85
|
| 220 |
+
Y = R * np.sin(Theta) * 0.55 + np.linspace(0, head_r*0.3, n_z).reshape(-1,1)
|
| 221 |
+
Z = Z + body_h * 0.92
|
| 222 |
+
surfaces.append((X, Y, Z, "#3A7BD5", "Hood", 0.65))
|
| 223 |
+
return surfaces
|
|
|
|
| 224 |
|
| 225 |
+
def _garment_surface_jacket(params):
|
| 226 |
+
return _garment_surface_shirt(params)
|
| 227 |
|
| 228 |
+
GARMENT_SURFACE_FUNCS = {
|
| 229 |
+
"shirt": _garment_surface_shirt, "blouse": _garment_surface_shirt,
|
| 230 |
+
"top": _garment_surface_shirt, "t-shirt": _garment_surface_shirt, "tee": _garment_surface_shirt,
|
| 231 |
+
"dress": _garment_surface_dress,
|
| 232 |
+
"skirt": _garment_surface_skirt,
|
| 233 |
+
"pants": _garment_surface_pants, "trousers": _garment_surface_pants, "jeans": _garment_surface_pants,
|
| 234 |
+
"jacket": _garment_surface_jacket, "coat": _garment_surface_jacket, "blazer": _garment_surface_jacket,
|
| 235 |
+
"hoodie": _garment_surface_hoodie, "sweatshirt": _garment_surface_hoodie,
|
| 236 |
+
"vest": _garment_surface_shirt,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
}
|
|
|
|
| 238 |
def create_3d_figure(analysis: Dict) -> go.Figure:
|
| 239 |
+
"""Create interactive 3D garment visualization with surface meshes."""
|
| 240 |
+
garment_type = analysis.get("garment_type", "shirt").lower()
|
| 241 |
+
measurements = analysis.get("measurements", {})
|
| 242 |
+
features = analysis.get("features", {})
|
| 243 |
params = {**measurements, **features}
|
| 244 |
+
chest = params.get("bust", 92)
|
| 245 |
+
waist_m = params.get("waist", 74)
|
| 246 |
+
hip_m = params.get("hip", 96)
|
|
|
|
| 247 |
fig = go.Figure()
|
| 248 |
+
# Body - translucent skin-toned surface
|
| 249 |
+
BX, BY, BZ = _make_body_surface(chest=chest, waist=waist_m, hip=hip_m)
|
| 250 |
+
fig.add_trace(go.Surface(
|
| 251 |
+
x=BX, y=BY, z=BZ,
|
| 252 |
+
surfacecolor=np.ones_like(BX) * 0.8,
|
| 253 |
+
colorscale=[[0,"#E8D0B0"],[1,"#E8D0B0"]],
|
| 254 |
+
opacity=0.25, showscale=False, name="Body",
|
| 255 |
+
hoverinfo="skip", lighting=dict(ambient=0.9, diffuse=0.1, specular=0)))
|
| 256 |
+
# Garment surfaces
|
| 257 |
+
func = GARMENT_SURFACE_FUNCS.get(garment_type, _garment_surface_shirt)
|
| 258 |
+
surfaces = func(params)
|
| 259 |
+
for X, Y, Z, color, name, opacity in surfaces:
|
| 260 |
+
fig.add_trace(go.Surface(
|
| 261 |
+
x=X, y=Y, z=Z,
|
| 262 |
+
surfacecolor=np.ones_like(X) * 0.5,
|
| 263 |
+
colorscale=[[0, color], [1, color]],
|
| 264 |
+
opacity=opacity, showscale=False, name=name,
|
| 265 |
+
hoverinfo="name",
|
| 266 |
+
lighting=dict(ambient=0.6, diffuse=0.4, specular=0.2, roughness=0.5)))
|
| 267 |
fig.update_layout(
|
| 268 |
scene=dict(
|
| 269 |
+
xaxis=dict(showgrid=False, showticklabels=False, title="", zeroline=False),
|
| 270 |
+
yaxis=dict(showgrid=False, showticklabels=False, title="", zeroline=False),
|
| 271 |
+
zaxis=dict(showgrid=False, showticklabels=False, title="", zeroline=False),
|
| 272 |
+
aspectmode="data",
|
| 273 |
+
camera=dict(eye=dict(x=1.8, y=0.8, z=0.9), center=dict(x=0, y=0, z=-0.15))),
|
| 274 |
margin=dict(l=0, r=0, t=30, b=0),
|
| 275 |
height=500,
|
| 276 |
+
title=dict(text=f"3D Preview: {garment_type.title()}", font=dict(size=14)),
|
| 277 |
+
paper_bgcolor="#fafafa")
|
| 278 |
+
return fig
|
|
|