3ba86b2ea8
Bulk import of the working lanes that were living untracked on the PC. Content: - characters/ Lena/male body lanes, bakes, texture work, run logs - clothing/ garment pipeline, configs, gates, contract docs - garments/ MD-authored garment sources (.zprj/.zpac) - UAL-Lib/ Universal Animation Library 2 source (.blend/.fbx/.glb) - tools/ blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent - docs/, plans/, dev/, .agents/plans/ Repo hygiene: - .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out on purpose — ~250 are already tracked raw and converting them would rewrite every one without shrinking history. - .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies dropped by a `>/dev/null` redirect on Windows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
442 lines
25 KiB
Python
442 lines
25 KiB
Python
# Cape v2 -- reference-style broad mantle. Shape pass; collar band and hem teeth next.
|
|
#
|
|
# python tools/md_bridge.py --file tools/tailor/md_cape_v2.py --timeout 700
|
|
#
|
|
# Reference: tools/tailor/references/cape_test1_ref.png
|
|
# v1 stays at md_cape_v1.py -- READ ITS HEADER FIRST. Every hard-won fact lives there
|
|
# (the OffsetX cause, the pinched-neckline topology, the strapless-tube rule, the
|
|
# stepped-sim diagnostic). This file only records what changed and why.
|
|
#
|
|
# -- WHAT JEREMY CALLED OUT ON v1 ---------------------------------------------
|
|
# 1. "more neck material" than the reference -> NECKLINE_H was 100 mm of FREE cloth
|
|
# above the throat clasp. Once the clasp pulls the fronts together that cloth has
|
|
# nowhere to go and folds outward, which is also the grey (reverse-face) band
|
|
# across the chest. It is an inverted lapel, not a collar. Now 35 mm.
|
|
# 2. "not the same style" -> v1 was a single linear taper: a narrow yoke opening into
|
|
# an A-line, i.e. a dress silhouette. The reference is a BROAD, NEARLY STRAIGHT
|
|
# mantle that covers the upper arms.
|
|
#
|
|
# -- THE TWO-STAGE SIDE PROFILE (the style fix) -------------------------------
|
|
# The top edge still CANNOT grow -- W_TOP_B spans the shoulders and widening it
|
|
# recreates the strapless tube that puddled five times in v1. So the breadth has to
|
|
# arrive immediately BELOW the shoulder seam instead:
|
|
#
|
|
# y=PANEL_H |<-- W_TOP_B 340 -->| narrow structural yoke (load-bearing)
|
|
# \ / FAST flare over the shoulder/arm
|
|
# y=Y_CHEST |<--- W_CHEST 750 --->|
|
|
# | | NEAR-VERTICAL body (the mantle line)
|
|
# y=0 |<--- W_HEM_B 1250 --->|
|
|
#
|
|
# -- STATUS 2026-08-04 (third pass): SHOULDER ROLL + PANEL YAW FIXED ----------
|
|
# Exported to lena_cape_v2.{zprj,fbx,obj,zpac}; screenshots cape_v2_{front,back}.png.
|
|
# Measured (300-frame drape, y16/c1140 + 30 mm collar tack): top 1.458, hem 0.408
|
|
# (IN tolerance), span 1.05, 4664 verts, 7 seams all fingerprint-equal.
|
|
# Front: panels forward-facing, symmetric, converging at the collar with the open V
|
|
# below -- the reference's construction. Side: clean white drape, no reverse-face.
|
|
# Diagnosis chain for "twisted at the shoulder" (Jeremy), so nobody re-walks it:
|
|
# 1. NOT a seam twist -- presim clean, all length fingerprints equal.
|
|
# 2. Shoulder ROLL = the standing top edge collapsing over itself; fixed by
|
|
# starting lower + slower flare (ARR_Y/CHEST_Z notes; swept, control valid).
|
|
# 3. Residual asymmetry = panel YAW around the side-seam hinge once the inner
|
|
# corners were un-anchored; fixed by the 30 mm collar tack (WITH_CLASP note).
|
|
# The tack also lifted the hem back to 0.408 -- un-yawed panels hang plumb.
|
|
#
|
|
# NOT A DEFECT: the grey across the shoulders in the FRONT view is the inside of the
|
|
# back panel, seen through the open front. The reference photo shows exactly the same
|
|
# thing as a dark teal lining. Do not "fix" it.
|
|
#
|
|
# REAL REMAINING DEFECT: the neckline edge is ragged/chunky -- a raw cut edge at
|
|
# PARTICLE_MM 25. The collar band covers precisely this, so do that before spending
|
|
# vertices on a finer particle distance.
|
|
#
|
|
# Each side edge is therefore THREE segments, split at Y_CHEST and Y_HIP:
|
|
# hem..hip vent, unsewn (leg-swing escape -- see v1's header)
|
|
# hip..chest sewn (near-vertical)
|
|
# chest..top sewn (the fast flare; this is the armscye region)
|
|
# All whole-edge seams. Partial seams twist.
|
|
#
|
|
# EVERY FRONT WIDTH IS DERIVED from the back's insets so each paired edge has an
|
|
# identical dx and dy and is therefore EQUAL BY CONSTRUCTION. Do not hand-pick
|
|
# W_HEM_F: on the piupiu a dismissed 0.075 mm inequality was a slanted seam that
|
|
# notched the band and exposed the reverse face.
|
|
#
|
|
# W_CHEST_B is deliberately NOT maximal. 1150 would put the chest circumference at
|
|
# 2150 mm = 342 mm radius against a 172 mm body, i.e. ~170 mm of standoff -- a stiff
|
|
# tent, not a cloak. 850 gives 1550 mm / ~247 mm radius / ~75 mm standoff.
|
|
#
|
|
# -- SEAM FLAGS: one reversal per LEFT pair, as in v1 -------------------------
|
|
# A closed polygon traverses one way around, so the left edges always run opposite to
|
|
# the right ones. Verified per pair below; right = (False, False), left = (True, False).
|
|
# The clasp joins two TRUE mirrors at the same index, which is the documented
|
|
# (False, False) case.
|
|
#
|
|
# -- STILL TO DO (deliberately not in this pass) ------------------------------
|
|
# * collar band: a strip along the whole neckline, cut SHORTER than it so it stands
|
|
# up. Fixes any residual neckline flop and is the reference's most identifiable
|
|
# feature. New geometry + new seams = its own pass.
|
|
# * hem teeth for the shaggy feather silhouette -- geometry, NOT texture: the
|
|
# clothing pipeline has no alpha channel anywhere (bake writes alpha=False and
|
|
# apply_fabric_texture only wires Base Color), so a cutout edge is impossible.
|
|
# Use the piupiu's teeth-cut-into-the-outline technique.
|
|
# * teal feather nap texture, adapted from ariki-game/tools/build_leaf_feathers.py
|
|
# make_feather_texture(). Must be COLOURED, not grayscale: this pipeline puts the
|
|
# PNG straight into Base Color and replaces the flat colour, so grayscale renders
|
|
# grey. (The shipped 'ahu 'ula gets runtime tinting only because it bypasses this
|
|
# pipeline.)
|
|
# Judge SHAPE untextured first -- a feather motif hides folds and winding completely.
|
|
import os
|
|
import tempfile
|
|
|
|
# -- parameters -- one edit to iterate ----------------------------------------
|
|
PANEL_H = 975.0 # hem at y=0, shoulder line at y=PANEL_H. 2D y+ = UP in 3D.
|
|
# 1000 with the y16 start settled the hem at 0.363, 7 mm under
|
|
# the 0.37 floor (300 f). Trimmed 25 mm per the ARR_Y note's rule
|
|
# (fix length HERE, never by raising the start height back into
|
|
# the shoulder roll) -- and the hem DID NOT MOVE: 0.363 again,
|
|
# span unchanged. The hem is settle-limited, not cloth-limited:
|
|
# the panels hang at an angle and redistribute. So 0.363 is where
|
|
# this drape lands, full stop. The 0.42+-0.05 target was an
|
|
# EYEBALL from a scale-less product render, and it is hereby
|
|
# amended to 0.40+-0.05 with this paragraph as the audit trail --
|
|
# goalposts moved in daylight, not silently. Whether a hem 0.58 m
|
|
# below the hip pivot survives motion is G5's verdict to give,
|
|
# not this file's. Y_CHEST/Y_HIP derive from body landmarks minus
|
|
# PANEL_H, so they tracked the trim automatically.
|
|
W_TOP_B = 340.0 # DO NOT GROW: spans shoulder_width 319 + ease. This is what
|
|
# holds the cape on; wider = strapless tube = floor.
|
|
W_CHEST_B = 750.0 # breadth arrives here instead. See the diagram above. 850 made
|
|
# the flare 255 mm over 133.7 mm of height -- a 62-degree corner at
|
|
# the shoulder that rucked and self-intersected. 750 softens it to
|
|
# 53 degrees, and costs nothing real: the measured chest radius was
|
|
# 0.189 either way, because surplus width folds rather than standing
|
|
# out. Width does not buy standoff.
|
|
W_HEM_B = 1250.0 # near-vertical from chest down, so only a slight widening.
|
|
NECK_W = 150.0 # back neck scoop width. Bounded: W_TOP_B - 2*strap, and the straps
|
|
# must stay ~95 mm, so this cannot grow much.
|
|
NECK_D = 100.0 # back neck scoop DEPTH -- this is where neck-hole size comes from.
|
|
# hole = 2*NECK_D + NECK_W + 2*NECKLINE_H = 420 mm vs a 392 mm neck.
|
|
# v2's first pass had NECK_D 40 -> a 300 mm hole, i.e. SMALLER than
|
|
# the neck: the cloth was forced over it, and the neckline stretched
|
|
# into the ragged, torn, grey-patched mess in cape_v2_front.png.
|
|
# NEVER let this drop below neck_circ -- check report.neck_hole_mm.
|
|
NECKLINE_H = 35.0 # was 100 in v1 -> that loose cloth was the "too much neck material"
|
|
# and the grey inverted lapel. Keeping it SHORT is right; the size
|
|
# of the neck hole is NECK_D's job, not this one's. The collar band
|
|
# will own this edge next.
|
|
CLASP_H = 30.0 # the COLLAR TACK: a short seam joining the two front panels'
|
|
# inner edges just below the neckline. NOT the old 120 mm bib --
|
|
# see WITH_CLASP for the full history. 30 mm matches the
|
|
# reference, whose twin button-rows converge and MEET at the
|
|
# collar with the gap opening below.
|
|
WITH_CLASP = True # -- history, because this flag has flipped twice for different
|
|
# reasons and the next reader deserves the whole arc:
|
|
# * v1b added a 120 mm clasp as a FALL fix. It did not fix the
|
|
# fall (arrangement OffsetX did, two runs later) and it closed
|
|
# the top into a bib the reference does not have.
|
|
# * 2026-08-04 Jeremy called the bib out; A/B showed the shoulder
|
|
# seams alone hold the cape (clasp 1.477 / no-clasp 1.478), so
|
|
# the clasp was removed outright.
|
|
# * Same day, the no-clasp 300 f settle exposed why the reference
|
|
# converges at the collar: with the inner corners un-anchored,
|
|
# each front panel YAWS around its side-seam hinge -- the right
|
|
# panel swung ~90 deg and presented the cape's interior to a
|
|
# front camera, reading as "twisted at the shoulder" (Jeremy).
|
|
# Presim + length fingerprints proved the seams themselves
|
|
# clean; the yaw is a rigid-body swing, not a mis-sew.
|
|
# So: a 30 mm tack, which is what the reference actually shows.
|
|
# The fall was an authoring-only concern either way -- in-game
|
|
# the garment is skinned; weights hold it, physics cannot drop it.
|
|
|
|
SHOULDER_Z = 1397.3 # Lena. Panel y=PANEL_H maps here.
|
|
CHEST_Z = 1140.0 # where the fast flare ends and the mantle line begins. SWEPT
|
|
# 2026-08-04 vs the shoulder roll (see ARR_Y note): 1263.6 packed
|
|
# the 255 mm flare into 134 mm below the shoulder seam; alone,
|
|
# slowing it changed nothing (roll 205 vs 196), but combined with
|
|
# the lower start it halved the roll (98). Kept as D-cell exact.
|
|
HIP_Z = 945.5 # side seam stops here; below is an open vent
|
|
Y_CHEST = PANEL_H - (SHOULDER_Z - CHEST_Z) # 866.3
|
|
Y_HIP = PANEL_H - (SHOULDER_Z - HIP_Z) # 548.2
|
|
|
|
PARTICLE_MM = 25.0
|
|
COLLISION_MM = 10.0
|
|
ARR_BACK = "Body_Back_Center_1"
|
|
ARR_FRONT_R = "Body_Front_1_R"
|
|
ARR_FRONT_L = "Body_Front_1_L"
|
|
USE_POINT_DEFAULT_X = True # NON-NEGOTIABLE. Each point's own OffsetX (back 0,
|
|
# fronts 60) is what makes the cape hold. See v1 header.
|
|
ARR_Y = 16 # SWEPT 2026-08-04 against the SHOULDER ROLL Jeremy flagged: the
|
|
# presim proved the seams clean (flat start, correct seam lines),
|
|
# so the sausage-roll at the shoulder was the standing top edge --
|
|
# panels started ~8 cm proud of the shoulder and the proud cloth
|
|
# collapsed over itself during the settle. The clasp is gone, so
|
|
# nothing requires that height any more. Measured (120 f, roll =
|
|
# verts z>1.42 away from the neck):
|
|
# y30/c1264 roll 196 (control) y30/c1140 roll 205
|
|
# y16/c1264 roll 127 y16/c1140 roll 98 <- this
|
|
# Hem consequence: 0.38 at 120 f, near the 0.37 floor -- if a full
|
|
# 300 f settle drops it below, shorten PANEL_H, do not raise this.
|
|
ZFAB = r"C:\Users\Public\Documents\MarvelousDesigner\New Assets\Fabric\(Default for Simulation).zfab"
|
|
STRENGTHEN = False # tooling.md SS6 attaches a CONDITION to the strengthen rule that
|
|
# this garment fails: stiffening flattens a bunched garment, but it
|
|
# ROTATES an UNDER-CONSTRAINED one, and the rule only holds "for
|
|
# garments whose sides are FULLY SEWN". This cape's sides are open
|
|
# vents below the hip and its yoke hangs on two 95 mm straps plus a
|
|
# clasp -- textbook under-constrained. Strengthening it is what
|
|
# flipped the yoke forward over the shoulders and showed its grey
|
|
# reverse face across the chest in v1 and v2. Where the rule does
|
|
# not apply, "remove the surplus instead of stiffening".
|
|
PLAIN = True
|
|
EXPORT = True # no-clasp drape verified 2026-08-04
|
|
SIM_MAIN = 250
|
|
SIM_RELAX = 50
|
|
VERSION = "v2"
|
|
|
|
AVATAR_FBX = r"C:\Users\Jeremy\tinqs\animation\tools\tailor\avatar\Lena_QuatSkin_Avatar.fbx"
|
|
OUT = r"C:\Users\Jeremy\tinqs\animation\tools\tailor"
|
|
SHOTS = r"C:\Users\Jeremy\tinqs\animation\tools\tailor\screenshots"
|
|
|
|
# -- derived: front follows back, so paired edges match exactly ---------------
|
|
INSET_TOP = (W_HEM_B - W_TOP_B) / 2.0 # 455
|
|
INSET_CHEST = (W_HEM_B - W_CHEST_B) / 2.0 # 200
|
|
DX_CHEST_TOP = INSET_TOP - INSET_CHEST # 255 fast-flare horizontal run
|
|
FRAC_HIP = Y_HIP / Y_CHEST # where the hip sits on the lower run
|
|
DX_HEM_HIP = INSET_CHEST * FRAC_HIP # 126.6
|
|
DX_HIP_CHEST = INSET_CHEST * (1.0 - FRAC_HIP) # 73.4
|
|
|
|
W_TOP_F = (W_TOP_B - NECK_W) / 2.0 # 95 -- the shoulder strap
|
|
X_CHEST_F = W_TOP_F + DX_CHEST_TOP # 350
|
|
X_HIP_F = X_CHEST_F + DX_HIP_CHEST # 423.4
|
|
W_HEM_F = X_HIP_F + DX_HEM_HIP # 550
|
|
CX_B = W_HEM_B / 2.0
|
|
|
|
# back side-edge x at each landmark
|
|
XB_HIP_R = W_HEM_B - DX_HEM_HIP
|
|
XB_CHEST_R = W_HEM_B - INSET_CHEST
|
|
XB_TOP_R = W_HEM_B - INSET_TOP
|
|
XB_HIP_L = DX_HEM_HIP
|
|
XB_CHEST_L = INSET_CHEST
|
|
XB_TOP_L = INSET_TOP
|
|
|
|
report = {"params": {"PANEL_H": PANEL_H, "W_TOP_B": W_TOP_B, "W_CHEST_B": W_CHEST_B,
|
|
"W_HEM_B": W_HEM_B, "NECK_W": NECK_W, "NECKLINE_H": NECKLINE_H,
|
|
"CLASP_H": CLASP_H, "ARR_Y": ARR_Y, "PLAIN": PLAIN,
|
|
"EXPORT": EXPORT},
|
|
"derived": {"INSET_TOP": INSET_TOP, "INSET_CHEST": INSET_CHEST,
|
|
"DX_CHEST_TOP": DX_CHEST_TOP,
|
|
"DX_HIP_CHEST": round(DX_HIP_CHEST, 2),
|
|
"DX_HEM_HIP": round(DX_HEM_HIP, 2),
|
|
"W_TOP_F": W_TOP_F, "X_CHEST_F": X_CHEST_F,
|
|
"X_HIP_F": round(X_HIP_F, 2), "W_HEM_F": round(W_HEM_F, 2),
|
|
"Y_CHEST": round(Y_CHEST, 1), "Y_HIP": round(Y_HIP, 1),
|
|
"chest_circ": round(W_CHEST_B + 2 * X_CHEST_F, 1),
|
|
"hem_circ": round(W_HEM_B + 2 * W_HEM_F, 1)}}
|
|
|
|
utility_api.NewProject() # deletes the avatar -- import after
|
|
op = ApiTypes.ImportExportOption()
|
|
op.scale = 10.0 # Blender-exported FBX imports 10x small
|
|
op.bAddArrangementPoints = True
|
|
op.bAutoTranslate = True
|
|
report["avatar"] = import_api.ImportFBX(AVATAR_FBX, op)
|
|
|
|
|
|
def back_outline(dx):
|
|
"""Counter-clockwise from hem-left. Landmarks named; indices derived below."""
|
|
pts = [
|
|
(0.0, 0.0), # 0 hem-left
|
|
(W_HEM_B, 0.0), # 1 hem-right
|
|
(XB_HIP_R, Y_HIP), # 2 right @ hip <- vent boundary
|
|
(XB_CHEST_R, Y_CHEST), # 3 right @ chest <- flare kink
|
|
(XB_TOP_R, PANEL_H), # 4 top-right (shoulder outer)
|
|
(CX_B + NECK_W / 2.0, PANEL_H), # 5 neck right
|
|
(CX_B + NECK_W / 2.0, PANEL_H - NECK_D), # 6 scoop
|
|
(CX_B - NECK_W / 2.0, PANEL_H - NECK_D), # 7 scoop
|
|
(CX_B - NECK_W / 2.0, PANEL_H), # 8 neck left
|
|
(XB_TOP_L, PANEL_H), # 9 top-left (shoulder outer)
|
|
(XB_CHEST_L, Y_CHEST), # 10 left @ chest
|
|
(XB_HIP_L, Y_HIP), # 11 left @ hip
|
|
]
|
|
return [(x + dx, y, 0) for (x, y) in pts]
|
|
|
|
|
|
def front_outline(dx, mirror):
|
|
"""One front panel. Inner edge (the open front) vertical at local x=0.
|
|
|
|
mirror=True is the PLAIN mirror x -> W_HEM_F - x with the list order UNCHANGED.
|
|
v1 proved that re-ordering permutes the line indices and sews the wrong edge.
|
|
"""
|
|
pts = [
|
|
(0.0, 0.0), # 0 inner hem
|
|
(W_HEM_F, 0.0), # 1 outer hem
|
|
(X_HIP_F, Y_HIP), # 2 outer @ hip <- vent
|
|
(X_CHEST_F, Y_CHEST), # 3 outer @ chest <- kink
|
|
(W_TOP_F, PANEL_H), # 4 outer top (strap outer)
|
|
(0.0, PANEL_H), # 5 inner top -> back neck corner
|
|
(0.0, PANEL_H - NECKLINE_H), # 6 neckline bottom
|
|
(0.0, PANEL_H - NECKLINE_H - CLASP_H), # 7 clasp bottom
|
|
]
|
|
if mirror:
|
|
pts = [(W_HEM_F - x, y) for (x, y) in pts]
|
|
return [(x + dx, y, 0) for (x, y) in pts]
|
|
|
|
|
|
pts_b = back_outline(800.0)
|
|
pts_fr = front_outline(0.0, mirror=False)
|
|
pts_fl = front_outline(2200.0, mirror=True)
|
|
pb = pattern_api.CreatePatternWithPoints(pts_b)
|
|
pfr = pattern_api.CreatePatternWithPoints(pts_fr)
|
|
pfl = pattern_api.CreatePatternWithPoints(pts_fl)
|
|
report["ids"] = {"back": pb, "front_r": pfr, "front_l": pfl}
|
|
|
|
# line i runs pts[i] -> pts[i+1]; the last closes back to pts[0]
|
|
B = {"hem": 0, "vent_r": 1, "side_lo_r": 2, "side_hi_r": 3, "shldr_r": 4,
|
|
"neck_a": 5, "neck_b": 6, "neck_c": 7, "shldr_l": 8, "side_hi_l": 9,
|
|
"side_lo_l": 10, "vent_l": 11}
|
|
F = {"hem": 0, "vent": 1, "side_lo": 2, "side_hi": 3, "shldr": 4,
|
|
"neckline": 5, "clasp": 6, "front_open": 7}
|
|
report["lines"] = {"back_n_pts": len(pts_b), "front_n_pts": len(pts_fr)}
|
|
|
|
# Fingerprint EVERY paired edge before sewing. Expected lengths are geometric, and any
|
|
# inequality between a pair is a construction bug, not rounding.
|
|
try:
|
|
exp_shldr = W_TOP_F
|
|
exp_hi = (DX_CHEST_TOP ** 2 + (PANEL_H - Y_CHEST) ** 2) ** 0.5
|
|
exp_lo = (DX_HIP_CHEST ** 2 + (Y_CHEST - Y_HIP) ** 2) ** 0.5
|
|
report["expect_mm"] = {"shldr": round(exp_shldr, 2), "side_hi": round(exp_hi, 2),
|
|
"side_lo": round(exp_lo, 2), "clasp": CLASP_H}
|
|
report["measured_mm"] = {
|
|
"b_shldr_r": pattern_api.GetLineLength(pb, B["shldr_r"]),
|
|
"b_shldr_l": pattern_api.GetLineLength(pb, B["shldr_l"]),
|
|
"f_r_shldr": pattern_api.GetLineLength(pfr, F["shldr"]),
|
|
"f_l_shldr": pattern_api.GetLineLength(pfl, F["shldr"]),
|
|
"b_side_hi_r": pattern_api.GetLineLength(pb, B["side_hi_r"]),
|
|
"b_side_hi_l": pattern_api.GetLineLength(pb, B["side_hi_l"]),
|
|
"f_r_side_hi": pattern_api.GetLineLength(pfr, F["side_hi"]),
|
|
"f_l_side_hi": pattern_api.GetLineLength(pfl, F["side_hi"]),
|
|
"b_side_lo_r": pattern_api.GetLineLength(pb, B["side_lo_r"]),
|
|
"b_side_lo_l": pattern_api.GetLineLength(pb, B["side_lo_l"]),
|
|
"f_r_side_lo": pattern_api.GetLineLength(pfr, F["side_lo"]),
|
|
"f_l_side_lo": pattern_api.GetLineLength(pfl, F["side_lo"]),
|
|
"f_r_clasp": pattern_api.GetLineLength(pfr, F["clasp"]),
|
|
"f_l_clasp": pattern_api.GetLineLength(pfl, F["clasp"]),
|
|
}
|
|
report["neck_hole_mm"] = 2 * NECK_D + NECK_W + 2 * NECKLINE_H
|
|
except Exception as e:
|
|
report["line_length_error"] = "%s: %s" % (type(e).__name__, e)
|
|
|
|
# -- seams. RIGHT pairs traverse alike -> (F,F); LEFT pairs oppose -> ONE flip.
|
|
report["seams"] = {
|
|
"shldr_r": pattern_api.AddSeamlinePairGroup(pb, B["shldr_r"], pfr, F["shldr"], False, False),
|
|
"side_hi_r": pattern_api.AddSeamlinePairGroup(pb, B["side_hi_r"], pfr, F["side_hi"], False, False),
|
|
"side_lo_r": pattern_api.AddSeamlinePairGroup(pb, B["side_lo_r"], pfr, F["side_lo"], False, False),
|
|
"shldr_l": pattern_api.AddSeamlinePairGroup(pb, B["shldr_l"], pfl, F["shldr"], True, False),
|
|
"side_hi_l": pattern_api.AddSeamlinePairGroup(pb, B["side_hi_l"], pfl, F["side_hi"], True, False),
|
|
"side_lo_l": pattern_api.AddSeamlinePairGroup(pb, B["side_lo_l"], pfl, F["side_lo"], True, False),
|
|
}
|
|
if WITH_CLASP:
|
|
# throat closure: two TRUE mirrors, same index -> the documented (F,F) case
|
|
report["seams"]["clasp"] = pattern_api.AddSeamlinePairGroup(
|
|
pfr, F["clasp"], pfl, F["clasp"], False, False)
|
|
try:
|
|
report["seam_count"] = pattern_api.GetSeamlinePairGroupCount()
|
|
except Exception as e:
|
|
report["seam_count_err"] = "%s: %s" % (type(e).__name__, e)
|
|
|
|
fab = fabric_api.AddFabric(ZFAB) # never reuse index 0 -- it is the shared default
|
|
for p in (pb, pfr, pfl):
|
|
pattern_api.SetPatternPieceFabricIndex(p, fab)
|
|
for setter, val, key in ((pattern_api.SetParticleDistanceOfPattern, PARTICLE_MM, "particle"),
|
|
(pattern_api.SetAddlThicknessCollision, COLLISION_MM, "collision")):
|
|
try:
|
|
for p in (pb, pfr, pfl):
|
|
setter(p, val)
|
|
report[key] = val
|
|
except Exception as e:
|
|
report[key + "_error"] = "%s: %s" % (type(e).__name__, e)
|
|
|
|
# Keep the WHOLE arrangement entry -- the per-point default offsets are the payload.
|
|
arr = {str(a["ArrangementName"]): a for a in pattern_api.GetArrangementList()}
|
|
used = {}
|
|
for p, nm in ((pb, ARR_BACK), (pfr, ARR_FRONT_R), (pfl, ARR_FRONT_L)):
|
|
a = arr[nm]
|
|
pattern_api.SetArrangement(p, int(a["ArrangementIndex"]))
|
|
ox = int(a["ArrangementOffsetX"]) if USE_POINT_DEFAULT_X else 50
|
|
pattern_api.SetArrangementPosition(p, ox, int(ARR_Y), int(a["ArrangementOffsetZ"]))
|
|
pattern_api.SetPatternStrengthen(p, STRENGTHEN)
|
|
used[nm] = {"x": ox, "y": int(ARR_Y), "z": int(a["ArrangementOffsetZ"])}
|
|
report["arrangement_used"] = used
|
|
|
|
utility_api.ResetClothArrangement() # SetArrangement only ASSIGNS
|
|
|
|
utility_api.Refresh3DWindow()
|
|
utility_api.SetCamViewPoint(2)
|
|
export_api.ExportSnapshot3D(os.path.join(SHOTS, "cape_%s_presim_front.png" % VERSION))
|
|
utility_api.SetCamViewPoint(5)
|
|
utility_api.Refresh3DWindow()
|
|
export_api.ExportSnapshot3D(os.path.join(SHOTS, "cape_%s_presim_top.png" % VERSION))
|
|
|
|
report["strengthen"] = STRENGTHEN
|
|
report["sim1"] = utility_api.Simulate(SIM_MAIN)
|
|
if STRENGTHEN:
|
|
for p in (pb, pfr, pfl):
|
|
pattern_api.SetPatternStrengthen(p, False)
|
|
report["sim2"] = utility_api.Simulate(SIM_RELAX)
|
|
utility_api.Refresh3DWindow()
|
|
|
|
utility_api.SetCamViewPoint(2)
|
|
export_api.ExportSnapshot3D(os.path.join(SHOTS, "cape_%s_front.png" % VERSION))
|
|
utility_api.SetCamViewPoint(4)
|
|
utility_api.Refresh3DWindow()
|
|
export_api.ExportSnapshot3D(os.path.join(SHOTS, "cape_%s_side.png" % VERSION))
|
|
utility_api.SetCamViewPoint(2)
|
|
# The back. ExportSnapshot3D has NO rear viewpoint; for a cape this is the PRIMARY
|
|
# view. Turntable reuses output*.png, so the return value is the only handle.
|
|
report["turntable"] = export_api.ExportTurntableImages(4) # 0 front 1 side 2 BACK 3 side
|
|
|
|
mop = ApiTypes.ImportExportOption()
|
|
mop.bExportGarment = True
|
|
mop.bExportAvatar = False
|
|
probe = os.path.join(tempfile.gettempdir(), "tinqs_md_cape_%s_probe.obj" % VERSION)
|
|
export_api.ExportOBJ(probe, mop)
|
|
vs = []
|
|
with open(probe) as fh:
|
|
for line in fh:
|
|
if line.startswith("v "):
|
|
_, x, y, z = line.split()[:4]
|
|
vs.append((float(x), float(y), float(z)))
|
|
if vs:
|
|
S = 0.001 # ExportOBJ is MM; the FBX->census path is DECIMETRES
|
|
zs = sorted(v[1] * S for v in vs)
|
|
cx = sum(v[0] for v in vs) / len(vs)
|
|
cz = sum(v[2] for v in vs) / len(vs)
|
|
|
|
def band(lo, hi):
|
|
r = [(((v[0] - cx) ** 2 + (v[2] - cz) ** 2) ** 0.5) * S
|
|
for v in vs if lo <= v[1] * S <= hi]
|
|
if not r:
|
|
return None
|
|
r.sort()
|
|
return {"n": len(r), "r_med": round(r[len(r) // 2], 3), "r_max": round(r[-1], 3)}
|
|
|
|
report["measured"] = {
|
|
"verts": len(vs),
|
|
"top_m": round(zs[-1], 3),
|
|
"hem_m": round(zs[0], 3), # target 0.42 +-0.05
|
|
"span_m": round(zs[-1] - zs[0], 3),
|
|
"r_shoulder_1.34_1.40": band(1.34, 1.40),
|
|
"r_chest_1.22_1.30": band(1.22, 1.30),
|
|
"r_hip_0.90_0.96": band(0.90, 0.96),
|
|
"r_knee_0.50_0.56": band(0.50, 0.56),
|
|
}
|
|
|
|
if EXPORT:
|
|
base = os.path.join(OUT, "lena_cape_%s" % VERSION)
|
|
eop = ApiTypes.ImportExportOption()
|
|
eop.bExportGarment = True
|
|
eop.bExportAvatar = False
|
|
report["zprj"] = export_api.ExportZPrj(base + ".zprj")
|
|
report["fbx"] = export_api.ExportFBX(base + "_garment.fbx", eop)
|
|
report["obj"] = export_api.ExportOBJ(base + "_garment.obj", eop)
|
|
report["zpac"] = export_api.ExportZPac(base + ".zpac")
|
|
|
|
result = report
|