From 769ab86ae2e02ab087d7ddf8a6c6622622c0c6f0 Mon Sep 17 00:00:00 2001 From: BRADSEC <7948876+bradsec@users.noreply.github.com> Date: Sun, 21 Jun 2026 19:43:58 +1000 Subject: [PATCH] feat: render shoulders, chest, bust in description and output pins --- nodes.py | 32 ++++++++++++++++++++++++++++---- test_random_person.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/nodes.py b/nodes.py index f06c787..f594184 100644 --- a/nodes.py +++ b/nodes.py @@ -164,7 +164,8 @@ _EXTRA_TIP = "Free text appended to the description as-is, comma-separated. Clo RETURN_NAMES = ("description", "sex", "age", "nationality", "complexion", "face", "hair", "facial_hair", "body_type", - "expression", "accessories", "makeup", "seed") + "expression", "accessories", "makeup", + "shoulders", "chest", "bust", "seed") # -- Selection logic ----------------------------------------------------------- @@ -283,7 +284,8 @@ def build_description(sex, nat, age, skin_texture, face_shape, complexion, eyes, nose_shape, mouth_shape, face_feature, hair_color, hair_style, hair_length, facial_hair, body, expression, accessories, makeup, extra_attributes="", - eye_shape=None, eyebrows=None): + eye_shape=None, eyebrows=None, + shoulders=None, chest=None, bust_size=None, bust_shape=None): """Produce a single comma-separated descriptor string.""" parts = [] @@ -333,6 +335,16 @@ def build_description(sex, nat, age, skin_texture, build_str = d(body) parts.append(build_str if build_str.lower().endswith("build") else f"{build_str} build") + add(shoulders) + add(chest) + + size_str = d(bust_size).strip() if bust_size else "" + shape_str = d(bust_shape).strip() if bust_shape else "" + if size_str and shape_str: + parts.append(f"a {size_str}, {shape_str} bust") + elif size_str or shape_str: + parts.append(f"a {size_str or shape_str} bust") + add(expression) add(accessories) add(makeup) @@ -376,7 +388,9 @@ def generate_person(seed, randomize, sex, category_args, p["face_shape"], p["complexion"], p["eyes"], p["nose_shape"], p["mouth_shape"], p["face_feature"], p["hair_color"], p["hair_style"], p["hair_length"], p["facial_hair"], p["body_type"], p["expression"], p["accessories"], p["makeup"], - extra_attributes, eye_shape=p["eye_shape"], eyebrows=p["eyebrows"]) + extra_attributes, eye_shape=p["eye_shape"], eyebrows=p["eyebrows"], + shoulders=p["shoulders"], chest=p["chest"], + bust_size=p["bust_size"], bust_shape=p["bust_shape"]) def dd(key, sub_key="description"): item = p[key] @@ -404,9 +418,19 @@ def generate_person(seed, randomize, sex, category_args, dd("face_feature"), ])) + size_str = dd("bust_size") + shape_str = dd("bust_shape") + if size_str and shape_str: + out_bust = f"a {size_str}, {shape_str} bust" + elif size_str or shape_str: + out_bust = f"a {size_str or shape_str} bust" + else: + out_bust = "" + return (description, resolved_sex, out_age, out_nat, out_comp, out_face, out_hair, dd("facial_hair"), dd("body_type"), - dd("expression"), dd("accessories"), dd("makeup"), seed) + dd("expression"), dd("accessories"), dd("makeup"), + dd("shoulders"), dd("chest"), out_bust, seed) def _collect_category_args(kwargs): diff --git a/test_random_person.py b/test_random_person.py index 2bef59b..5f8de7a 100644 --- a/test_random_person.py +++ b/test_random_person.py @@ -192,6 +192,44 @@ class TestBodyData(unittest.TestCase): for key in self.BODY_KEYS: self.assertIn(key, core.SEX_GATED, key) + def test_chest_in_description_male(self): + out = run(sex="male", chest=("fixed", "", "broad")) + self.assertIn("a broad chest", out[0]) + + def test_bust_combines_size_and_shape_female(self): + out = run(sex="female", + bust_size=("fixed", "", "full"), + bust_shape=("fixed", "", "round")) + self.assertIn("a full, round bust", out[0]) + + def test_bust_size_only_female(self): + out = run(sex="female", bust_size=("fixed", "", "petite"), + bust_shape=("off", "", "(none)")) + self.assertIn("a petite bust", out[0]) + + def test_shoulders_pin_present(self): + names = core.RETURN_NAMES + self.assertIn("shoulders", names) + self.assertIn("chest", names) + self.assertIn("bust", names) + out = run(sex="male", shoulders=("fixed", "", "broad")) + idx = names.index("shoulders") + self.assertEqual(out[idx], "broad shoulders") + + def test_male_never_grows_bust(self): + for seed in range(30): + out = run(seed=seed, sex="male", + bust_size=("random", "", "(none)"), + bust_shape=("random", "", "(none)")) + bust_idx = core.RETURN_NAMES.index("bust") + self.assertEqual(out[bust_idx], "", seed) + + def test_female_never_grows_chest(self): + for seed in range(30): + out = run(seed=seed, sex="female", chest=("random", "", "(none)")) + chest_idx = core.RETURN_NAMES.index("chest") + self.assertEqual(out[chest_idx], "", seed) + if __name__ == "__main__": unittest.main()