bool DCLN::Extract(const SpecBase& dataSpec, PAKEntryReadStream& rs, const hecl::ProjectPath& outPath, PAKRouter<PAKBridge>& pakRouter, const PAK::Entry& entry, bool force, hecl::blender::Token& btok, std::function<void(const hecl::SystemChar*)> fileChanged) { DCLN dcln; dcln.read(rs); hecl::blender::Connection& conn = btok.getBlenderConnection(); if (!conn.createBlend(outPath, hecl::blender::BlendType::ColMesh)) return false; dcln.sendToBlender(conn, pakRouter.getBestEntryName(entry, false)); return conn.saveBlend(); }
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(); }
bool ReadANCSToBlender(hecl::blender::Connection& conn, const ANCSDNA& ancs, const hecl::ProjectPath& outPath, PAKRouter& pakRouter, const typename PAKRouter::EntryType& entry, const SpecBase& dataspec, std::function<void(const hecl::SystemChar*)> fileChanged, bool force) { /* Extract character CMDL/CSKR first */ std::vector<CharacterResInfo<typename PAKRouter::IDType>> chResInfo; ancs.getCharacterResInfo(chResInfo); for (const auto& info : chResInfo) { const nod::Node* node; const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(info.cmdl, &node, true, false); if (cmdlE) { hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE); if (force || cmdlPath.isNone()) { cmdlPath.makeDirChain(false); if (!conn.createBlend(cmdlPath, hecl::blender::BlendType::Mesh)) return false; std::string bestName = pakRouter.getBestEntryName(*cmdlE); hecl::SystemStringConv bestNameView(bestName); fileChanged(bestNameView.c_str()); typename ANCSDNA::CSKRType cskr; pakRouter.lookupAndReadDNA(info.cskr, cskr); typename ANCSDNA::CINFType cinf; pakRouter.lookupAndReadDNA(info.cinf, cinf); using RigPair = std::pair<typename ANCSDNA::CSKRType*, typename ANCSDNA::CINFType*>; RigPair rigPair(&cskr, &cinf); PAKEntryReadStream rs = cmdlE->beginReadStream(*node); DNACMDL::ReadCMDLToBlender<PAKRouter, MaterialSet, RigPair, SurfaceHeader, CMDLVersion>( conn, rs, pakRouter, *cmdlE, dataspec, rigPair); conn.saveBlend(); } } } /* Extract attachment CMDL/CSKRs first */ auto attRange = pakRouter.lookupCharacterAttachmentRigs(entry.id); for (auto it = attRange.first; it != attRange.second; ++it) { auto cmdlid = it->second.first.second; const nod::Node* node; const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(cmdlid, &node, true, false); if (cmdlE) { hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE); if (force || cmdlPath.isNone()) { cmdlPath.makeDirChain(false); if (!conn.createBlend(cmdlPath, hecl::blender::BlendType::Mesh)) return false; std::string bestName = pakRouter.getBestEntryName(*cmdlE); hecl::SystemStringConv bestNameView(bestName); fileChanged(bestNameView.c_str()); const auto* rp = pakRouter.lookupCMDLRigPair(cmdlid); typename ANCSDNA::CSKRType cskr; pakRouter.lookupAndReadDNA(rp->first, cskr); typename ANCSDNA::CINFType cinf; pakRouter.lookupAndReadDNA(rp->second, cinf); using RigPair = std::pair<typename ANCSDNA::CSKRType*, typename ANCSDNA::CINFType*>; RigPair rigPair(&cskr, &cinf); PAKEntryReadStream rs = cmdlE->beginReadStream(*node); DNACMDL::ReadCMDLToBlender<PAKRouter, MaterialSet, RigPair, SurfaceHeader, CMDLVersion>( conn, rs, pakRouter, *cmdlE, dataspec, rigPair); conn.saveBlend(); } } } std::string bestName = pakRouter.getBestEntryName(entry); hecl::SystemStringConv bestNameView(bestName); fileChanged(bestNameView.c_str()); /* Establish ANCS blend */ if (!conn.createBlend(outPath, hecl::blender::BlendType::Actor)) return false; std::string firstName; typename ANCSDNA::CINFType firstCinf; { hecl::blender::PyOutStream os = conn.beginPythonOut(true); os.format( "import bpy\n" "from mathutils import Vector\n" "bpy.context.scene.name = '%s'\n" "bpy.context.scene.hecl_mesh_obj = bpy.context.scene.name\n" "\n" "# Using 'Blender Game'\n" "bpy.context.scene.render.engine = 'BLENDER_GAME'\n" "\n" "# Clear Scene\n" "for ob in bpy.data.objects:\n" " if ob.type != 'LAMP' and ob.type != 'CAMERA':\n" " bpy.context.scene.objects.unlink(ob)\n" " bpy.data.objects.remove(ob)\n" "\n" "actor_data = bpy.context.scene.hecl_sact_data\n" "arm_obj = None\n", pakRouter.getBestEntryName(entry).c_str()); std::unordered_set<typename PAKRouter::IDType> cinfsDone; for (const auto& info : chResInfo) { /* Provide data to add-on */ os.format( "actor_subtype = actor_data.subtypes.add()\n" "actor_subtype.name = '%s'\n\n", info.name.c_str()); /* Build CINF if needed */ if (cinfsDone.find(info.cinf) == cinfsDone.end()) { typename ANCSDNA::CINFType cinf; pakRouter.lookupAndReadDNA(info.cinf, cinf); cinf.sendCINFToBlender(os, info.cinf); if (cinfsDone.empty()) { firstName = ANCSDNA::CINFType::GetCINFArmatureName(info.cinf); firstCinf = cinf; } cinfsDone.insert(info.cinf); } else os.format("arm_obj = bpy.data.objects['CINF_%s']\n", info.cinf.toString().c_str()); os << "actor_subtype.linked_armature = arm_obj.name\n"; /* Link CMDL */ const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(info.cmdl, nullptr, true, false); if (cmdlE) { hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE); os.linkBlend(cmdlPath.getAbsolutePathUTF8().data(), pakRouter.getBestEntryName(*cmdlE).data(), true); /* Attach CMDL to CINF */ os << "if obj.name not in bpy.context.scene.objects:\n" " bpy.context.scene.objects.link(obj)\n" "obj.parent = arm_obj\n" "obj.parent_type = 'ARMATURE'\n" "actor_subtype.linked_mesh = obj.name\n\n"; } /* Link overlays */ for (const auto& overlay : info.overlays) { os << "overlay = actor_subtype.overlays.add()\n"; os.format("overlay.name = '%s'\n", overlay.first.c_str()); /* Link CMDL */ const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(overlay.second.first, nullptr, true, false); if (cmdlE) { hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE); os.linkBlend(cmdlPath.getAbsolutePathUTF8().data(), pakRouter.getBestEntryName(*cmdlE).data(), true); /* Attach CMDL to CINF */ os << "if obj.name not in bpy.context.scene.objects:\n" " bpy.context.scene.objects.link(obj)\n" "obj.parent = arm_obj\n" "obj.parent_type = 'ARMATURE'\n" "overlay.linked_mesh = obj.name\n\n"; } } } /* Link attachments */ for (auto it = attRange.first; it != attRange.second; ++it) { os << "attachment = actor_data.attachments.add()\n"; os.format("attachment.name = '%s'\n", it->second.second.c_str()); auto cinfid = it->second.first.first; auto cmdlid = it->second.first.second; if (cinfid) { /* Build CINF if needed */ if (cinfsDone.find(cinfid) == cinfsDone.end()) { typename ANCSDNA::CINFType cinf; pakRouter.lookupAndReadDNA(cinfid, cinf); cinf.sendCINFToBlender(os, cinfid); if (cinfsDone.empty()) { firstName = ANCSDNA::CINFType::GetCINFArmatureName(cinfid); firstCinf = cinf; } cinfsDone.insert(cinfid); } else os.format("arm_obj = bpy.data.objects['CINF_%s']\n", cinfid.toString().c_str()); os << "attachment.linked_armature = arm_obj.name\n"; } /* Link CMDL */ const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(cmdlid, nullptr, true, false); if (cmdlE) { hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE); os.linkBlend(cmdlPath.getAbsolutePathUTF8().data(), pakRouter.getBestEntryName(*cmdlE).data(), true); /* Attach CMDL to CINF */ os << "if obj.name not in bpy.context.scene.objects:\n" " bpy.context.scene.objects.link(obj)\n" "obj.parent = arm_obj\n" "obj.parent_type = 'ARMATURE'\n" "attachment.linked_mesh = obj.name\n\n"; } } } { hecl::blender::DataStream ds = conn.beginData(); std::unordered_map<std::string, hecl::blender::Matrix3f> matrices = ds.getBoneMatrices(firstName); ds.close(); DNAANIM::RigInverter<typename ANCSDNA::CINFType> inverter(firstCinf, matrices); hecl::blender::PyOutStream os = conn.beginPythonOut(true); os << "import bpy\n" "actor_data = bpy.context.scene.hecl_sact_data\n"; /* Get animation primitives */ std::map<atUint32, AnimationResInfo<typename PAKRouter::IDType>> animResInfo; ancs.getAnimationResInfo(&pakRouter, animResInfo); for (const auto& id : animResInfo) { typename ANCSDNA::ANIMType anim; if (pakRouter.lookupAndReadDNA(id.second.animId, anim, true)) { os.format( "act = bpy.data.actions.new('%s')\n" "act.use_fake_user = True\n", id.second.name.c_str()); anim.sendANIMToBlender(os, inverter, id.second.additive); } os.format( "actor_action = actor_data.actions.add()\n" "actor_action.name = '%s'\n", id.second.name.c_str()); /* Extract EVNT if present */ anim.extractEVNT(id.second, outPath, pakRouter, force); } } conn.saveBlend(); return true; }
void Material::SectionPASS::constructNode(hecl::BlenderConnection::PyOutStream& out, const PAKRouter<PAKBridge>& pakRouter, const PAK::Entry& entry, const Material::ISection* prevSection, unsigned idx, unsigned& texMapIdx, unsigned& texMtxIdx, unsigned& kColorIdx) const { /* Add Texture nodes */ if (txtrId) { std::string texName = pakRouter.getBestEntryName(txtrId); const nod::Node* node; const PAK::Entry* texEntry = pakRouter.lookupEntry(txtrId, &node); hecl::ProjectPath txtrPath = pakRouter.getWorking(texEntry); if (txtrPath.getPathType() == hecl::ProjectPath::Type::None) { PAKEntryReadStream rs = texEntry->beginReadStream(*node); TXTR::Extract(rs, txtrPath); } hecl::SystemString resPath = pakRouter.getResourceRelativePath(entry, txtrId); hecl::SystemUTF8View resPathView(resPath); out.format("if '%s' in bpy.data.textures:\n" " image = bpy.data.images['%s']\n" " texture = bpy.data.textures[image.name]\n" "else:\n" " image = bpy.data.images.load('''//%s''')\n" " image.name = '%s'\n" " texture = bpy.data.textures.new(image.name, 'IMAGE')\n" " texture.image = image\n" "tex_maps.append(texture)\n" "\n", texName.c_str(), texName.c_str(), resPathView.str().c_str(), texName.c_str()); if (uvAnim.size()) { const UVAnimation& uva = uvAnim[0]; DNAMP1::MaterialSet::Material::AddTexture(out, GX::TexGenSrc(uva.unk1 + (uva.unk1 < 2 ? 0 : 2)), texMtxIdx, texMapIdx++); DNAMP1::MaterialSet::Material::AddTextureAnim(out, uva.anim.mode, texMtxIdx++, uva.anim.vals); } else DNAMP1::MaterialSet::Material::AddTexture(out, GX::TexGenSrc(uvSrc + 4), -1, texMapIdx++); } /* Special case for RFLV (environment UV mask) */ if (Subtype(subtype.toUint32()) == Subtype::RFLV) { if (txtrId) out << "rflv_tex_node = texture_nodes[-1]\n"; return; } /* Add PASS node */ bool linkRAS = false; out << "prev_pnode = pnode\n" "pnode = new_nodetree.nodes.new('ShaderNodeGroup')\n"; switch (Subtype(subtype.toUint32())) { case Subtype::DIFF: { out << "pnode.node_tree = bpy.data.node_groups['RetroPassDIFF']\n"; if (txtrId) { out << "new_material.hecl_lightmap = texture.name\n" << "texture.image.use_fake_user = True\n"; } linkRAS = true; break; } case Subtype::RIML: out << "pnode.node_tree = bpy.data.node_groups['RetroPassRIML']\n"; if (idx == 0) linkRAS = true; break; case Subtype::BLOL: out << "pnode.node_tree = bpy.data.node_groups['RetroPassBLOL']\n"; if (idx == 0) linkRAS = true; break; case Subtype::BLOD: out << "pnode.node_tree = bpy.data.node_groups['RetroPassBLOD']\n"; if (idx == 0) linkRAS = true; break; case Subtype::CLR: out << "pnode.node_tree = bpy.data.node_groups['RetroPassCLR']\n"; if (idx == 0) linkRAS = true; break; case Subtype::TRAN: if (flags.TRANInvert()) out << "pnode.node_tree = bpy.data.node_groups['RetroPassTRANInv']\n"; else out << "pnode.node_tree = bpy.data.node_groups['RetroPassTRAN']\n"; break; case Subtype::INCA: out << "pnode.node_tree = bpy.data.node_groups['RetroPassINCA']\n"; break; case Subtype::RFLV: out << "pnode.node_tree = bpy.data.node_groups['RetroPassRFLV']\n"; break; case Subtype::RFLD: out << "pnode.node_tree = bpy.data.node_groups['RetroPassRFLD']\n" "if rflv_tex_node:\n" " new_nodetree.links.new(rflv_tex_node.outputs['Color'], pnode.inputs['Mask Color'])\n" " new_nodetree.links.new(rflv_tex_node.outputs['Value'], pnode.inputs['Mask Alpha'])\n"; break; case Subtype::LRLD: out << "pnode.node_tree = bpy.data.node_groups['RetroPassLRLD']\n"; break; case Subtype::LURD: out << "pnode.node_tree = bpy.data.node_groups['RetroPassLURD']\n"; break; case Subtype::BLOI: out << "pnode.node_tree = bpy.data.node_groups['RetroPassBLOI']\n"; break; case Subtype::XRAY: out << "pnode.node_tree = bpy.data.node_groups['RetroPassXRAY']\n"; break; case Subtype::TOON: out << "pnode.node_tree = bpy.data.node_groups['RetroPassTOON']\n"; break; default: break; } out << "gridder.place_node(pnode, 2)\n"; if (txtrId) { out << "new_nodetree.links.new(texture_nodes[-1].outputs['Color'], pnode.inputs['Tex Color'])\n" "new_nodetree.links.new(texture_nodes[-1].outputs['Value'], pnode.inputs['Tex Alpha'])\n"; } if (linkRAS) out << "new_nodetree.links.new(material_node.outputs['Color'], pnode.inputs['Prev Color'])\n" "new_nodetree.links.new(material_node.outputs['Alpha'], pnode.inputs['Prev Alpha'])\n"; else if (prevSection) { if (prevSection->m_type == ISection::Type::PASS && Subtype(static_cast<const SectionPASS*>(prevSection)->subtype.toUint32()) != Subtype::RFLV) out << "new_nodetree.links.new(prev_pnode.outputs['Next Color'], pnode.inputs['Prev Color'])\n" "new_nodetree.links.new(prev_pnode.outputs['Next Alpha'], pnode.inputs['Prev Alpha'])\n"; else if (prevSection->m_type == ISection::Type::CLR) out << "new_nodetree.links.new(kcolor_nodes[-1][0].outputs[0], pnode.inputs['Prev Color'])\n" "new_nodetree.links.new(kcolor_nodes[-1][1].outputs[0], pnode.inputs['Prev Alpha'])\n"; } /* Row Break in gridder */ out << "gridder.row_break(2)\n"; }