backup befor new branch to test Blockbenh instead of regex renderer

This commit is contained in:
2026-08-04 16:04:30 -05:00
parent f59e9a165c
commit 8c17c273fe
11 changed files with 376 additions and 19 deletions
@@ -0,0 +1,11 @@
from django.core.management.base import BaseCommand
from library.vanilla_textures import refresh_vanilla_index
class Command(BaseCommand):
help = 'Rebuild the vanilla entity-texture index (media/vanilla/entity_index.json).'
def handle(self, *args, **options):
index = refresh_vanilla_index()
self.stdout.write(self.style.SUCCESS(f'Indexed {len(index)} entities.'))
+1
View File
@@ -15,6 +15,7 @@ urlpatterns = [
path('packs/<slug:slug>/versions/<int:version_id>/download/', views.version_download, name='version_download'),
path('packs/<slug:slug>/versions/<int:version_id>/files/<uuid:file_uuid>/download/', views.version_file_download, name='version_file_download'),
path('packs/<slug:slug>/versions/<int:version_id>/asset/<path:asset_path>', views.pack_asset, name='pack_asset'),
path('vanilla/<path:asset_path>', views.vanilla_asset, name='vanilla_asset'),
path('packs/<slug:slug>/guide/<int:version_id>/<uuid:file_uuid>/', views.guide_doc, name='guide_doc'),
path('packs/<slug:slug>/gallery/upload/', views.asset_upload, name='asset_upload'),
path('packs/<slug:slug>/gallery/<int:asset_id>/thumb/', views.asset_thumbnail, name='asset_thumbnail'),
+107
View File
@@ -0,0 +1,107 @@
"""Build the vanilla entity-texture index for the model viewer.
Scans ``MEDIA_ROOT/vanilla/entity/`` (extracted from a Minecraft client jar)
and writes ``MEDIA_ROOT/vanilla/entity_index.json`` mapping each entity name to
the best "base skin" relative path, so the renderer can fall back to real
vanilla textures for models that have no pack textures of their own.
"""
import json
import os
from django.conf import settings
ENTITY_ROOT = 'entity'
INDEX_NAME = 'entity_index.json'
# Entity names whose vanilla layout differs from the model-name convention.
ALIASES = {
'polar_bear': 'bear/polarbear.png',
'cave_spider': 'spider/cave_spider.png',
'husk': 'zombie/husk.png',
'drowned': 'zombie/drowned.png',
'stray': 'skeleton/stray.png',
'zoglin': 'hoglin/zoglin.png',
'piglin_brute': 'piglin/piglin_brute.png',
'zombified_piglin': 'piglin/zombified_piglin.png',
'horse': 'horse/horse_brown.png',
'donkey': 'horse/donkey.png',
'mule': 'horse/mule.png',
'skeleton_horse': 'horse/horse_skeleton.png',
'zombie_horse': 'horse/horse_zombie.png',
'evoker': 'illager/evoker.png',
'pillager': 'illager/pillager.png',
'vex': 'illager/vex.png',
'vindicator': 'illager/vindicator.png',
'ravager': 'illager/ravager.png',
'cat': 'cat/tabby.png',
'cow': 'cow/temperate_cow.png',
'pig': 'pig/temperate_pig.png',
'slime_size_0': 'slime/slime.png',
'slime_size_1': 'slime/slime.png',
'slime_size_3': 'slime/slime.png',
'player': 'player/wide/steve.png',
'player_slim': 'player/slim/steve.png',
}
# Texture names that are auxiliary layers, never a base skin.
_LAYER_KEYWORDS = (
'overlay', '_eyes', '_armor', '_collar', '_markings', '_tame', '_angry',
'_saddle', '_crack', '_outer', '_inner', '_layer', '_emissive', '_pelt',
'_fur', '_shell', '_crackiness',
)
def _base_candidates(entity):
"""Return (entity, [candidate relative paths]) in priority order."""
if entity in ALIASES:
return entity, [ALIASES[entity]]
root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT)
top = os.path.join(root, entity + '.png')
if os.path.isfile(top):
return entity, [entity + '.png']
folder = os.path.join(root, entity)
if not os.path.isdir(folder):
return entity, []
try:
names = sorted(os.listdir(folder))
except OSError:
return entity, []
pngs = [n for n in names if n.endswith('.png')]
exact = [f'{entity}/{n}' for n in pngs if n == f'{entity}.png']
default = [f'{entity}/{n}' for n in pngs if n in ('temperate_' + entity + '.png', 'default.png')]
plain = [
f'{entity}/{n}' for n in pngs
if not any(k in n for k in _LAYER_KEYWORDS)
]
return entity, exact + default + plain + [f'{entity}/{n}' for n in pngs]
def refresh_vanilla_index():
"""Rebuild the entity → base-skin map from the extracted vanilla set."""
root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT)
if not os.path.isdir(root):
raise FileNotFoundError(f'{root} missing — extract vanilla entity textures first')
index = {}
for entry in sorted(os.listdir(root)):
rel = os.path.join(root, entry)
if entry.endswith('.png'):
entity = entry[:-4]
if not index.get(entity):
index[entity] = entry
continue
if not os.path.isdir(rel):
continue
entity = entry
_, candidates = _base_candidates(entity)
if candidates and not index.get(entity):
index[entity] = candidates[0]
for entity, rel in ALIASES.items():
if not index.get(entity):
index[entity] = rel
out = os.path.join(settings.MEDIA_ROOT, 'vanilla', INDEX_NAME)
with open(out, 'w') as f:
json.dump(index, f, indent=1, sort_keys=True)
return index
+13
View File
@@ -423,6 +423,19 @@ def api_packs_latest(request, namespace, pack_id):
})
def vanilla_asset(request, asset_path):
"""Serve a bundled Minecraft vanilla resource (e.g. entity textures) — gated."""
if not asset_path or asset_path.startswith('/') or '..' in asset_path.split('/'):
raise Http404
full = Path(settings.MEDIA_ROOT) / 'vanilla' / asset_path
if not full.is_file():
raise Http404
content_type = mimetypes.guess_type(asset_path)[0] or 'application/octet-stream'
response = HttpResponse(full.read_bytes(), content_type=content_type)
response['Cache-Control'] = 'public, max-age=86400'
return response
def guide_doc(request, slug, version_id, file_uuid):
"""Render one markdown guide document server-side (used by the switcher)."""
version = get_object_or_404(
+19
View File
@@ -275,10 +275,29 @@ def read_models_manifest(path):
for bone in geom.get('bones') or []:
bone_count += 1
cube_count += len(bone.get('cubes') or [])
entity = name.rsplit('/', 1)[-1]
if entity.endswith('.geo.json'):
entity = entity[:-len('.geo.json')]
for suffix in ('.mf', '.fm', '.m', '.f', '.g'):
if entity.endswith(suffix):
entity = entity[:-len(suffix)]
break
# A pack-provided full skin is the model's default texture when the
# model has no per-bone textures of its own.
default_texture = ''
if not bone_textures and entity:
for n in names:
if n.endswith(f'/textures/entity/{entity}/{entity}.png') or n.endswith(f'/textures/entity/{entity}.png'):
default_texture = n
break
models.append({
'name': name.rsplit('/', 1)[-1],
'member': name,
'identifier': identifier,
'entity': entity,
'default_texture': default_texture,
'bones': bone_count,
'cubes': cube_count,
'bone_textures': bone_textures,