feat: clothing lane, character sources, and DCC bridges
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>
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user