nifskope icon indicating copy to clipboard operation
nifskope copied to clipboard

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

Open JMPZ11 opened this issue 10 months ago • 5 comments

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.

JMPZ11 avatar Apr 06 '24 05:04 JMPZ11

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

JMPZ11 avatar Apr 06 '24 06:04 JMPZ11

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 )


JMPZ11 avatar Apr 06 '24 22:04 JMPZ11

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 avatar Apr 08 '24 09:04 fo76utils

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).
  • 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.

fo76utils avatar Apr 09 '24 17:04 fo76utils

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: @.***>

JMPZ11 avatar Apr 10 '24 03:04 JMPZ11

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

JMPZ11 avatar May 12 '24 20:05 JMPZ11