feat(hero): banner lettering, re-render the three staging shots; defer the rest to 0.9.0

Banner lettering (JIMMY KIMMEL LIVE): new Tools/gen/generate_banner_texture.py
draws the exact title with Pillow + Liberation Sans Bold - an image model would
mangle a trademark string, and Qwen-Image 2.1 is research-only. Gold JIMMY /
KIMMEL / LIVE stacked on a crimson ground. ue_hero_facade_import.py generalises
import_albedo to import_texture and wires the banner into M_Facade_Banner_Ref as
Base Color (tiling 1.0), replacing the flat dark tint with a brighter red per the
owner's 'less dark, more bright'.

Re-rendered all three ReviewMap staging shots (front, oblique, portals) and
updated their Screenshots/README.md status rows.

The hero facade rework is now closed at 'surface detail that reads at 45 m'. The
remaining elevation work - volute capital depth, lattice grille depth, and the
full realism pass - is deferred to 0.9.0 per the owner, and the 0.9.0 hero item
records the hand-off.
This commit is contained in:
zwitschi committed 2026-11-10 15:27:08 +01:00
1 parent bc9fed8e11
commit 8c34eaece0
26 files changed
+171 -41

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
View File
@@ -680,3 +680,18 @@ A visual change needs **build → import → render**, never just a render: `ren
- **The `seat_on_front` table had a dead duplicate** — `Hero_MottoBand` appeared at both - **The `seat_on_front` table had a dead duplicate** — `Hero_MottoBand` appeared at both
0.06 and 0.40, and the first match (0.06) won, so the 0.40 was silently ignored. That 0.06 and 0.40, and the first match (0.06) won, so the 0.40 was silently ignored. That
is why "the band is buried" looked fixed in the table while the build kept burying it. is why "the band is buried" looked fixed in the table while the build kept burying it.
## 2026-11-10 — Hero facade rework closed; remaining detail deferred to 0.9.0
- The hero facade rework (split out of 0.3.0) is **closed at "surface detail that reads
at 45 m"**. Done and committed: carved inscription, stepped medallions, light motto
band + proud surround (all three had been buried inside the parapet), and gold
JIMMY KIMMEL LIVE banner lettering.
- **Banner lettering is procedural, not ComfyUI.** `Tools/gen/generate_banner_texture.py`
draws the exact title with Pillow + Liberation Sans Bold. An image model (Qwen-Image
2.1) would mangle a trademark string and is research-only, so for a *known* string a
font rasteriser is strictly better than txt2img. Reserve ComfyUI/Qwen for things that
need a model, not for literal text.
- **Deferred to 0.9.0 (owner's instruction):** volute capital depth, lattice grille
depth, and the full realism pass (banded rustication, measured materials, texel density,
LOD/collision). The 0.9.0 "Hero façade (6840)" item records the hand-off.
+4 -4
View File
@@ -5,7 +5,7 @@ Two reviews in one file:
- **ReviewMap** stages imported assets on their own — individual objects, the Blender import, the camera angles and the lighting comparison. Nothing from the game map is involved. - **ReviewMap** stages imported assets on their own — individual objects, the Blender import, the camera angles and the lighting comparison. Nothing from the game map is involved.
- **DemoMap** is the actual game map as it is being developed: the street, the buildings, the player and the vehicle in their real placement. - **DemoMap** is the actual game map as it is being developed: the street, the buildings, the player and the vehicle in their real placement.
Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../Tools/render_screenshots.sh); the asset renders come from [`Tools/blender/blender_screenshots.py`](../Tools/blender_screenshots.py). Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../Tools/review/render_screenshots.sh); the asset renders come from [`Tools/blender/blender_screenshots.py`](../Tools/blender/blender_screenshots.py).
## Status ## Status
@@ -16,9 +16,9 @@ Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../To
| 1 | ReviewMap | ![character feet](review/review-character-feet.png) | [x] | approved | | 1 | ReviewMap | ![character feet](review/review-character-feet.png) | [x] | approved |
| 2 | ReviewMap | ![character](review/review-character.png) | [x] | approved | | 2 | ReviewMap | ![character](review/review-character.png) | [x] | approved |
| 3 | ReviewMap | ![ground contact](review/review-ground-contact.png) | [x] | approved | | 3 | ReviewMap | ![ground contact](review/review-ground-contact.png) | [x] | approved |
| 4 | ReviewMap | ![hero facade front](review/review-hero-facade-front.png) | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth | | 4 | ReviewMap | ![hero facade front](review/review-hero-facade-front.png) | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
| 5 | ReviewMap | ![hero facade oblique](review/review-hero-facade-oblique.png) | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth | | 5 | ReviewMap | ![hero facade oblique](review/review-hero-facade-oblique.png) | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
| 6 | ReviewMap | ![hero portals](review/review-hero-portals.png) | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth | | 6 | ReviewMap | ![hero portals](review/review-hero-portals.png) | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
| 7 | ReviewMap | ![npc prefab](review/review-npc-prefab.png) | [x] | | | 7 | ReviewMap | ![npc prefab](review/review-npc-prefab.png) | [x] | |
| 8 | ReviewMap | ![overview](review/review-overview.png) | [x] | approved | | 8 | ReviewMap | ![overview](review/review-overview.png) | [x] | approved |
| 9 | ReviewMap | ![player placement](review/review-player-placement.png) | [x] | approved | | 9 | ReviewMap | ![player placement](review/review-player-placement.png) | [x] | approved |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6 -4
View File
@@ -225,9 +225,11 @@ Open:
- [x] Carved inscription: `inscription()` in `Tools/blender/hero_facade_build.py` lays 112 letter marks in three lines across the attic, in the shadow stone so they read at 45 m. They had to be **seated on the parapet face** (`FRONT_Y + 0.40`): the parapet is 1.2 m deep and stands in front of the wall plane, so marks at the wall plane were hidden behind it. - [x] Carved inscription: `inscription()` in `Tools/blender/hero_facade_build.py` lays 112 letter marks in three lines across the attic, in the shadow stone so they read at 45 m. They had to be **seated on the parapet face** (`FRONT_Y + 0.40`): the parapet is 1.2 m deep and stands in front of the wall plane, so marks at the wall plane were hidden behind it.
- [x] Medallion relief that reads at 45 m. **Measured first, then fixed:** the discs were not just flat - they were **buried**. `seat_on_front` put `Hero_Medallion` at offset 0.08 → front face Y −0.42, while the parapet (1.2 m deep) stands at Y −0.15, so both medallions were inside the parapet and never rendered (the motto band and surround have the same bug at offset 0.06). Fixed: three stepped layers per medallion — `Hero_MedallionRim` (0.55 m, dark, offset 0.36 → Y −0.14), `Hero_MedallionField` (0.38 m, light, offset 0.43 → Y −0.07), `Hero_MedallionBoss` (0.15 m, dark, offset 0.50 → Y 0.00) — a 7 cm step each way so the rim/field/boss read as relief. Moved from ±6.2 (inside the inscription text, ±6.87) to ±9.8. Rendered and judged; the two-model judge is advisory-pessimistic (`no` 0.93) and does not flag medallions any more. Owner review pending. - [x] Medallion relief that reads at 45 m. **Measured first, then fixed:** the discs were not just flat - they were **buried**. `seat_on_front` put `Hero_Medallion` at offset 0.08 → front face Y −0.42, while the parapet (1.2 m deep) stands at Y −0.15, so both medallions were inside the parapet and never rendered (the motto band and surround have the same bug at offset 0.06). Fixed: three stepped layers per medallion — `Hero_MedallionRim` (0.55 m, dark, offset 0.36 → Y −0.14), `Hero_MedallionField` (0.38 m, light, offset 0.43 → Y −0.07), `Hero_MedallionBoss` (0.15 m, dark, offset 0.50 → Y 0.00) — a 7 cm step each way so the rim/field/boss read as relief. Moved from ±6.2 (inside the inscription text, ±6.87) to ±9.8. Rendered and judged; the two-model judge is advisory-pessimistic (`no` 0.93) and does not flag medallions any more. Owner review pending.
- [x] Motto band and surround — same burial bug (offset 0.06 → front Y −0.44). The band is now **light stone** (was `stone_dark`, which would have swallowed the dark lettering once visible), recessed at offset 0.34 (Y −0.16), with the moulded surround proud at offset 0.43 (Y −0.07) and the inscription at 0.40 (Y −0.10); the dead duplicate `Hero_MottoBand 0.40` entry is removed. Rendered and judged; the judge (`no` 0.93) no longer flags the band, only the stone warmth/column fluting the owner has already directed lighter. Owner review pending. - [x] Motto band and surround — same burial bug (offset 0.06 → front Y −0.44). The band is now **light stone** (was `stone_dark`, which would have swallowed the dark lettering once visible), recessed at offset 0.34 (Y −0.16), with the moulded surround proud at offset 0.43 (Y −0.07) and the inscription at 0.40 (Y −0.10); the dead duplicate `Hero_MottoBand 0.40` entry is removed. Rendered and judged; the judge (`no` 0.93) no longer flags the band, only the stone warmth/column fluting the owner has already directed lighter. Owner review pending.
- [ ] Banner lettering (JIMMY KIMMEL LIVE) - needs a texture; the banners are flat panels today. - [x] Banner lettering (JIMMY KIMMEL LIVE). New `Tools/gen/generate_banner_texture.py` draws the exact title with Pillow + Liberation Sans Bold (an image model would mangle a trademark string, and Qwen-Image 2.1 is research-only). Gold `JIMMY` / `KIMMEL` / `LIVE` stacked on a crimson ground, 512 × 2304. `ue_hero_facade_import.py` generalised `import_albedo` → `import_texture` and wires the banner into `M_Facade_Banner_Ref` as Base Color (tiling 1.0). Owner review pending.
- [ ] Crimson banner tone and the JIMMY KIMMEL LIVE lettering. - [x] Crimson banner tone + lettering. The texture carries the red ground (186,24,24 — brighter than the old dark tint, per the owner's "less dark, more bright") and the gold lettering, so the flat tint is replaced outright.
- [ ] Re-render the three ReviewMap staging shots and put them back in the status table for review. - [x] Re-render the three ReviewMap staging shots and put them back in the status table for review. All three (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) regenerated 2026-11-10 with the full attic detail, and the `Screenshots/README.md` status table updated. Owner review pending.
**Deferred to `0.9.0` (owner, 2026-11-10).** This pass is closed at "surface detail that reads at 45 m". The remaining elevation work is a `0.9.0` pass, not another patch here: **volute capital depth** (the capitals are block-only, the reference has strong shadow lines), **lattice grille depth** (the window bays are plain panels with bars, not the dark metal lattice), and the full realism pass (heavy banded rustication, measured material library, texel-density and LOD/collision per building). The `0.9.0` hero item records the hand-off.
## 0.3.0 — Chaos vehicle, enter/exit, closed driving route (weeks 10–13) ## 0.3.0 — Chaos vehicle, enter/exit, closed driving route (weeks 10–13)
@@ -485,7 +487,7 @@ Suggested blender skills: **archviz** (real-world scale, façade composition, el
Each building needs its own reference set; where none exists the blockout stays and the item says so. Each building needs its own reference set; where none exists the blockout stays and the item says so.
- [ ] Hero façade (6840): raise to this pass's detail level — capitals and fluting, window rhythm behind the colonnade, parapet motto band and medallions. - [ ] Hero façade (6840): raise to this pass's detail level — **volute capital depth** and **lattice grille depth** (both deferred from the 2026-11-10 hero rework, which closed the inscription/medallions/motto-band/banner surface), window rhythm behind the colonnade, and the heavy banded rustication the elevation still lacks. The motto band, medallions and banner lettering are already done and carry over.
- [ ] Hero façade (6840), entrance: **doors at street level left and right of the central bay**, as the photographs show, and correct positioning of the central-bay elements (column bases, entrance stairs, set-back entrance, door frames, banners and signage). Nothing may stand in front of the façade that blocks it — no awning, no temporary structure. Carried over from `Screenshots/README.md`, 2026-11-09. - [ ] Hero façade (6840), entrance: **doors at street level left and right of the central bay**, as the photographs show, and correct positioning of the central-bay elements (column bases, entrance stairs, set-back entrance, door frames, banners and signage). Nothing may stand in front of the façade that blocks it — no awning, no temporary structure. Carried over from `Screenshots/README.md`, 2026-11-09.
- [ ] El Capitan Theatre (6838): marquee, blade sign, entrance and the theatre's own elevation from its 18-frame set. - [ ] El Capitan Theatre (6838): marquee, blade sign, entrance and the theatre's own elevation from its 18-frame set.
- [ ] 6800 Lee Drug and 6806–6820: second pass on the `0.1.6` trim. Moved here from that patch because geometry cannot close them: **wall lettering as a decal** (a plate at 0.10 m still vanishes at 40 m), a **moulded parapet profile** instead of a two-tier box, richer **corner-tower stepping**, and the portrait panels reworked as textures rather than framed plates. - [ ] 6800 Lee Drug and 6806–6820: second pass on the `0.1.6` trim. Moved here from that patch because geometry cannot close them: **wall lettering as a decal** (a plate at 0.10 m still vanishes at 40 m), a **moulded parapet profile** instead of a two-tier box, richer **corner-tower stepping**, and the portrait panels reworked as textures rather than framed plates.
+1
View File
@@ -105,6 +105,7 @@ Scripts resolve the repository root as `../..` from their own folder and put the
| [`gen/comfyui_generate.py`](gen/comfyui_generate.py) | Drives the **local** ComfyUI at `http://127.0.0.1:8188` (Stability Matrix install). `models` lists checkpoints; `txt2img` renders through the HTTP API; **`texture` renders material sheets with the project's documented texture workflows** (see below). Every run writes a provenance sidecar (model, prompt, seed, settings, full graph). Two candidates by default — same prompt and seed, both scored by high-frequency detail, the winner takes `--out`, the loser is kept beside it. Local and human-triggered; no credential. | | [`gen/comfyui_generate.py`](gen/comfyui_generate.py) | Drives the **local** ComfyUI at `http://127.0.0.1:8188` (Stability Matrix install). `models` lists checkpoints; `txt2img` renders through the HTTP API; **`texture` renders material sheets with the project's documented texture workflows** (see below). Every run writes a provenance sidecar (model, prompt, seed, settings, full graph). Two candidates by default — same prompt and seed, both scored by high-frequency detail, the winner takes `--out`, the loser is kept beside it. Local and human-triggered; no credential. |
| [`gen/generate_facade_textures.py`](gen/generate_facade_textures.py) | Procedural, deterministic facade texture maps (ashlar base colour + roughness, stucco, asphalt, concrete) written with a pure-zlib PNG encoder — no model, no network, byte-identical on every run. | | [`gen/generate_facade_textures.py`](gen/generate_facade_textures.py) | Procedural, deterministic facade texture maps (ashlar base colour + roughness, stucco, asphalt, concrete) written with a pure-zlib PNG encoder — no model, no network, byte-identical on every run. |
| [`gen/generate_character_textures.py`](gen/generate_character_textures.py) | Procedural character texture maps, same deterministic approach as the facade set. | | [`gen/generate_character_textures.py`](gen/generate_character_textures.py) | Procedural character texture maps, same deterministic approach as the facade set. |
| [`gen/generate_banner_texture.py`](gen/generate_banner_texture.py) | Draws the hero-facade banner (`JIMMY KIMMEL LIVE` in gold on crimson) with Pillow + a system font — exact text, deterministic, no model. An image model would mangle a trademark string, and Qwen-Image 2.1 is research-only, so this is the lettering path. `--out Art/textures/generated/T_Banner_KIMMEL.png` by default. |
| [`gen/facade_detail_concepts.py`](gen/facade_detail_concepts.py) | Generates **frontal building plates** (whole elevation, entrance in its facade, or building with neighbours) from a local reference photograph with an image model (OpenRouter); close-up detail crops are deliberately not requested because they cannot be measured for proportion. `--list` is free and shows which studies have their reference present; `--all`/`--only <name>` generates. Never overwrites (writes `-v2`, `-v3`), writes a `.refgen.json` sidecar per image (model, prompt, source photo, SHA-256), and puts everything under `Art/reference/facade-details/` — gitignored, local-only, never shipped. Costs money per image, so it is never automatic. | | [`gen/facade_detail_concepts.py`](gen/facade_detail_concepts.py) | Generates **frontal building plates** (whole elevation, entrance in its facade, or building with neighbours) from a local reference photograph with an image model (OpenRouter); close-up detail crops are deliberately not requested because they cannot be measured for proportion. `--list` is free and shows which studies have their reference present; `--all`/`--only <name>` generates. Never overwrites (writes `-v2`, `-v3`), writes a `.refgen.json` sidecar per image (model, prompt, source photo, SHA-256), and puts everything under `Art/reference/facade-details/` — gitignored, local-only, never shipped. Costs money per image, so it is never automatic. |
| [`review/judge_screenshot.py`](review/judge_screenshot.py) | (see Visual review) | | [`review/judge_screenshot.py`](review/judge_screenshot.py) | (see Visual review) |
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Generate the JIMMY KIMMEL LIVE banner texture for the hero facade.
The three event banners are flat panels; the lettering is text and cannot be
read as geometry at 45 m. An image model would mangle a specific trademark
string, so the text is drawn here with Pillow and a system font instead:
deterministic, exact, and (unlike Qwen-Image 2.1) not research-only.
The banner face is 1.35 m wide x 6.2 m tall (aspect 1:4.59). The texture is
written at 512 x 2304 (aspect 1:4.5), a 2% stretch that is invisible at the
review distance.
python3 Tools/gen/generate_banner_texture.py
python3 Tools/gen/generate_banner_texture.py --out Art/textures/generated/T_Banner_KIMMEL.png
Requires Pillow (host-only tool, not a gate; standard library is not enough to
rasterise a font). The text is a trademark reproduced for internal development
review only - record it in THIRD_PARTY_NOTICES.md before any public build.
"""
from __future__ import annotations
import argparse
import os
from PIL import Image, ImageDraw, ImageFont
# Banner face aspect: 1.35 m wide x 6.2 m tall. 512 x 2304 keeps that to within 2 %.
WIDTH = 512
HEIGHT = 2304
# sRGB colours. The banner is a deep event red, the lettering is gold; both match the
# photographs (and the owner's "less dark, more bright" direction for the elevation).
BANNER = (186, 24, 24)
GOLD = (232, 198, 90)
FONT_BOLD = "/usr/share/fonts/liberation-sans-fonts/LiberationSans-Bold.ttf"
FONT_REGULAR = "/usr/share/fonts/liberation-sans-fonts/LiberationSans-Regular.ttf"
# Three stacked words, as the real vertical banners arrange the title. `size` is the
# font height in texture pixels; the block is centred vertically in the banner.
LINES = [
("JIMMY", 150, FONT_BOLD),
("KIMMEL", 150, FONT_BOLD),
("LIVE", 120, FONT_REGULAR),
]
LINE_GAP = 70 # pixels between baselines beyond the font ascent
def _font(path: str, size: int) -> ImageFont.FreeTypeFont:
if not os.path.exists(path):
raise SystemExit(f"font missing: {path}")
return ImageFont.truetype(path, size)
def draw_banner(out: str) -> None:
img = Image.new("RGB", (WIDTH, HEIGHT), BANNER)
draw = ImageDraw.Draw(img)
fonts = [(text, size, _font(path, size)) for text, size, path in LINES]
# Measure the whole block so it sits centred vertically in the 6.2 m banner.
ascents = [f.getbbox(text)[3] for text, _, f in fonts]
block_h = sum(ascents) + LINE_GAP * (len(fonts) - 1)
y = (HEIGHT - block_h) // 2
for text, size, font in fonts:
bbox = font.getbbox(text)
text_w = bbox[2] - bbox[0]
x = (WIDTH - text_w) // 2 - bbox[0]
draw.text((x, y), text, font=font, fill=GOLD)
y += bbox[3] + LINE_GAP
os.makedirs(os.path.dirname(os.path.abspath(out)) or ".", exist_ok=True)
img.save(out)
print(f"wrote {out} ({WIDTH}x{HEIGHT})")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--out", default="Art/textures/generated/T_Banner_KIMMEL.png")
args = parser.parse_args()
draw_banner(args.out)
if __name__ == "__main__":
main()
+27 -9
View File
@@ -47,6 +47,8 @@ def log(msg):
# reference facade is coursed rubble-ashlar, not a flat tint, and a texture is what carries that at 45 m. # reference facade is coursed rubble-ashlar, not a flat tint, and a texture is what carries that at 45 m.
FACADE_ALBEDO = "Art/textures/generated/T_FacadeAshlar_AI.png" FACADE_ALBEDO = "Art/textures/generated/T_FacadeAshlar_AI.png"
FACADE_ALBEDO_ASSET = "T_FacadeAshlar_AI" FACADE_ALBEDO_ASSET = "T_FacadeAshlar_AI"
BANNER_ALBEDO = "Art/textures/generated/T_Banner_KIMMEL.png"
BANNER_ALBEDO_ASSET = "T_Banner_KIMMEL"
TEXTURE_DIR = "/Game/Hollywood/Textures" TEXTURE_DIR = "/Game/Hollywood/Textures"
FACADE_TINTS = { FACADE_TINTS = {
@@ -67,26 +69,26 @@ FACADE_TINTS = {
} }
def import_albedo(): def import_texture(rel_path, asset_name):
"""Import the generated ashlar albedo, if it is on disk. Returns the asset or None.""" """Import a generated texture, if it is on disk. Returns the asset or None."""
# unreal.Paths.project_dir() is Game/GigLine, not the repository root: the textures live beside Art/, # unreal.Paths.project_dir() is Game/GigLine, not the repository root: the textures live beside Art/,
# two levels above this file. # two levels above this file.
# Tools/unreal/<this file> -> repository root is three levels up. # Tools/unreal/<this file> -> repository root is three levels up.
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
path = os.path.join(repo_root, FACADE_ALBEDO) path = os.path.join(repo_root, rel_path)
if not os.path.exists(path): if not os.path.exists(path):
log(f"albedo missing: {path}") log(f"texture missing: {path}")
return None return None
task = unreal.AssetImportTask() task = unreal.AssetImportTask()
task.filename = path task.filename = path
task.destination_path = TEXTURE_DIR task.destination_path = TEXTURE_DIR
task.destination_name = FACADE_ALBEDO_ASSET task.destination_name = asset_name
task.automated = True task.automated = True
task.save = True task.save = True
task.replace_existing = True task.replace_existing = True
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
asset = unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{FACADE_ALBEDO_ASSET}") asset = unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{asset_name}")
log(f"albedo imported: {asset.get_path_name() if asset else 'FAILED'}") log(f"texture imported: {asset.get_path_name() if asset else 'FAILED'}")
return asset return asset
@@ -102,7 +104,8 @@ def tint_imported_materials():
and the owner's review was that the elevation read near-white grey. and the owner's review was that the elevation read near-white grey.
""" """
library = unreal.MaterialEditingLibrary library = unreal.MaterialEditingLibrary
albedo = import_albedo() albedo = import_texture(FACADE_ALBEDO, FACADE_ALBEDO_ASSET)
banner_tex = import_texture(BANNER_ALBEDO, BANNER_ALBEDO_ASSET)
built = 0 built = 0
for base, rgb in FACADE_TINTS.items(): for base, rgb in FACADE_TINTS.items():
path = f"{DEST}/{base}_Ref" path = f"{DEST}/{base}_Ref"
@@ -114,6 +117,7 @@ def tint_imported_materials():
material, unreal.MaterialExpressionConstant3Vector, -600, 200) material, unreal.MaterialExpressionConstant3Vector, -600, 200)
colour.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0)) colour.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0))
source = colour source = colour
kind = "tint"
if albedo is not None and base in ("M_Facade_Stone", "M_Facade_StoneShadow"): if albedo is not None and base in ("M_Facade_Stone", "M_Facade_StoneShadow"):
sample = library.create_material_expression( sample = library.create_material_expression(
material, unreal.MaterialExpressionTextureSampleParameter2D, -900, -100) material, unreal.MaterialExpressionTextureSampleParameter2D, -900, -100)
@@ -130,11 +134,25 @@ def tint_imported_materials():
library.connect_material_expressions(sample, "RGB", multiply, "A") library.connect_material_expressions(sample, "RGB", multiply, "A")
library.connect_material_expressions(colour, "", multiply, "B") library.connect_material_expressions(colour, "", multiply, "B")
source = multiply source = multiply
kind = "albedo x tint"
elif banner_tex is not None and base == "M_Facade_Banner":
# The banner texture carries both the red ground and the gold lettering, so it
# replaces the tint outright (tiling 1.0, not the 6x stone coursing).
sample = library.create_material_expression(
material, unreal.MaterialExpressionTextureSampleParameter2D, -900, -100)
sample.set_editor_property("parameter_name", f"Banner_{base}")
sample.set_editor_property("texture", banner_tex)
sample.set_editor_property("sampler_type", unreal.MaterialSamplerType.SAMPLERTYPE_COLOR)
uvs = library.create_material_expression(
material, unreal.MaterialExpressionTextureCoordinate, -1150, -100)
library.connect_material_expressions(uvs, "", sample, "UVs")
source = sample
kind = "banner"
library.connect_material_property(source, "", unreal.MaterialProperty.MP_BASE_COLOR) library.connect_material_property(source, "", unreal.MaterialProperty.MP_BASE_COLOR)
library.recompile_material(material) library.recompile_material(material)
saved = unreal.EditorAssetLibrary.save_loaded_asset(material) saved = unreal.EditorAssetLibrary.save_loaded_asset(material)
built += 1 built += 1
log(f"{base}_Ref: {'albedo x tint' if source is not colour else 'tint'} {rgb}, saved {saved}") log(f"{base}_Ref: {kind} {rgb}, saved {saved}")
log(f"authored {built} facade materials") log(f"authored {built} facade materials")