moeru-ai/airi · error · Error

Textures not found

Error message

Textures not found

What it means

Thrown by createFakeSettings when the uploaded files contain no .png entries. After pinning the single moc file, the synthesized Cubism4ModelSettings needs a Textures array; the heuristic simply collects all files ending in '.png'. An archive whose textures are .webp/.jpg, or where textures are missing entirely, fails here.

Solutions

  1. Re-export or re-package the model keeping the original .png texture atlases (the fake-settings path only recognizes .png)
  2. Include the real .model3.json so FileReferences.Textures from the editor export is used rather than the png-only heuristic
  3. Pre-scan uploads: if no file ends with '.png', reject with guidance before calling the loader
  4. Check for nested zips and extract them first so texture paths are visible to the loader

Example fix

// before
const settings = FileLoader.createSettings(files) // 'Textures not found'

// after
const hasPng = files.some(f => f.name.toLowerCase().endsWith('.png'))
if (!hasPng) {
  throw new Error('No .png textures found. Keep original PNG texture files, or include the model\'s .model3.json so texture paths are read from it.')
}
Defensive patterns

Strategy: validation

Validate before calling

const hasPng = files.some(f => f.name.toLowerCase().endsWith('.png'))
if (!hasPng) throw new Error('Model needs its original .png texture atlases')

Try / catch

catch (e) { if (e.message === 'Textures not found') { showUserError('No PNG textures in this archive; re-export with original textures'); return } throw e }

Prevention

When it happens

Trigger: Uploading a ZIP whose textures were converted to webp/jpg (breaking the .endsWith('.png') filter); textures inside a nested zip so their paths never appear in the flat file list; an archive with the moc but textures accidentally excluded; folder upload filtered to only some file types.

Common situations: Optimization pipelines that recompress texture atlases to webp; texture folders named with uppercase .PNG (case-sensitive endsWith); partial downloads of large model archives.

Related errors


AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-08-18). Data as JSON: /api/errors/6677f695f2a20da5. Report an issue: GitHub.

Appendix: source

Thrown at packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts:176

}

// copy and modified from https://github.com/guansss/live2d-viewer-web/blob/f6060b2ce52c2e26b6b61fa903c837fe343f72d1/src/app/upload.ts#L81-L142
function createFakeSettings(files: string[]): ModelSettings {
  const mocFiles = files.filter(file => isMocFile(file))

  if (mocFiles.length !== 1) {
    const fileList = mocFiles.length ? `(${mocFiles.map(f => `"${f}"`).join(',')})` : ''

    throw new Error(`Expected exactly one moc file, got ${mocFiles.length} ${fileList}`)
  }

  const mocFile = mocFiles[0]
  const modelName = basename(mocFile).replace(/\.moc3?/, '')

  const textures = files.filter(f => f.endsWith('.png'))

  if (!textures.length) {
    throw new Error('Textures not found')
  }

  const motions = files.filter(f => f.endsWith('.mtn') || f.endsWith('.motion3.json'))
  const physics = files.find(f => f.includes('physics'))
  const pose = files.find(f => f.includes('pose'))

  const settings = new Cubism4ModelSettings({
    url: `${modelName}.model3.json`,
    Version: 3,
    FileReferences: {
      Moc: mocFile,
      Textures: textures,
      Physics: physics,
      Pose: pose,
      Motions: motions.length
        ? {
            '': motions.map(motion => ({ File: motion })),
          }

View on GitHub (pinned to 9c213115f8)