finishing with Phase 2

This commit is contained in:
JakeBreath
2026-08-04 19:05:38 -05:00
parent b1ae2a9827
commit 8a6833def0
10 changed files with 43 additions and 23 deletions
+1
View File
@@ -5,5 +5,6 @@ Packs_DB
.migrations_done .migrations_done
nonpacks/staticfiles/ nonpacks/staticfiles/
nonpacks/media/ nonpacks/media/
nonpacks/static/vanilla/entity_index.json
*.log *.log
AGENTS/ AGENTS/
@@ -4,7 +4,7 @@ from library.vanilla_textures import refresh_vanilla_index
class Command(BaseCommand): class Command(BaseCommand):
help = 'Rebuild the vanilla entity-texture index (media/vanilla/entity_index.json).' help = 'Rebuild the vanilla entity-texture index (static/vanilla/entity_index.json).'
def handle(self, *args, **options): def handle(self, *args, **options):
index = refresh_vanilla_index() index = refresh_vanilla_index()
-1
View File
@@ -15,7 +15,6 @@ urlpatterns = [
path('packs/<slug:slug>/versions/<int:version_id>/download/', views.version_download, name='version_download'), 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>/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('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>/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/upload/', views.asset_upload, name='asset_upload'),
path('packs/<slug:slug>/gallery/<int:asset_id>/thumb/', views.asset_thumbnail, name='asset_thumbnail'), path('packs/<slug:slug>/gallery/<int:asset_id>/thumb/', views.asset_thumbnail, name='asset_thumbnail'),
+13 -7
View File
@@ -1,9 +1,10 @@
"""Build the vanilla entity-texture index for the model viewer. """Build the vanilla entity-texture index for the model viewer.
Scans ``MEDIA_ROOT/vanilla/entity/`` (extracted from a Minecraft client jar) Scans ``static/vanilla/entity/`` (extracted from a Minecraft client jar,
and writes ``MEDIA_ROOT/vanilla/entity_index.json`` mapping each entity name to tracked in git) and writes ``static/vanilla/entity_index.json`` mapping each
the best "base skin" relative path, so the renderer can fall back to real entity name to the best "base skin" relative path, so the renderer can fall
vanilla textures for models that have no pack textures of their own. back to real vanilla textures for models that have no pack textures of their
own.
""" """
import json import json
import os import os
@@ -13,6 +14,11 @@ from django.conf import settings
ENTITY_ROOT = 'entity' ENTITY_ROOT = 'entity'
INDEX_NAME = 'entity_index.json' INDEX_NAME = 'entity_index.json'
def vanilla_dir():
"""The tracked static directory holding the vanilla texture set."""
return os.path.join(settings.BASE_DIR, 'static', 'vanilla')
# Entity names whose vanilla layout differs from the model-name convention. # Entity names whose vanilla layout differs from the model-name convention.
ALIASES = { ALIASES = {
'polar_bear': 'bear/polarbear.png', 'polar_bear': 'bear/polarbear.png',
@@ -75,7 +81,7 @@ def _base_candidates(entity):
"""Return (entity, [candidate relative paths]) in priority order.""" """Return (entity, [candidate relative paths]) in priority order."""
if entity in ALIASES: if entity in ALIASES:
return entity, [ALIASES[entity]] return entity, [ALIASES[entity]]
root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT) root = os.path.join(vanilla_dir(), ENTITY_ROOT)
top = os.path.join(root, entity + '.png') top = os.path.join(root, entity + '.png')
if os.path.isfile(top): if os.path.isfile(top):
return entity, [entity + '.png'] return entity, [entity + '.png']
@@ -98,7 +104,7 @@ def _base_candidates(entity):
def refresh_vanilla_index(): def refresh_vanilla_index():
"""Rebuild the entity → {default, variants} skin map from the vanilla set.""" """Rebuild the entity → {default, variants} skin map from the vanilla set."""
root = os.path.join(settings.MEDIA_ROOT, 'vanilla', ENTITY_ROOT) root = os.path.join(vanilla_dir(), ENTITY_ROOT)
if not os.path.isdir(root): if not os.path.isdir(root):
raise FileNotFoundError(f'{root} missing — extract vanilla entity textures first') raise FileNotFoundError(f'{root} missing — extract vanilla entity textures first')
@@ -125,7 +131,7 @@ def refresh_vanilla_index():
index[entity]['default'] = variants[0] index[entity]['default'] = variants[0]
index[entity]['variants'] = variants index[entity]['variants'] = variants
out = os.path.join(settings.MEDIA_ROOT, 'vanilla', INDEX_NAME) out = os.path.join(vanilla_dir(), INDEX_NAME)
with open(out, 'w') as f: with open(out, 'w') as f:
json.dump(index, f, indent=1, sort_keys=True) json.dump(index, f, indent=1, sort_keys=True)
return index return index
-13
View File
@@ -423,19 +423,6 @@ 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): def guide_doc(request, slug, version_id, file_uuid):
"""Render one markdown guide document server-side (used by the switcher).""" """Render one markdown guide document server-side (used by the switcher)."""
version = get_object_or_404( version = get_object_or_404(
+12
View File
@@ -147,6 +147,18 @@
} }
} }
// The vanilla/base texture must always be loaded first, then features. // The vanilla/base texture must always be loaded first, then features.
// Wide player models additionally always load L1Z0's custom skins
// alongside the default Steve skin (tracked in static/player_skins/).
if (entity === 'player') {
for (const name of ['destroyed_skin.png', 'russian_goat.png']) {
try {
const img = await loadImage('/static/player_skins/' + name);
baseTextures.push({ name: name, dataUrl: imageDataUrl(img) });
} catch (e) {
console.warn('packs: skip player skin', name, e);
}
}
}
return baseTextures.concat(featureTextures); return baseTextures.concat(featureTextures);
} }
@@ -244,7 +244,7 @@
data-member="{{ model.member }}" data-name="{{ model.name }}" data-member="{{ model.member }}" data-name="{{ model.name }}"
data-default-texture="{{ model.default_texture }}" data-default-texture="{{ model.default_texture }}"
data-base-url="{% url 'library:pack_asset' project.slug versions.0.pk 'ASSET' %}" data-base-url="{% url 'library:pack_asset' project.slug versions.0.pk 'ASSET' %}"
data-vanilla-base-url="{% url 'library:vanilla_asset' 'ASSET' %}"><i class="fas fa-cube"></i> Render</button> data-vanilla-base-url="/static/vanilla/ASSET"><i class="fas fa-cube"></i> Render</button>
</div> </div>
<p class="model-meta">{{ model.bones }} bones · {{ model.cubes }} cubes</p> <p class="model-meta">{{ model.bones }} bones · {{ model.cubes }} cubes</p>
<div class="model-textures"> <div class="model-textures">
+7
View File
@@ -0,0 +1,7 @@
# Packs site dependencies (Python 3.14)
Django==6.0.3
gunicorn==25.1.0
markdown-it-py==4.0.0
pillow==12.2.0
PyMySQL==1.1.2
whitenoise==6.12.0
+4
View File
@@ -28,5 +28,9 @@ else
echo "To force re-run, use: $0 --force-setup" echo "To force re-run, use: $0 --force-setup"
fi fi
# Rebuild the vanilla entity-texture index (gitignored generated file)
echo "Rebuilding vanilla entity-texture index..."
"$PYTHON_BIN" manage.py refresh_vanilla_index
echo "Starting Django development server on 0.0.0.0:8000..." echo "Starting Django development server on 0.0.0.0:8000..."
exec "$PYTHON_BIN" manage.py runserver 0.0.0.0:8000 exec "$PYTHON_BIN" manage.py runserver 0.0.0.0:8000
+4
View File
@@ -29,6 +29,10 @@ else
echo "To force re-run, use: $0 --force-setup" echo "To force re-run, use: $0 --force-setup"
fi fi
# Rebuild the vanilla entity-texture index (gitignored generated file)
echo "Rebuilding vanilla entity-texture index..."
"$PYTHON_BIN" manage.py refresh_vanilla_index
# Collect static files (served by gunicorn via whitenoise) # Collect static files (served by gunicorn via whitenoise)
"$PYTHON_BIN" manage.py collectstatic --noinput "$PYTHON_BIN" manage.py collectstatic --noinput