working model cat backup
This commit is contained in:
@@ -636,7 +636,7 @@ def _reparse_version(version):
|
||||
else:
|
||||
version.animation_id = ''
|
||||
version.animation_manifest = read_animation_manifest(path) or {}
|
||||
version.models_manifest = read_models_manifest(path) or {}
|
||||
version.models_manifest = read_models_manifest(path, version.project.category) or {}
|
||||
version.logical_manifest = read_logical_manifest(path) or {}
|
||||
manifest = parse_mods_manifest(path, release.file.original_filename)
|
||||
version.mods_manifest = manifest if manifest.get('files') else {}
|
||||
@@ -694,7 +694,7 @@ def _finalize_version(project, version_name, changelog, temp_uploads, actor):
|
||||
if animation_manifest is None:
|
||||
animation_manifest = read_animation_manifest(path)
|
||||
if models_manifest is None:
|
||||
models_manifest = read_models_manifest(path)
|
||||
models_manifest = read_models_manifest(path, project.category)
|
||||
if logical_manifest is None:
|
||||
logical_manifest = read_logical_manifest(path)
|
||||
if not mods_manifest:
|
||||
|
||||
+68
-31
@@ -12,6 +12,9 @@ import zipfile
|
||||
INJECTOR_NAMES = {'V': 'Vaginal', 'M': 'Mouth', 'A': 'Anal'}
|
||||
PROBLEM_TAGS = {'broken', 'bugged', 'borked'}
|
||||
|
||||
# Blockbench-compatible model formats accepted by the explicit Model category.
|
||||
MODEL_FILE_EXTENSIONS = ('.bbmodel', '.geo.json', '.jem', '.gltf', '.glb')
|
||||
|
||||
|
||||
def _decode(data):
|
||||
"""Decode JSON text, tolerating a UTF-8 BOM (common in hand-edited packs)."""
|
||||
@@ -239,53 +242,61 @@ def _resource_to_member(resource):
|
||||
return f'assets/{resource}'
|
||||
|
||||
|
||||
def read_models_manifest(path):
|
||||
"""Build the Models/Textures manifest from GeckoLib geo models.
|
||||
def read_models_manifest(path, category='non_pack'):
|
||||
"""Build the Models/Textures manifest.
|
||||
|
||||
Returns None when the pack has no geo models. Each model records its geo
|
||||
filename, identifier, bone/cube counts, and the afw_bone_textures map
|
||||
(bone → texture zip member) so the viewer can texture it."""
|
||||
For the explicit ``model`` category this scans the archive for any
|
||||
Blockbench-compatible model file (``MODEL_FILE_EXTENSIONS``) anywhere in
|
||||
the zip. For other categories it keeps the NoN GeckoLib geo-model
|
||||
detection (``geckolib/models/*.geo.json``). Returns None when no models
|
||||
are found. Each model records its filename, zip member, format, bone/cube
|
||||
counts, and the afw_bone_textures map (bone → texture zip member).
|
||||
"""
|
||||
zf = _open_zip(path)
|
||||
if zf is None:
|
||||
return None
|
||||
try:
|
||||
names = set(zf.namelist())
|
||||
geo_names = sorted(
|
||||
n for n in names if '/geckolib/models/' in n and n.endswith('.geo.json')
|
||||
)
|
||||
if not geo_names:
|
||||
if category == 'model':
|
||||
model_names = sorted(
|
||||
n for n in names
|
||||
if not n.endswith('/') and n.lower().endswith(MODEL_FILE_EXTENSIONS)
|
||||
)
|
||||
else:
|
||||
model_names = sorted(
|
||||
n for n in names if '/geckolib/models/' in n and n.endswith('.geo.json')
|
||||
)
|
||||
if not model_names:
|
||||
return None
|
||||
|
||||
models = []
|
||||
for name in geo_names:
|
||||
for name in model_names:
|
||||
ext = _model_ext(name)
|
||||
data = None
|
||||
try:
|
||||
data = _decode(zf.read(name))
|
||||
except (KeyError, json.JSONDecodeError):
|
||||
continue
|
||||
except (KeyError, json.JSONDecodeError, UnicodeDecodeError):
|
||||
data = None
|
||||
|
||||
bone_textures = {}
|
||||
for bone, tex in (data.get('afw_bone_textures') or {}).items():
|
||||
member = _resource_to_member(str(tex))
|
||||
if member in names:
|
||||
bone_textures[str(bone)] = member
|
||||
bone_count = 0
|
||||
cube_count = 0
|
||||
identifier = ''
|
||||
for geom in data.get('minecraft:geometry') or []:
|
||||
identifier = identifier or (geom.get('description') or {}).get('identifier', '')
|
||||
for bone in geom.get('bones') or []:
|
||||
bone_count += 1
|
||||
cube_count += len(bone.get('cubes') or [])
|
||||
if data and isinstance(data, dict):
|
||||
for bone, tex in (data.get('afw_bone_textures') or {}).items():
|
||||
member = _resource_to_member(str(tex))
|
||||
if member in names:
|
||||
bone_textures[str(bone)] = member
|
||||
if ext == '.geo.json':
|
||||
for geom in data.get('minecraft:geometry') or []:
|
||||
identifier = identifier or (geom.get('description') or {}).get('identifier', '')
|
||||
for bone in geom.get('bones') or []:
|
||||
bone_count += 1
|
||||
cube_count += len(bone.get('cubes') or [])
|
||||
elif ext == '.jem':
|
||||
bone_count = len(data.get('models') 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.
|
||||
entity = _entity_name(name)
|
||||
default_texture = ''
|
||||
if not bone_textures and entity:
|
||||
for n in names:
|
||||
@@ -295,11 +306,14 @@ def read_models_manifest(path):
|
||||
models.append({
|
||||
'name': name.rsplit('/', 1)[-1],
|
||||
'member': name,
|
||||
'ext': ext,
|
||||
'mode': 'pack',
|
||||
'identifier': identifier,
|
||||
'entity': entity,
|
||||
'default_texture': default_texture,
|
||||
'bones': bone_count,
|
||||
'cubes': cube_count,
|
||||
'has_embedded_texture': ext in ('.bbmodel', '.gltf', '.glb'),
|
||||
'bone_textures': bone_textures,
|
||||
'textures': sorted(set(bone_textures.values())),
|
||||
})
|
||||
@@ -310,6 +324,29 @@ def read_models_manifest(path):
|
||||
zf.close()
|
||||
|
||||
|
||||
def _model_ext(name):
|
||||
"""The matching MODEL_FILE_EXTENSIONS suffix for a member, or ''."""
|
||||
lower = name.lower()
|
||||
for ext in MODEL_FILE_EXTENSIONS:
|
||||
if lower.endswith(ext):
|
||||
return ext
|
||||
return ''
|
||||
|
||||
|
||||
def _entity_name(name):
|
||||
"""Derive the entity name from a model filename (strip ext + gender suffix)."""
|
||||
base = name.rsplit('/', 1)[-1]
|
||||
for ext in MODEL_FILE_EXTENSIONS:
|
||||
if base.lower().endswith(ext):
|
||||
base = base[: -len(ext)]
|
||||
break
|
||||
for suffix in ('.mf', '.fm', '.m', '.f', '.g'):
|
||||
if base.endswith(suffix):
|
||||
base = base[:-len(suffix)]
|
||||
break
|
||||
return base
|
||||
|
||||
|
||||
def _item_name_from_member(member):
|
||||
parts = member.split('/')
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user