Exemple #1
0
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();
}
Exemple #2
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();
}
Exemple #3
0
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;
}
Exemple #4
0
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";
}