Exemplo n.º 1
0
Arquivo: DCLN.cpp Projeto: AxioDL/urde
bool DCLN::Cook(const hecl::ProjectPath& outPath, const std::vector<Mesh>& meshes) {
  DCLN dcln;
  dcln.colCount = atUint32(meshes.size());
  for (const Mesh& mesh : meshes) {
    dcln.collision.emplace_back();
    Collision& colOut = dcln.collision.back();
    DeafBabeBuildFromBlender(colOut, mesh);
    colOut.root = std::move(*OBBTreeBuilder::buildCol<Collision::Node>(mesh));
    colOut.memSize = atUint32(colOut.root.getMemoryUsage());
  }

#if DCLN_DUMP_OBB
  hecl::blender::Connection& conn = hecl::blender::SharedBlenderToken.getBlenderConnection();
  conn.createBlend(outPath.getWithExtension(_SYS_STR(".blend")), hecl::blender::BlendType::ColMesh);
  dcln.sendToBlender(conn, "BLAH");
  conn.saveBlend();
#endif

  athena::io::FileWriter w(outPath.getAbsolutePath());
  dcln.write(w);
  int64_t rem = w.position() % 32;
  if (rem)
    for (int64_t i = 0; i < 32 - rem; ++i)
      w.writeUByte(0xff);
  return true;
}
Exemplo n.º 2
0
 static bool Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath)
 {
     STRG strg;
     FILE* fp = hecl::Fopen(inPath.getAbsolutePath().c_str(), _S("rb"));
     strg.fromYAMLFile(fp);
     fclose(fp);
     athena::io::FileWriter ws(outPath.getAbsolutePath());
     strg.write(ws);
     return true;
 }
Exemplo n.º 3
0
Space::Class Resource::DeduceDefaultSpaceClass(const hecl::ProjectPath& path)
{
    athena::io::FileReader r(path.getAbsolutePath(), 32*1024, false);
    if (r.hasError())
        return Space::Class::None;
    return Space::Class::None;
}
Exemplo n.º 4
0
 static bool Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath)
 {
     STRG strg;
     strg.read(rs);
     FILE* fp = hecl::Fopen(outPath.getAbsolutePath().c_str(), _S("wb"));
     strg.toYAMLFile(fp);
     fclose(fp);
     return true;
 }
Exemplo n.º 5
0
 static bool Extract(const SpecBase& dataSpec,
                     PAKEntryReadStream& rs,
                     const hecl::ProjectPath& outPath,
                     PAKRouter<PAKBridge>& pakRouter,
                     const PAK::Entry& entry,
                     bool force,
                     std::function<void(const hecl::SystemChar*)> fileChanged)
 {
     MLVL mlvl;
     mlvl.read(rs);
     FILE* fp = hecl::Fopen(outPath.getWithExtension(_S(".yaml"), true).getAbsolutePath().c_str(), _S("wb"));
     mlvl.toYAMLFile(fp);
     fclose(fp);
     hecl::BlenderConnection& conn = hecl::BlenderConnection::SharedConnection();
     return DNAMLVL::ReadMLVLToBlender(conn, mlvl, outPath, pakRouter,
                                       entry, force, fileChanged);
 }
Exemplo n.º 6
0
Arquivo: MAPA.cpp Projeto: AxioDL/urde
bool ReadMAPAToBlender(hecl::blender::Connection& conn, const MAPA& mapa, const hecl::ProjectPath& outPath,
                       PAKRouter& pakRouter, const typename PAKRouter::EntryType& entry, bool force) {
  if (!force && outPath.isFile())
    return true;

  if (!conn.createBlend(outPath, hecl::blender::BlendType::MapArea))
    return false;
  hecl::blender::PyOutStream os = conn.beginPythonOut(true);

  os << "import bpy, bmesh\n"
        "from mathutils import Matrix\n"
        "\n"
        "bpy.types.Object.retro_mappable_type = bpy.props.IntProperty(name='Retro: MAPA object type', default=-1)\n"
        "bpy.types.Object.retro_mappable_sclyid = bpy.props.StringProperty(name='Retro: MAPA object SCLY ID')\n"
        "bpy.types.Scene.retro_map_vis_mode = bpy.props.EnumProperty(items=[('ALWAYS', 'Always', 'Always Visible', 0),"
        "('MAPSTATIONORVISIT', 'Map Station or Visit', 'Visible after Map Station or Visit', 1),"
        "('VISIT', 'Visit', 'Visible after Visit', 2),"
        "('NEVER', 'Never', 'Never Visible', 3)],"
        "name='Retro: Map Visibility Mode')\n"
        "bpy.types.Object.retro_mapobj_vis_mode = bpy.props.EnumProperty(items=[('ALWAYS', 'Always', 'Always Visible', "
        "0),"
        "('MAPSTATIONORVISIT', 'Map Station or Visit', 'Visible after Map Station or Visit', 1),"
        "('VISIT', 'Visit', 'Visible after Door Visit', 2),"
        "('NEVER', 'Never', 'Never Visible', 3),"
        "('MAPSTATIONORVISIT2', 'Map Station or Visit 2', 'Visible after Map Station or Visit', 4)],"
        "name='Retro: Map Object Visibility Mode')\n"
        "\n"
        "for ar in bpy.context.screen.areas:\n"
        "    for sp in ar.spaces:\n"
        "        if sp.type == 'VIEW_3D':\n"
        "            sp.viewport_shade = 'SOLID'\n"
        "\n"
        "# Clear Scene\n"
        "for ob in bpy.data.objects:\n"
        "    if ob.type != 'CAMERA':\n"
        "        bpy.context.scene.objects.unlink(ob)\n"
        "        bpy.data.objects.remove(ob)\n"
        "\n"
        "def add_triangle(bm, verts):\n"
        "    verts = [bm.verts[vi] for vi in verts]\n"
        "    face = bm.faces.get(verts)\n"
        "    if face:\n"
        "        face = face.copy()\n"
        "        bm.verts.ensure_lookup_table()\n"
        "        face.normal_flip()\n"
        "    else:\n"
        "        bm.faces.new(verts)\n"
        "\n"
        "def add_border(bm, verts):\n"
        "    verts = [bm.verts[vi] for vi in verts]\n"
        "    edge = bm.edges.get(verts)\n"
        "    if not edge:\n"
        "        edge = bm.edges.new(verts)\n"
        "    edge.seam = True\n"
        "\n";

  os.format(
      "bpy.context.scene.name = 'MAPA_%s'\n"
      "bpy.context.scene.retro_map_vis_mode = '%s'\n",
      entry.id.toString().c_str(), RetroMapVisModes[mapa.header->visMode()]);

  /* Add empties representing MappableObjects */
  int moIdx = 0;
  for (const std::unique_ptr<MAPA::IMappableObject>& mo : mapa.mappableObjects) {
    if (mapa.version < 5) {
      const MAPA::MappableObjectMP1_2* moMP12 = static_cast<const MAPA::MappableObjectMP1_2*>(mo.get());
      zeus::simd_floats mtxF[3];
      for (int i = 0; i < 3; ++i)
        moMP12->transformMtx[i].simd.copy_to(mtxF[i]);
      os.format(
          "obj = bpy.data.objects.new('MAPOBJ_%02d', None)\n"
          "bpy.context.scene.objects.link(obj)\n"
          "obj.retro_mappable_type = %d\n"
          "obj.retro_mapobj_vis_mode = '%s'\n"
          "obj.retro_mappable_sclyid = '0x%08X'\n"
          "mtx = Matrix(((%f,%f,%f,%f),(%f,%f,%f,%f),(%f,%f,%f,%f),(0.0,0.0,0.0,1.0)))\n"
          "mtxd = mtx.decompose()\n"
          "obj.rotation_mode = 'QUATERNION'\n"
          "obj.location = mtxd[0]\n"
          "obj.rotation_quaternion = mtxd[1]\n"
          "obj.scale = mtxd[2]\n",
          moIdx, moMP12->type, RetroMapObjVisModes[moMP12->visMode], moMP12->sclyId, mtxF[0][0], mtxF[0][1], mtxF[0][2],
          mtxF[0][3], mtxF[1][0], mtxF[1][1], mtxF[1][2], mtxF[1][3], mtxF[2][0], mtxF[2][1], mtxF[2][2], mtxF[2][3]);
      ++moIdx;
      continue;
    } else {
      const MAPA::MappableObjectMP3* moMP3 = static_cast<const MAPA::MappableObjectMP3*>(mo.get());
      zeus::simd_floats mtxF[3];
      for (int i = 0; i < 3; ++i)
        moMP3->transformMtx[i].simd.copy_to(mtxF[i]);
      os.format(
          "obj = bpy.data.objects.new('MAPOBJ_%02d', None)\n"
          "bpy.context.scene.objects.link(obj)\n"
          "obj.retro_mappable_type = %d\n"
          "obj.retro_mapobj_vis_mode = '%s'\n"
          "obj.retro_mappable_sclyid = '0x%08X'\n"
          "mtx = Matrix(((%f,%f,%f,%f),(%f,%f,%f,%f),(%f,%f,%f,%f),(0.0,0.0,0.0,1.0)))\n"
          "mtxd = mtx.decompose()\n"
          "obj.rotation_mode = 'QUATERNION'\n"
          "obj.location = mtxd[0]\n"
          "obj.rotation_quaternion = mtxd[1]\n"
          "obj.scale = mtxd[2]\n",
          moIdx, moMP3->type, RetroMapObjVisModes[moMP3->visMode], moMP3->sclyId, mtxF[0][0], mtxF[0][1], mtxF[0][2],
          mtxF[0][3], mtxF[1][0], mtxF[1][1], mtxF[1][2], mtxF[1][3], mtxF[2][0], mtxF[2][1], mtxF[2][2], mtxF[2][3]);
      ++moIdx;
      continue;
    }
  }

  os << "# Begin bmesh\n"
        "bm = bmesh.new()\n"
        "\n";

  /* Read in verts */
  for (const atVec3f& vert : mapa.vertices) {
    zeus::simd_floats f(vert.simd);
    os.format("bm.verts.new((%f,%f,%f))\n", f[0], f[1], f[2]);
  }
  os << "bm.verts.ensure_lookup_table()\n";

  /* Read in surfaces */
  for (const typename MAPA::Surface& surf : mapa.surfaces) {
    for (const typename MAPA::Surface::Primitive& prim : surf.primitives) {
      auto iit = prim.indices.cbegin();

      /* 3 Prim Verts to start */
      int c = 0;
      unsigned int primVerts[3] = {*iit++, *iit++, *iit++};

      if (GX::Primitive(prim.type) == GX::TRIANGLESTRIP) {
        atUint8 flip = 0;
        for (size_t v = 0; v < prim.indexCount - 2; ++v) {
          if (flip) {
            os.format("add_triangle(bm, (%u,%u,%u))\n", primVerts[c % 3], primVerts[(c + 2) % 3],
                      primVerts[(c + 1) % 3]);
          } else {
            os.format("add_triangle(bm, (%u,%u,%u))\n", primVerts[c % 3], primVerts[(c + 1) % 3],
                      primVerts[(c + 2) % 3]);
          }
          flip ^= 1;

          /* Break if done */
          if (iit == prim.indices.cend())
            break;

          bool peek = (v >= prim.indexCount - 3);

          /* Advance one prim vert */
          if (peek)
            primVerts[c % 3] = *iit;
          else
            primVerts[c % 3] = *iit++;
          ++c;
        }
      } else if (GX::Primitive(prim.type) == GX::TRIANGLES) {
        for (size_t v = 0; v < prim.indexCount; v += 3) {
          os.format("add_triangle(bm, (%u,%u,%u))\n", primVerts[0], primVerts[1], primVerts[2]);

          /* Break if done */
          if (v + 3 >= prim.indexCount)
            break;

          /* Advance 3 Prim Verts */
          for (int pv = 0; pv < 3; ++pv)
            primVerts[pv] = *iit++;
        }
      }
    }

    for (const typename MAPA::Surface::Border& border : surf.borders) {
      auto iit = border.indices.cbegin();
      for (size_t i = 0; i < border.indexCount - 1; ++i) {
        os.format("add_border(bm, (%u,%u))\n", *iit, *(iit + 1));
        ++iit;
      }
    }
  }

  os << "mesh = bpy.data.meshes.new('MAP')\n"
        "mesh.show_edge_seams = True\n"
        "obj = bpy.data.objects.new(mesh.name, mesh)\n"
        "bm.to_mesh(mesh)\n"
        "bpy.context.scene.objects.link(obj)\n"
        "bm.free()\n";

  const zeus::CMatrix4f* tmpMtx = pakRouter.lookupMAPATransform(entry.id);
  const zeus::CMatrix4f& mtx = tmpMtx ? *tmpMtx : zeus::skIdentityMatrix4f;
  os.format(
      "mtx = Matrix(((%f,%f,%f,%f),(%f,%f,%f,%f),(%f,%f,%f,%f),(0.0,0.0,0.0,1.0)))\n"
      "mtxd = mtx.decompose()\n"
      "obj.rotation_mode = 'QUATERNION'\n"
      "obj.location = mtxd[0]\n"
      "obj.rotation_quaternion = mtxd[1]\n"
      "obj.scale = mtxd[2]\n",
      mtx[0][0], mtx[1][0], mtx[2][0], mtx[3][0], mtx[0][1], mtx[1][1], mtx[2][1], mtx[3][1], mtx[0][2], mtx[1][2],
      mtx[2][2], mtx[3][2]);

  /* World background */
  hecl::ProjectPath worldBlend(outPath.getParentPath().getParentPath(), "!world.blend");
  if (worldBlend.isFile())
    os.linkBackground("//../!world.blend", "World");

  os.centerView();
  os.close();
  conn.saveBlend();
  return true;
}
Exemplo n.º 7
0
Arquivo: MAPA.cpp Projeto: AxioDL/urde
bool Cook(const hecl::blender::MapArea& mapaIn, const hecl::ProjectPath& out) {
  if (mapaIn.verts.size() >= 256) {
    Log.report(logvisor::Error, _SYS_STR("MAPA %s vertex range exceeded [%d/%d]"), out.getRelativePath().data(),
               mapaIn.verts.size(), 255);
    return false;
  }

  MAPAType mapa;
  mapa.magic = 0xDEADD00D;
  mapa.version = MAPAType::Version();

  zeus::CAABox aabb;
  for (const hecl::blender::Vector3f& vert : mapaIn.verts)
    aabb.accumulateBounds(vert.val);

  mapa.header = std::make_unique<typename MAPAType::Header>();
  typename MAPAType::Header& header = static_cast<typename MAPAType::Header&>(*mapa.header);
  header.unknown1 = 0;
  header.mapVisMode = mapaIn.visType.val;
  header.boundingBox[0] = aabb.min;
  header.boundingBox[1] = aabb.max;
  header.moCount = mapaIn.pois.size();
  header.vtxCount = mapaIn.verts.size();
  header.surfCount = mapaIn.surfaces.size();

  mapa.mappableObjects.reserve(mapaIn.pois.size());
  for (const hecl::blender::MapArea::POI& poi : mapaIn.pois) {
    mapa.mappableObjects.push_back(std::make_unique<typename MAPAType::MappableObject>());
    typename MAPAType::MappableObject& mobj =
        static_cast<typename MAPAType::MappableObject&>(*mapa.mappableObjects.back());
    mobj.type = MAPA::IMappableObject::Type(poi.type);
    mobj.visMode = poi.visMode;
    mobj.sclyId = poi.objid;
    mobj.transformMtx[0] = poi.xf.val[0];
    mobj.transformMtx[1] = poi.xf.val[1];
    mobj.transformMtx[2] = poi.xf.val[2];
  }

  mapa.vertices.reserve(mapaIn.verts.size());
  for (const hecl::blender::Vector3f& vert : mapaIn.verts)
    mapa.vertices.push_back(vert.val);

  size_t offsetCur = 0;
  for (const auto& mo : mapa.mappableObjects)
    mo->binarySize(offsetCur);
  offsetCur += mapa.vertices.size() * 12;
  offsetCur += mapaIn.surfaces.size() * 32;

  mapa.surfaceHeaders.reserve(mapaIn.surfaces.size());
  mapa.surfaces.reserve(mapaIn.surfaces.size());
  for (const hecl::blender::MapArea::Surface& surfIn : mapaIn.surfaces) {
    mapa.surfaceHeaders.emplace_back();
    DNAMAPA::MAPA::SurfaceHeader& surfHead = mapa.surfaceHeaders.back();
    mapa.surfaces.emplace_back();
    DNAMAPA::MAPA::Surface& surf = mapa.surfaces.back();

    surf.primitiveCount = 1;
    surf.primitives.emplace_back();
    DNAMAPA::MAPA::Surface::Primitive& prim = surf.primitives.back();
    prim.type = GX::TRIANGLESTRIP;
    prim.indexCount = surfIn.count;
    prim.indices.reserve(surfIn.count);
    auto itBegin = mapaIn.indices.begin() + surfIn.start.val;
    auto itEnd = itBegin + surfIn.count;
    for (auto it = itBegin; it != itEnd; ++it)
      prim.indices.push_back(it->val);

    surf.borderCount = surfIn.borders.size();
    surf.borders.reserve(surfIn.borders.size());
    for (const auto& borderIn : surfIn.borders) {
      surf.borders.emplace_back();
      DNAMAPA::MAPA::Surface::Border& border = surf.borders.back();
      border.indexCount = borderIn.second.val;
      border.indices.reserve(borderIn.second.val);
      auto it2Begin = mapaIn.indices.begin() + borderIn.first.val;
      auto it2End = it2Begin + borderIn.second.val;
      for (auto it = it2Begin; it != it2End; ++it)
        border.indices.push_back(it->val);
    }

    surfHead.normal = surfIn.normal.val;
    surfHead.centroid = surfIn.centerOfMass;
    surfHead.polyOff = offsetCur;
    offsetCur += 4;
    prim.binarySize(offsetCur);
    surfHead.edgeOff = offsetCur;
    offsetCur += 4;
    for (const auto& border : surf.borders)
      border.binarySize(offsetCur);
  }

  athena::io::FileWriter f(out.getAbsolutePath());
  mapa.write(f);
  int64_t rem = f.position() % 32;
  if (rem)
    for (int64_t i = 0; i < 32 - rem; ++i)
      f.writeBytes((atInt8*)"\xff", 1);
  return true;
}
Exemplo n.º 8
0
bool MREA::Extract(const SpecBase& dataSpec,
                   PAKEntryReadStream& rs,
                   const hecl::ProjectPath& outPath,
                   PAKRouter<PAKBridge>& pakRouter,
                   const PAK::Entry& entry,
                   bool force,
                   std::function<void(const hecl::SystemChar*)>)
{
    using RigPair = std::pair<CSKR*, CINF*>;
    RigPair dummy(nullptr, nullptr);

    hecl::ProjectPath mreaPath;
    if (pakRouter.isShared())
        /* Rename MREA for consistency */
        mreaPath = hecl::ProjectPath(outPath.getParentPath(), _S("!area.blend"));
    else
        /* We're not in a world pak, so lets keep the original name */
        mreaPath = outPath;

    if (!force && mreaPath.getPathType() == hecl::ProjectPath::Type::File)
        return true;

    /* Do extract */
    Header head;
    head.read(rs);
    rs.seekAlign32();

    /* MREA decompression stream */
    StreamReader drs(rs, head.compressedBlockCount, head.secIndexCount);
    athena::io::FileWriter mreaDecompOut(pakRouter.getCooked(&entry).getWithExtension(_S(".decomp")).getAbsolutePath());
    head.write(mreaDecompOut);
    mreaDecompOut.seekAlign32();
    drs.writeDecompInfos(mreaDecompOut);
    mreaDecompOut.seekAlign32();
    drs.writeSecIdxs(mreaDecompOut);
    mreaDecompOut.seekAlign32();
    atUint64 decompLen = drs.length();
    mreaDecompOut.writeBytes(drs.readBytes(decompLen).get(), decompLen);
    mreaDecompOut.close();
    drs.seek(0, athena::Begin);


    /* Start up blender connection */
    hecl::BlenderConnection& conn = hecl::BlenderConnection::SharedConnection();
    if (!conn.createBlend(mreaPath, hecl::BlenderConnection::BlendType::Area))
        return false;

    /* Open Py Stream and read sections */
    hecl::BlenderConnection::PyOutStream os = conn.beginPythonOut(true);
    os.format("import bpy\n"
              "import bmesh\n"
              "from mathutils import Vector\n"
              "\n"
              "bpy.context.scene.name = '%s'\n",
              pakRouter.getBestEntryName(entry).c_str());
    DNACMDL::InitGeomBlenderContext(os, dataSpec.getMasterShaderPath());
    MaterialSet::RegisterMaterialProps(os);
    os << "# Clear Scene\n"
          "for ob in bpy.data.objects:\n"
          "    bpy.context.scene.objects.unlink(ob)\n"
          "    bpy.data.objects.remove(ob)\n"
          "bpy.types.Lamp.retro_layer = bpy.props.IntProperty(name='Retro: Light Layer')\n"
          "bpy.types.Lamp.retro_origtype = bpy.props.IntProperty(name='Retro: Original Type')\n"
          "\n";

    /* One shared material set for all meshes */
    os << "# Materials\n"
          "materials = []\n"
          "\n";
    MaterialSet matSet;
    atUint64 secStart = drs.position();
    matSet.read(drs);
    matSet.readToBlender(os, pakRouter, entry, 0);
    drs.seek(secStart + head.secSizes[0], athena::Begin);
    std::vector<DNACMDL::VertexAttributes> vertAttribs;
    DNACMDL::GetVertexAttributes(matSet, vertAttribs);

    /* Read mesh info */
    atUint32 curSec = 1;
    std::vector<atUint32> surfaceCounts;
    surfaceCounts.reserve(head.meshCount);
    for (atUint32 m=0 ; m<head.meshCount ; ++m)
    {
        /* Mesh header */
        MeshHeader mHeader;
        secStart = drs.position();
        mHeader.read(drs);
        drs.seek(secStart + head.secSizes[curSec++], athena::Begin);

        /* Surface count from here */
        secStart = drs.position();
        surfaceCounts.push_back(drs.readUint32Big());
        drs.seek(secStart + head.secSizes[curSec++], athena::Begin);

        /* Seek through AROT-relation sections */
        drs.seek(head.secSizes[curSec++], athena::Current);
        drs.seek(head.secSizes[curSec++], athena::Current);
    }

    /* Skip though WOBJs */
    auto secIdxIt = drs.beginSecIdxs();
    while (secIdxIt->first == FOURCC('WOBJ'))
        ++secIdxIt;

    /* Skip AROT */
    if (secIdxIt->first == FOURCC('ROCT'))
    {
        drs.seek(head.secSizes[curSec++], athena::Current);
        ++secIdxIt;
    }

    /* Skip AABB */
    if (secIdxIt->first == FOURCC('AABB'))
    {
        drs.seek(head.secSizes[curSec++], athena::Current);
        ++secIdxIt;
    }

    /* Now the meshes themselves */
    if (secIdxIt->first == FOURCC('GPUD'))
    {
        for (atUint32 m=0 ; m<head.meshCount ; ++m)
        {
            curSec += DNACMDL::ReadGeomSectionsToBlender<PAKRouter<PAKBridge>, MaterialSet, RigPair, DNACMDL::SurfaceHeader_3>
                          (os, drs, pakRouter, entry, dummy, true,
                           false, vertAttribs, m, head.secCount, 0, &head.secSizes[curSec], surfaceCounts[m]);
        }
        ++secIdxIt;
    }

    /* Skip DEPS */
    if (secIdxIt->first == FOURCC('DEPS'))
    {
        drs.seek(head.secSizes[curSec++], athena::Current);
        ++secIdxIt;
    }

    /* Skip SOBJ (SCLY) */
    if (secIdxIt->first == FOURCC('SOBJ'))
    {
        for (atUint32 l=0 ; l<head.sclyLayerCount ; ++l)
            drs.seek(head.secSizes[curSec++], athena::Current);
        ++secIdxIt;
    }

    /* Skip SGEN */
    if (secIdxIt->first == FOURCC('SGEN'))
    {
        drs.seek(head.secSizes[curSec++], athena::Current);
        ++secIdxIt;
    }

    /* Read Collision Meshes */
    if (secIdxIt->first == FOURCC('COLI'))
    {
        DNAMP2::DeafBabe collision;
        secStart = drs.position();
        collision.read(drs);
        DNAMP2::DeafBabe::BlenderInit(os);
        collision.sendToBlender(os);
        drs.seek(secStart + head.secSizes[curSec++], athena::Begin);
        ++secIdxIt;
    }

    /* Read BABEDEAD Lights as Cycles emissives */
    if (secIdxIt->first == FOURCC('LITE'))
    {
        secStart = drs.position();
        ReadBabeDeadToBlender_3(os, drs);
        drs.seek(secStart + head.secSizes[curSec++], athena::Begin);
        ++secIdxIt;
    }

    /* Origins to center of mass */
    os << "bpy.context.scene.layers[1] = True\n"
          "bpy.ops.object.select_by_type(type='MESH')\n"
          "bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')\n"
          "bpy.ops.object.select_all(action='DESELECT')\n"
          "bpy.context.scene.layers[1] = False\n";

    os.centerView();
    os.close();
    return conn.saveBlend();
}