# 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