mirror of https://github.com/AxioDL/metaforce.git
More RigInverter work
This commit is contained in:
parent
9ee8840b54
commit
1b1a94c649
|
@ -85,69 +85,56 @@ bool ReadANCSToBlender(hecl::BlenderConnection& conn,
|
||||||
/* Establish ANCS blend */
|
/* Establish ANCS blend */
|
||||||
if (!conn.createBlend(outPath, hecl::BlenderConnection::BlendType::Actor))
|
if (!conn.createBlend(outPath, hecl::BlenderConnection::BlendType::Actor))
|
||||||
return false;
|
return false;
|
||||||
hecl::BlenderConnection::PyOutStream os = conn.beginPythonOut(true);
|
|
||||||
|
|
||||||
os.format("import bpy\n"
|
std::string firstName;
|
||||||
"from mathutils import Vector\n"
|
typename ANCSDNA::CINFType firstCinf;
|
||||||
"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':\n"
|
|
||||||
" bpy.context.scene.objects.unlink(ob)\n"
|
|
||||||
" bpy.data.objects.remove(ob)\n"
|
|
||||||
"\n"
|
|
||||||
"actor_data = bpy.context.scene.hecl_sact_data\n",
|
|
||||||
pakRouter.getBestEntryName(entry).c_str());
|
|
||||||
|
|
||||||
typename ANCSDNA::CINFType cinf;
|
|
||||||
std::unordered_set<typename PAKRouter::IDType> cinfsDone;
|
|
||||||
for (const auto& info : chResInfo)
|
|
||||||
{
|
{
|
||||||
/* Provide data to add-on */
|
hecl::BlenderConnection::PyOutStream os = conn.beginPythonOut(true);
|
||||||
os.format("actor_subtype = actor_data.subtypes.add()\n"
|
|
||||||
"actor_subtype.name = '%s'\n\n",
|
|
||||||
info.name.c_str());
|
|
||||||
|
|
||||||
/* Build CINF if needed */
|
os.format("import bpy\n"
|
||||||
if (cinfsDone.find(info.cinf) == cinfsDone.end())
|
"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':\n"
|
||||||
|
" bpy.context.scene.objects.unlink(ob)\n"
|
||||||
|
" bpy.data.objects.remove(ob)\n"
|
||||||
|
"\n"
|
||||||
|
"actor_data = bpy.context.scene.hecl_sact_data\n",
|
||||||
|
pakRouter.getBestEntryName(entry).c_str());
|
||||||
|
|
||||||
|
std::unordered_set<typename PAKRouter::IDType> cinfsDone;
|
||||||
|
for (const auto& info : chResInfo)
|
||||||
{
|
{
|
||||||
pakRouter.lookupAndReadDNA(info.cinf, cinf);
|
/* Provide data to add-on */
|
||||||
cinf.sendCINFToBlender(os, info.cinf);
|
os.format("actor_subtype = actor_data.subtypes.add()\n"
|
||||||
cinfsDone.insert(info.cinf);
|
"actor_subtype.name = '%s'\n\n",
|
||||||
}
|
info.name.c_str());
|
||||||
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 */
|
/* Build CINF if needed */
|
||||||
const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(info.cmdl, nullptr, true, true);
|
if (cinfsDone.find(info.cinf) == cinfsDone.end())
|
||||||
if (cmdlE)
|
{
|
||||||
{
|
typename ANCSDNA::CINFType cinf;
|
||||||
hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE);
|
pakRouter.lookupAndReadDNA(info.cinf, cinf);
|
||||||
os.linkBlend(cmdlPath.getAbsolutePathUTF8().c_str(),
|
cinf.sendCINFToBlender(os, info.cinf);
|
||||||
pakRouter.getBestEntryName(*cmdlE).c_str(), true);
|
if (cinfsDone.empty())
|
||||||
|
{
|
||||||
/* Attach CMDL to CINF */
|
firstName = ANCSDNA::CINFType::GetCINFArmatureName(info.cinf);
|
||||||
os << "if obj.name not in bpy.context.scene.objects:\n"
|
firstCinf = cinf;
|
||||||
" bpy.context.scene.objects.link(obj)\n"
|
}
|
||||||
"obj.parent = arm_obj\n"
|
cinfsDone.insert(info.cinf);
|
||||||
"obj.parent_type = 'ARMATURE'\n"
|
}
|
||||||
"actor_subtype.linked_mesh = obj.name\n\n";
|
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 overlays */
|
|
||||||
for (const auto& overlay : info.overlays)
|
|
||||||
{
|
|
||||||
os << "overlay = actor_subtype.overlays.add()\n";
|
|
||||||
os.format("overlay.name = '%s'\n", overlay.first.toString().c_str());
|
|
||||||
|
|
||||||
/* Link CMDL */
|
/* Link CMDL */
|
||||||
const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(overlay.second.first, nullptr, true, true);
|
const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(info.cmdl, nullptr, true, true);
|
||||||
if (cmdlE)
|
if (cmdlE)
|
||||||
{
|
{
|
||||||
hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE);
|
hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE);
|
||||||
|
@ -159,31 +146,62 @@ bool ReadANCSToBlender(hecl::BlenderConnection& conn,
|
||||||
" bpy.context.scene.objects.link(obj)\n"
|
" bpy.context.scene.objects.link(obj)\n"
|
||||||
"obj.parent = arm_obj\n"
|
"obj.parent = arm_obj\n"
|
||||||
"obj.parent_type = 'ARMATURE'\n"
|
"obj.parent_type = 'ARMATURE'\n"
|
||||||
"overlay.linked_mesh = obj.name\n\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.toString().c_str());
|
||||||
|
|
||||||
|
/* Link CMDL */
|
||||||
|
const typename PAKRouter::EntryType* cmdlE = pakRouter.lookupEntry(overlay.second.first, nullptr, true, true);
|
||||||
|
if (cmdlE)
|
||||||
|
{
|
||||||
|
hecl::ProjectPath cmdlPath = pakRouter.getWorking(cmdlE);
|
||||||
|
os.linkBlend(cmdlPath.getAbsolutePathUTF8().c_str(),
|
||||||
|
pakRouter.getBestEntryName(*cmdlE).c_str(), 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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DNAANIM::RigInverter<typename ANCSDNA::CINFType> inverter(cinf);
|
|
||||||
|
|
||||||
/* Get animation primitives */
|
|
||||||
std::map<atUint32, AnimationResInfo<typename PAKRouter::IDType>> animResInfo;
|
|
||||||
ancs.getAnimationResInfo(animResInfo);
|
|
||||||
for (const auto& id : animResInfo)
|
|
||||||
{
|
{
|
||||||
typename ANCSDNA::ANIMType anim;
|
hecl::BlenderConnection::DataStream ds = conn.beginData();
|
||||||
if (pakRouter.lookupAndReadDNA(id.second.animId, anim, true))
|
std::unordered_map<std::string,
|
||||||
|
hecl::BlenderConnection::DataStream::Matrix3f> matrices = ds.getBoneMatrices(firstName);
|
||||||
|
ds.close();
|
||||||
|
DNAANIM::RigInverter<typename ANCSDNA::CINFType> inverter(firstCinf, matrices);
|
||||||
|
|
||||||
|
hecl::BlenderConnection::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(animResInfo);
|
||||||
|
for (const auto& id : animResInfo)
|
||||||
{
|
{
|
||||||
os.format("act = bpy.data.actions.new('%s')\n"
|
typename ANCSDNA::ANIMType anim;
|
||||||
"act.use_fake_user = True\n", id.second.name.c_str());
|
if (pakRouter.lookupAndReadDNA(id.second.animId, anim, true))
|
||||||
anim.sendANIMToBlender(os, inverter, id.second.additive);
|
{
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
os.format("actor_action = actor_data.actions.add()\n"
|
|
||||||
"actor_action.name = '%s'\n", id.second.name.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
os.close();
|
|
||||||
conn.saveBlend();
|
conn.saveBlend();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,6 @@ RigInverter<CINFType>::Bone::Bone(const CINFType& cinf, const typename CINFType:
|
||||||
m_tail /= float(actualChildren);
|
m_tail /= float(actualChildren);
|
||||||
if (m_tail.magSquared() < 0.001f)
|
if (m_tail.magSquared() < 0.001f)
|
||||||
m_tail = naturalTail;
|
m_tail = naturalTail;
|
||||||
else
|
|
||||||
m_inverter = zeus::CQuaternion(m_tail, naturalTail);
|
|
||||||
}
|
}
|
||||||
else if (parentIdx != -1)
|
else if (parentIdx != -1)
|
||||||
{
|
{
|
||||||
|
@ -62,8 +60,6 @@ RigInverter<CINFType>::Bone::Bone(const CINFType& cinf, const typename CINFType:
|
||||||
m_tail = zeus::CVector3f(origBone.origin) * 2.f - zeus::CVector3f(pBone.origin);
|
m_tail = zeus::CVector3f(origBone.origin) * 2.f - zeus::CVector3f(pBone.origin);
|
||||||
if (m_tail.magSquared() < 0.001f)
|
if (m_tail.magSquared() < 0.001f)
|
||||||
m_tail = naturalTail;
|
m_tail = naturalTail;
|
||||||
else
|
|
||||||
m_inverter = zeus::CQuaternion(m_tail, naturalTail);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -81,13 +77,38 @@ RigInverter<CINFType>::RigInverter(const CINFType& cinf)
|
||||||
m_bones.emplace_back(cinf, b);
|
m_bones.emplace_back(cinf, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class CINFType>
|
||||||
|
RigInverter<CINFType>::RigInverter(const CINFType& cinf,
|
||||||
|
const std::unordered_map<std::string,
|
||||||
|
hecl::BlenderConnection::DataStream::Matrix3f>& matrices)
|
||||||
|
: m_cinf(cinf)
|
||||||
|
{
|
||||||
|
m_bones.reserve(cinf.bones.size());
|
||||||
|
for (const typename CINFType::Bone& b : cinf.bones)
|
||||||
|
{
|
||||||
|
m_bones.emplace_back(cinf, b);
|
||||||
|
const std::string* name = cinf.getBoneNameFromId(b.id);
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
auto search = matrices.find(*name);
|
||||||
|
if (search != matrices.cend())
|
||||||
|
{
|
||||||
|
m_bones.back().m_inverter = zeus::CMatrix3f(search->second[0],
|
||||||
|
search->second[1],
|
||||||
|
search->second[2]);
|
||||||
|
m_bones.back().m_restorer = m_bones.back().m_inverter.transposed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class CINFType>
|
template <class CINFType>
|
||||||
zeus::CQuaternion
|
zeus::CQuaternion
|
||||||
RigInverter<CINFType>::transformRotation(atUint32 boneId, const zeus::CQuaternion& origRot) const
|
RigInverter<CINFType>::transformRotation(atUint32 boneId, const zeus::CQuaternion& origRot) const
|
||||||
{
|
{
|
||||||
for (const Bone& b : m_bones)
|
for (const Bone& b : m_bones)
|
||||||
if (b.m_origBone.id == boneId)
|
if (b.m_origBone.id == boneId)
|
||||||
return b.m_inverter * origRot;
|
return b.m_inverter * zeus::CMatrix3f(origRot) * b.m_restorer;
|
||||||
return origRot;
|
return origRot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
#define __COMMON_RIGINVERTER_HPP__
|
#define __COMMON_RIGINVERTER_HPP__
|
||||||
|
|
||||||
#include "zeus/CVector3f.hpp"
|
#include "zeus/CVector3f.hpp"
|
||||||
|
#include "zeus/CMatrix3f.hpp"
|
||||||
#include "zeus/CQuaternion.hpp"
|
#include "zeus/CQuaternion.hpp"
|
||||||
|
#include "BlenderConnection.hpp"
|
||||||
|
|
||||||
namespace DataSpec
|
namespace DataSpec
|
||||||
{
|
{
|
||||||
|
@ -18,7 +20,8 @@ public:
|
||||||
struct Bone
|
struct Bone
|
||||||
{
|
{
|
||||||
const typename CINFType::Bone& m_origBone;
|
const typename CINFType::Bone& m_origBone;
|
||||||
zeus::CQuaternion m_inverter;
|
zeus::CMatrix3f m_inverter;
|
||||||
|
zeus::CMatrix3f m_restorer;
|
||||||
zeus::CVector3f m_tail;
|
zeus::CVector3f m_tail;
|
||||||
zeus::CVector3f m_parentDelta;
|
zeus::CVector3f m_parentDelta;
|
||||||
Bone(const CINFType& cinf, const typename CINFType::Bone& origBone);
|
Bone(const CINFType& cinf, const typename CINFType::Bone& origBone);
|
||||||
|
@ -28,6 +31,9 @@ private:
|
||||||
std::vector<Bone> m_bones;
|
std::vector<Bone> m_bones;
|
||||||
public:
|
public:
|
||||||
RigInverter(const CINFType& cinf);
|
RigInverter(const CINFType& cinf);
|
||||||
|
RigInverter(const CINFType& cinf,
|
||||||
|
const std::unordered_map<std::string,
|
||||||
|
hecl::BlenderConnection::DataStream::Matrix3f>& matrices);
|
||||||
const CINFType& getCINF() const {return m_cinf;}
|
const CINFType& getCINF() const {return m_cinf;}
|
||||||
const std::vector<Bone>& getBones() const {return m_bones;}
|
const std::vector<Bone>& getBones() const {return m_bones;}
|
||||||
zeus::CQuaternion transformRotation(atUint32 boneId, const zeus::CQuaternion& origRot) const;
|
zeus::CQuaternion transformRotation(atUint32 boneId, const zeus::CQuaternion& origRot) const;
|
||||||
|
|
|
@ -13,6 +13,10 @@ void ANIM::IANIM::sendANIMToBlender(hecl::BlenderConnection::PyOutStream& os, co
|
||||||
os.format("act.hecl_fps = round(%f)\n", (1.0f / mainInterval));
|
os.format("act.hecl_fps = round(%f)\n", (1.0f / mainInterval));
|
||||||
|
|
||||||
auto kit = chanKeys.begin();
|
auto kit = chanKeys.begin();
|
||||||
|
|
||||||
|
std::vector<zeus::CQuaternion> fixedRotKeys;
|
||||||
|
std::vector<zeus::CVector3f> fixedTransKeys;
|
||||||
|
|
||||||
for (const std::pair<atUint32, bool>& bone : bones)
|
for (const std::pair<atUint32, bool>& bone : bones)
|
||||||
{
|
{
|
||||||
const std::string* bName = rig.getCINF().getBoneNameFromId(bone.first);
|
const std::string* bName = rig.getCINF().getBoneNameFromId(bone.first);
|
||||||
|
@ -45,13 +49,14 @@ void ANIM::IANIM::sendANIMToBlender(hecl::BlenderConnection::PyOutStream& os, co
|
||||||
"crv.keyframe_points[-1].interpolation = 'LINEAR'\n"
|
"crv.keyframe_points[-1].interpolation = 'LINEAR'\n"
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
|
if (bone.first == 3)
|
||||||
|
printf("");
|
||||||
ANIMOutStream ao = os.beginANIMCurve();
|
ANIMOutStream ao = os.beginANIMCurve();
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::vector<DNAANIM::Value>& rotKeys = *kit++;
|
const std::vector<DNAANIM::Value>& rotKeys = *kit++;
|
||||||
std::vector<zeus::CQuaternion> fixedRotKeys;
|
fixedRotKeys.clear();
|
||||||
fixedRotKeys.resize(rotKeys.size());
|
fixedRotKeys.resize(rotKeys.size());
|
||||||
fprintf(stderr, "alloc %d\n", rotKeys.size());
|
|
||||||
|
|
||||||
for (int c=0 ; c<4 ; ++c)
|
for (int c=0 ; c<4 ; ++c)
|
||||||
{
|
{
|
||||||
|
@ -70,14 +75,12 @@ void ANIM::IANIM::sendANIMToBlender(hecl::BlenderConnection::PyOutStream& os, co
|
||||||
for (const zeus::CQuaternion& val : fixedRotKeys)
|
for (const zeus::CQuaternion& val : fixedRotKeys)
|
||||||
ao.write(*frameit++, val[c]);
|
ao.write(*frameit++, val[c]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "size %d\n", fixedRotKeys.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bone.second)
|
if (bone.second)
|
||||||
{
|
{
|
||||||
const std::vector<DNAANIM::Value>& transKeys = *kit++;
|
const std::vector<DNAANIM::Value>& transKeys = *kit++;
|
||||||
std::vector<zeus::CVector3f> fixedTransKeys;
|
fixedTransKeys.clear();
|
||||||
fixedTransKeys.resize(transKeys.size());
|
fixedTransKeys.resize(transKeys.size());
|
||||||
|
|
||||||
for (int c=0 ; c<3 ; ++c)
|
for (int c=0 ; c<3 ; ++c)
|
||||||
|
|
|
@ -87,7 +87,7 @@ struct CINF : BigDNA
|
||||||
void sendCINFToBlender(hecl::BlenderConnection::PyOutStream& os, const UniqueID32& cinfId) const
|
void sendCINFToBlender(hecl::BlenderConnection::PyOutStream& os, const UniqueID32& cinfId) const
|
||||||
{
|
{
|
||||||
DNAANIM::RigInverter<CINF> inverter(*this);
|
DNAANIM::RigInverter<CINF> inverter(*this);
|
||||||
|
|
||||||
os.format("arm = bpy.data.armatures.new('CINF_%08X')\n"
|
os.format("arm = bpy.data.armatures.new('CINF_%08X')\n"
|
||||||
"arm_obj = bpy.data.objects.new(arm.name, arm)\n"
|
"arm_obj = bpy.data.objects.new(arm.name, arm)\n"
|
||||||
"bpy.context.scene.objects.link(arm_obj)\n"
|
"bpy.context.scene.objects.link(arm_obj)\n"
|
||||||
|
@ -114,6 +114,11 @@ struct CINF : BigDNA
|
||||||
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetCINFArmatureName(const UniqueID32& cinfId)
|
||||||
|
{
|
||||||
|
return hecl::Format("CINF_%08X", cinfId.toUint32());
|
||||||
|
}
|
||||||
|
|
||||||
CINF() = default;
|
CINF() = default;
|
||||||
using Armature = hecl::BlenderConnection::DataStream::Actor::Armature;
|
using Armature = hecl::BlenderConnection::DataStream::Actor::Armature;
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,11 @@ struct CINF : BigDNA
|
||||||
|
|
||||||
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetCINFArmatureName(const UniqueID32& cinfId)
|
||||||
|
{
|
||||||
|
return hecl::Format("CINF_%08X", cinfId.toUint32());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,11 @@ struct CINF : DNAMP2::CINF
|
||||||
|
|
||||||
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
os << "bpy.ops.object.mode_set(mode='OBJECT')\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetCINFArmatureName(const UniqueID64& cinfId)
|
||||||
|
{
|
||||||
|
return hecl::Format("CINF_%016" PRIX64, cinfId.toUint64());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
2
hecl
2
hecl
|
@ -1 +1 @@
|
||||||
Subproject commit 71e97f724085893d27eaab6da71abf02aa37cb1b
|
Subproject commit 76788ee293a8e25131e6fae118b9337f70b8f371
|
2
specter
2
specter
|
@ -1 +1 @@
|
||||||
Subproject commit a1d60af72c17c4df4152f36766ec9e39c9c56200
|
Subproject commit 38768581b2fb0504a4e1043333176ebc9bc86d13
|
Loading…
Reference in New Issue