hexabits / nifskope

Other
245 stars 54 forks source link

Feature Request: Means of updating Starfield's .mesh paths in bulk #76

Closed JasonJShuler closed 3 months ago

JasonJShuler commented 5 months ago

Need a means of bulk updating mesh paths in a Starfield.nif.

Thanks to the recursive export feature mentioned in #75, I am able to use the blender plugin to import the entire nif. This dramatically simplified the process of punching holes (and as a bonus I got all the LODSs as well) but... the .nif file it creates is just all messed up (it seems targetted toward outfits and armor rather than ship modules). The meshes, however, seem fine.

I made a little batch script to move / rename all the exported meshes back to the vanilla paths so I could use it with the original .nif.

It works like a charm... except I can't leave them in the vanilla paths - it would override the source object. And I don't really have as way to tell which meshes were changed... so need them all probably.

At the most basic level, just having the ability to add a prefix or suffix without renaming would at least make it possible. Preferably something like regex replace, or even better - rename them all to be in a single folder with more meaningful names.

Another alternative would be if I could somehow copy the paths from the plugin output nif onto the original..

(yet another would be if the .nif could be exported to xml to do some regex replace and then imported?

This while file pointer to meshes business really is pain isn't it...

Thank you so much for all of this. Truly amazing work.

JasonJShuler commented 5 months ago

I'm taking a stab at hacking in my own hardcoded spell - I'm no c++ guy so hacking is the operative word lol

JasonJShuler commented 5 months ago

I used this to rename and everything looks fine in nifskope, but the model is missing in Starfield...

#include "spellbook.h"

#include <QDialog>
#include <QFileDialog>
#include <QSettings>

#include "gamemanager.h"
#include "libfo76utils/src/common.hpp"
#include "libfo76utils/src/filebuf.hpp"
#include "libfo76utils/src/material.hpp"

#ifdef Q_OS_WIN32
#  include <direct.h>
#else
#  include <sys/stat.h>
#endif

// Brief description is deliberately not autolinked to class Spell
/*! \file filerename.cpp
 * \brief Prepend "example" folder to all meshes (spRenameAllResources)
 *
 * All classes here inherit from the Spell class.
 */

//! 
class spRenameAllResources final : public Spell
{
public:
    QString name() const override final { return Spell::tr( "Add example prefix to all meshes"); }
    QString page() const override final { return Spell::tr( "" ); }
    QIcon icon() const override final
    {
        return QIcon();
    }
    bool constant() const override final { return true; }
    bool instant() const override final { return true; }

    bool isApplicable( const NifModel * nif, const QModelIndex & index ) override final
    {
        return ( nif && !index.isValid() );
    }

    static bool is_Applicable2( const NifModel * nif, NifItem * item )
    {
        NifValue::Type  vt = item->valueType();
        if ( vt != NifValue::tStringIndex && vt != NifValue::tSizedString ) {
            if ( !( nif->checkVersion( 0x14010003, 0 ) && ( vt == NifValue::tString || vt == NifValue::tFilePath ) ) )
                return false;
        }
        do {
            if ( item->parent() && nif && nif->getBSVersion() >= 130 ) {
                if ( item->name() == "Name" && ( item->parent()->name() == "BSLightingShaderProperty" || item->parent()->name() == "BSEffectShaderProperty" ) )
                    break;      // Fallout 4, 76 or Starfield material
            }
            if ( item->parent() && item->parent()->name() == "Textures" )
                break;
            if ( item->name() == "Path" || item->name() == "Mesh Path" || item->name().startsWith( "Texture " ) )
                break;
            return false;
        } while ( false );
        return !( nif->resolveString( item ).isEmpty() );
    }

    bool updateNifItemFilePath( NifModel * nif, NifItem * item, std::string prefix );
    void renameFiles( NifModel * nif, NifItem * item, std::string prefix );
    QModelIndex cast( NifModel * nif, const QModelIndex & index ) override final;
};

bool spRenameAllResources::updateNifItemFilePath( NifModel * nif, NifItem * item, std::string prefix)
{
    quint32 bsVersion = nif->getBSVersion();
    if ( bsVersion >= 160 && item->name() == "Mesh Path" ) {
        QString filePath( nif->resolveString( item ) );
        if ( filePath.isEmpty() )
            return false;
        nif->assignString(item, QString::fromStdString(prefix) + filePath, true);
        return true;
    }

return false;
}

//recursive
void spRenameAllResources::renameFiles(NifModel * nif, NifItem * item, std::string prefix )
{
    //processs current node
    if ( spRenameAllResources::is_Applicable2( nif, item ) ) {
        spRenameAllResources::updateNifItemFilePath( nif, item, prefix );
    }
    // process children
    for ( int i = 0; i < item->childCount(); i++ ) {
        if ( item->child( i ) )
            renameFiles(nif, item->child( i ) , prefix);
    }
}

QModelIndex spRenameAllResources::cast( NifModel * nif, const QModelIndex & index )
{
    if ( !nif )
        return index;
    Game::GameMode  game = Game::GameManager::get_game( nif->getVersionNumber(), nif->getUserVersion(), nif->getBSVersion() );

    std::set< std::string > fileSet;
    for ( int b = 0; b < nif->getBlockCount(); b++ ) {
        NifItem * item = nif->getBlockItem( quint32(b) );
        if ( item )
        // THIS IS WHERE YOU CHANGE PREFIX
            renameFiles(nif, item, "example\\");
    }
    return index;
}

REGISTER_SPELL( spRenameAllResources )
fo76utils commented 5 months ago

I will look into implementing this feature, it would not be difficult to use regular expression search/replace, since QString already has a function that implements that.

fo76utils commented 5 months ago

A spell for this purpose is now added, it can be found in the main menu as "Search/Replace Resource Paths". It takes the following as input:

All replacements need to be confirmed in a message box.

JasonJShuler commented 5 months ago

Holy cats, you are a legend! Thank you so much! Also major kudos on the source code layout, design and readability. I usually have to spend days grappling c++ - and I did literally pull out some hair while hacking this together. And your code is top notch. sigh c++ is always good at making me feel inadequate.

On Tue, Apr 9, 2024 at 1:07 PM fo76utils @.***> wrote:

A spell for this purpose is now added, it can be found in the main menu as "Search/Replace Resource Paths". It takes the following as input:

  • Regular expression to search for: the pattern to be replaced, note that characters like \ need to be escaped.
  • Replacement text: this is what every match of the above will be replaced with. It may reference groups defined with parentheses in the regular expression, using \1, \2, etc. (see the example here https://doc.qt.io/qt-5/qstring.html#replace-12).
  • Path filter regular expression: only paths matching this are processed. If the field is left empty, then it matches everything.

All replacements need to be confirmed in a message box.

— Reply to this email directly, view it on GitHub https://github.com/hexabits/nifskope/issues/76#issuecomment-2045700753, or unsubscribe https://github.com/notifications/unsubscribe-auth/AHDGDXPF4FKQ55CUG5BHOUDY4QN6BAVCNFSM6AAAAABF2EURIOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBVG4YDANZVGM . You are receiving this because you authored the thread.Message ID: @.***>

JasonJShuler commented 3 months ago

This is great, thank you - sorry for the delayed response. Closing!