From 2044252221575c49e4df5559f04bb21b42e1743a Mon Sep 17 00:00:00 2001
From: Tazpn <tazpn@users.sourceforge.net>
Date: Sun, 24 Jun 2007 17:05:26 +0000
Subject: [PATCH] More Geomorph related changes

---
 NifCommon/nimorph.cpp         | 545 ++++++++++++++++++++++++++++++++++
 NifCommon/niutils.cpp         |  42 ---
 NifCommon/niutils.h           |   9 +
 NifExport/Animation.cpp       |  34 +++
 NifExport/Exporter.cpp        |   2 +
 NifExport/Exporter.h          |   2 +
 NifExport/Util.cpp            |  19 +-
 NifImport/ImportAnimation.cpp | 179 +----------
 NifPlugins_VC80.vcproj        |   4 +
 9 files changed, 616 insertions(+), 220 deletions(-)
 create mode 100644 NifCommon/nimorph.cpp

diff --git a/NifCommon/nimorph.cpp b/NifCommon/nimorph.cpp
new file mode 100644
index 0000000..9904715
--- /dev/null
+++ b/NifCommon/nimorph.cpp
@@ -0,0 +1,545 @@
+/**********************************************************************
+*<
+FILE: NIUtils.cpp
+
+DESCRIPTION:	NifImporter Utilities
+
+CREATED BY: tazpn (Theo)
+
+HISTORY: 
+
+*>	Copyright (c) 2006, All Rights Reserved.
+**********************************************************************/
+#include "pch.h"
+#include "niutils.h"
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <malloc.h>
+#include <sstream>
+#include <modstack.h>
+#include <iparamb2.h>
+#include <iskin.h>
+#include "../NifProps/bhkRigidBodyInterface.h"
+
+#ifdef USE_BIPED
+#  include <cs/BipedApi.h>
+#  include <cs/OurExp.h> 
+#endif
+#include "maxscrpt\Strings.h"
+#include "maxscrpt\Parser.h"
+
+using namespace std;
+using namespace Niflib;
+
+Modifier *GetMorpherModifier(INode* node)
+{
+	const Class_ID MORPHERMODIFIER_CLASS_ID(0x17bb6854, 0xa5cba2a3);
+
+	Object* pObj = node->GetObjectRef();
+	if (!pObj) return NULL;
+	while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID)
+	{
+		IDerivedObject* pDerObj = (IDerivedObject *)(pObj);
+		int Idx = 0;
+		while (Idx < pDerObj->NumModifiers())
+		{
+			// Get the modifier. 
+			Modifier* mod = pDerObj->GetModifier(Idx);
+			if (mod->ClassID() == MORPHERMODIFIER_CLASS_ID)
+			{
+				return mod;
+			}
+			Idx++;
+		}
+		pObj = pDerObj->GetObjRef();
+	}
+	return NULL;
+}
+
+Modifier *CreateMorpherModifier(INode* node)
+{
+	const Class_ID MORPHERMODIFIER_CLASS_ID(0x17bb6854, 0xa5cba2a3);
+
+	Modifier *mod = GetMorpherModifier(node);
+	if (mod == NULL)
+	{
+		IDerivedObject *dobj = CreateDerivedObject(node->GetObjectRef());
+		mod = (Modifier*) CreateInstance(OSM_CLASS_ID, MORPHERMODIFIER_CLASS_ID);
+		dobj->SetAFlag(A_LOCK_TARGET);
+		dobj->AddModifier(mod);
+		dobj->ClearAFlag(A_LOCK_TARGET);
+		node->SetObjectRef(dobj);
+	}
+	return mod;
+}
+
+// CallMaxscript
+// Send the string to maxscript 
+//
+void MorpherBuildFromNode(Modifier* mod, int index, INode *target)
+{
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	six_value_locals(name, fn, mod, index, target, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_BuildFromNode"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[3];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+			args[2] = vl.target = MAXNode::intern(target);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 3);
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+}
+
+TSTR MorpherGetName(Modifier* mod, int index)
+{
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	six_value_locals(name, fn, mod, index, value, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	TSTR string;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_GetName"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[2];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
+			if (vl.result->tag == class_tag(String)) {
+				string = vl.result->to_string();
+			}
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+	return string;
+}
+
+void MorpherSetName(Modifier* mod, int index, TSTR& name)
+{
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	six_value_locals(name, fn, mod, index, value, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	String* value = new String(name);
+
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_SetName"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[3];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+			args[2] = vl.value = value;
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 3);
+		}
+	} catch (...) {
+		value->collect();
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+}
+
+void MorpherRebuild(Modifier* mod, int index)
+{
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	five_value_locals(name, fn, mod, index, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_Rebuild"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[2];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+}
+
+bool MorpherIsActive(Modifier* mod, int index)
+{
+	bool retval = false;
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	five_value_locals(name, fn, mod, index, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_IsActive"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[2];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
+			if (vl.result->tag == class_tag(Boolean))
+				retval = vl.result->to_bool();
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+	return retval;
+}
+
+bool MorpherHasData(Modifier* mod, int index)
+{
+	bool retval = false;
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	five_value_locals(name, fn, mod, index, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_HasData"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[2];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
+			if (vl.result->tag == class_tag(Boolean))
+				retval = vl.result->to_bool();
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+	return retval;
+}
+
+int MorpherNumProgMorphs(Modifier* mod, int index)
+{
+	int retval = 0;
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	five_value_locals(name, fn, mod, index, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_HasData"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[2];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
+			if (vl.result->tag == class_tag(Integer))
+				retval = vl.result->to_int();
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+	return retval;
+}
+
+INode *MorpherGetProgMorph(Modifier* mod, int index, int morphIdx)
+{
+	INode *retval = 0;
+	// Magic initialization stuff for maxscript.
+	static bool script_initialized = false;
+	if (!script_initialized) {
+		init_MAXScript();
+		script_initialized = TRUE;
+	}
+	init_thread_locals();
+	push_alloc_frame();
+	six_value_locals(name, fn, mod, index, midx, result);
+	save_current_frames();
+	trace_back_active = FALSE;
+	try	{
+		// Create the name of the maxscript function we want.
+		// and look it up in the global names
+		vl.name = Name::intern(_T("WM3_MC_HasData"));
+		vl.fn = globals->get(vl.name);
+
+		// For some reason we get a global thunk back, so lets
+		// check the cell which should point to the function.
+		// Just in case if it points to another global thunk
+		// try it again.
+		while (vl.fn != NULL && is_globalthunk(vl.fn))
+			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
+		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
+			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
+
+		// Now we should have a MAXScriptFunction, which we can
+		// call to do the actual conversion. If we didn't
+		// get a MAXScriptFunction, we can't convert.
+		// class_tag(MAXScriptFunction)
+		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
+			Value* args[3];
+
+			// Ok. WM3_MC_BuildFromNode takes three parameters
+			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
+			args[1] = vl.index = Integer::intern(index);
+			args[2] = vl.midx = Integer::intern(morphIdx);
+
+			// Call the function and save the result.
+			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 3);
+			if (vl.result->tag == class_tag(MAXNode))
+				retval = vl.result->to_node();
+		}
+	} catch (...) {
+		clear_error_source_data();
+		restore_current_frames();
+		MAXScript_signals = 0;
+		if (progress_bar_up)
+			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
+	}
+
+	// Magic Max Script stuff to clear the frame and locals.
+	pop_value_locals();
+	pop_alloc_frame();
+	return retval;
+}
\ No newline at end of file
diff --git a/NifCommon/niutils.cpp b/NifCommon/niutils.cpp
index ade1713..f1f6362 100644
--- a/NifCommon/niutils.cpp
+++ b/NifCommon/niutils.cpp
@@ -1184,45 +1184,3 @@ Matrix3 GetLocalTM(INode *node)
 		return node->GetNodeTM(0);
 	}
 }
-
-Modifier *GetMorpherModifier(INode* node)
-{
-	const Class_ID MORPHERMODIFIER_CLASS_ID(0x17bb6854, 0xa5cba2a3);
-
-	Object* pObj = node->GetObjectRef();
-	if (!pObj) return NULL;
-	while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID)
-	{
-		IDerivedObject* pDerObj = (IDerivedObject *)(pObj);
-		int Idx = 0;
-		while (Idx < pDerObj->NumModifiers())
-		{
-			// Get the modifier. 
-			Modifier* mod = pDerObj->GetModifier(Idx);
-			if (mod->ClassID() == MORPHERMODIFIER_CLASS_ID)
-			{
-				return mod;
-			}
-			Idx++;
-		}
-		pObj = pDerObj->GetObjRef();
-	}
-	return NULL;
-}
-
-Modifier *CreateMorpherModifier(INode* node)
-{
-	const Class_ID MORPHERMODIFIER_CLASS_ID(0x17bb6854, 0xa5cba2a3);
-
-	Modifier *mod = GetMorpherModifier(node);
-	if (mod == NULL)
-	{
-		IDerivedObject *dobj = CreateDerivedObject(node->GetObjectRef());
-		mod = (Modifier*) CreateInstance(OSM_CLASS_ID, MORPHERMODIFIER_CLASS_ID);
-		dobj->SetAFlag(A_LOCK_TARGET);
-		dobj->AddModifier(mod);
-		dobj->ClearAFlag(A_LOCK_TARGET);
-		node->SetObjectRef(dobj);
-	}
-	return mod;
-}
diff --git a/NifCommon/niutils.h b/NifCommon/niutils.h
index feef397..b9ef787 100644
--- a/NifCommon/niutils.h
+++ b/NifCommon/niutils.h
@@ -430,7 +430,16 @@ void GetIniFileName(char *iniName);
 
 Matrix3 GetLocalTM(INode *node);
 
+// Morph related routines in nimorph.cpp
 extern Modifier *GetMorpherModifier(INode* node);
 extern Modifier *CreateMorpherModifier(INode* node);
+extern void MorpherBuildFromNode(Modifier* mod, int index, INode *target);
+extern void MorpherSetName(Modifier* mod, int index, TSTR& name);
+extern void MorpherRebuild(Modifier* mod, int index);
+extern TSTR MorpherGetName(Modifier* mod, int index);
+extern bool MorpherIsActive(Modifier* mod, int index);
+extern bool MorpherHasData(Modifier* mod, int index);
+extern int MorpherNumProgMorphs(Modifier* mod, int index);
+extern INode *MorpherGetProgMorph(Modifier* mod, int index, int morphIdx);
 
 #endif // _NIUTILS_H_
\ No newline at end of file
diff --git a/NifExport/Animation.cpp b/NifExport/Animation.cpp
index c6a07f1..8a5af4d 100644
--- a/NifExport/Animation.cpp
+++ b/NifExport/Animation.cpp
@@ -36,6 +36,11 @@ HISTORY:
 #include <obj/NiBSplineCompTransformInterpolator.h>
 #include <obj/NiDefaultAVObjectPalette.h>
 #include <obj/NiMultiTargetTransformController.h>
+#include <obj/NiGeomMorpherController.h>
+#include <obj/NiMorphData.h>
+#include <obj/NiBSplineCompFloatInterpolator.h>
+#include <obj/NiFloatInterpolator.h>
+#include <obj/NiFloatData.h>
 using namespace Niflib;
 
 const Class_ID IPOS_CONTROL_CLASS_ID = Class_ID(0x118f7e02,0xffee238a);
@@ -241,6 +246,35 @@ bool Exporter::isNodeTracked(INode *node)
    return false;
 }
 
+Exporter::Result Exporter::scanForAnimation(INode *node)
+{   
+	if (NULL == node) 
+		return Exporter::Skip;
+
+	// Ideally check for Morph: targets
+#if VERSION_3DSMAX >= ((8000<<16)+(15<<8)+0) // Version 8+
+	if (Modifier * mod = GetMorpherModifier(node)){
+		int idx = -1;
+		for (int i=1; i<=100; ++i) {
+			if (MorpherIsActive(mod, i) && MorpherHasData(mod, i)) {
+				TSTR str = MorpherGetName(mod, i);
+				int nodes = MorpherNumProgMorphs(mod, i);
+				for (int j=1; j<=nodes; j++)
+				{
+					if (INode *morph = MorpherGetProgMorph(mod, i, j))
+					{
+						markAsHandled(morph);
+					}
+				}
+			}
+		}
+	}
+	for (int i=0; i<node->NumberOfChildren(); i++) {
+		scanForIgnore(node->GetChildNode(i));
+	}
+#endif
+	return Exporter::Ok;
+}
 
 static bool HasKeys(Control *c)
 {
diff --git a/NifExport/Exporter.cpp b/NifExport/Exporter.cpp
index 6580fb8..ac68da3 100755
--- a/NifExport/Exporter.cpp
+++ b/NifExport/Exporter.cpp
@@ -113,7 +113,9 @@ Exporter::Result Exporter::doExport(NiNodeRef &root, INode *node)
    }
 
    // Always Scan for Collision Nodes first
+   scanForIgnore(node);
    scanForCollision(node);
+   scanForAnimation(node);
 
    mNiRoot = root;
    if (mSelectedOnly) {
diff --git a/NifExport/Exporter.h b/NifExport/Exporter.h
index b729c6d..feb1b66 100755
--- a/NifExport/Exporter.h
+++ b/NifExport/Exporter.h
@@ -169,6 +169,8 @@ public:
 	bool					isCollision(INode *node);
 	bool					isHandled(INode *node);
 	bool					markAsHandled(INode* node);
+	Result					scanForAnimation(INode* node);
+	Result					scanForIgnore(INode *node);
 
 	/* utility functions */
 	Mtl						*getMaterial(INode *node, int subMtl);
diff --git a/NifExport/Util.cpp b/NifExport/Util.cpp
index 0a1c6c0..4e25720 100755
--- a/NifExport/Util.cpp
+++ b/NifExport/Util.cpp
@@ -609,4 +609,21 @@ void Exporter::sortVector3(vector<Vector3>& vector)
 void Exporter::sortFloat4(vector<Float4>& vector)
 {
 	std::stable_sort(vector.begin(), vector.end(), SortVectorEquivalence());
-}
\ No newline at end of file
+}
+
+Exporter::Result Exporter::scanForIgnore(INode *node)
+{   
+	if (NULL == node) 
+		return Exporter::Skip;
+
+	BOOL ignore = FALSE;
+	if (node->GetUserPropBool("np_ignore", ignore))
+	{
+		markAsHandled(node);
+	}
+	for (int i=0; i<node->NumberOfChildren(); i++) {
+		scanForIgnore(node->GetChildNode(i));
+	}
+	return Exporter::Ok;
+}
+
diff --git a/NifImport/ImportAnimation.cpp b/NifImport/ImportAnimation.cpp
index 737961b..0459234 100644
--- a/NifImport/ImportAnimation.cpp
+++ b/NifImport/ImportAnimation.cpp
@@ -105,10 +105,6 @@ struct AnimationImport
 
    bool ImportGeoMorph(INode *n, NiGeomMorpherControllerRef ctrl, float time);
    INode* CreateGeoMesh(const vector<Vector3>& verts, const vector<Triangle>& tris, Matrix3& tm, INode *parent);
-
-   void MorpherBuildFromNode(Modifier* mod, int index, INode *target);
-   void MorpherSetName(Modifier* mod, int index, TSTR& name);
-   void MorpherRebuild(Modifier* mod, int index);
 };
 
 bool NifImporter::ImportAnimation()
@@ -550,7 +546,7 @@ bool KFMImporter::ImportAnimation()
 					 {
 						 int idx = -1;
 						 for (int i=1; i<=100; ++i) {
-							 if (strmatch(var2, mod->SubAnimName(i))) {
+							 if (strmatch(var2, MorpherGetName(mod, i))) {
 								 idx = i;
 								 break;
 							 }
@@ -1131,6 +1127,7 @@ INode *AnimationImport::CreateGeoMesh(
 		tnode->SetPrimaryVisibility(FALSE);
 		tnode->SetSecondaryVisibility(FALSE);
 		tnode->SetWireColor( RGB(0,255,0) );
+		tnode->SetUserPropBool("np_ignore", TRUE);
 
 		returnNode = node->GetINode();
 		
@@ -1141,175 +1138,3 @@ INode *AnimationImport::CreateGeoMesh(
 	return returnNode;
 }
 
-// CallMaxscript
-// Send the string to maxscript 
-//
-void AnimationImport::MorpherBuildFromNode(Modifier* mod, int index, INode *target)
-{
-	// Magic initialization stuff for maxscript.
-	static bool script_initialized = false;
-	if (!script_initialized) {
-		init_MAXScript();
-		script_initialized = TRUE;
-	}
-	init_thread_locals();
-	push_alloc_frame();
-	six_value_locals(name, fn, mod, index, target, result);
-	save_current_frames();
-	trace_back_active = FALSE;
-
-	try	{
-		// Create the name of the maxscript function we want.
-		// and look it up in the global names
-		vl.name = Name::intern(_T("WM3_MC_BuildFromNode"));
-		vl.fn = globals->get(vl.name);
-
-		// For some reason we get a global thunk back, so lets
-		// check the cell which should point to the function.
-		// Just in case if it points to another global thunk
-		// try it again.
-		while (vl.fn != NULL && is_globalthunk(vl.fn))
-			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
-		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
-			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
-
-		// Now we should have a MAXScriptFunction, which we can
-		// call to do the actual conversion. If we didn't
-		// get a MAXScriptFunction, we can't convert.
-		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
-			Value* args[3];
-
-			// Ok. WM3_MC_BuildFromNode takes three parameters
-			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
-			args[1] = vl.index = Integer::intern(index);
-			args[2] = vl.target = MAXNode::intern(target);
-
-			// Call the function and save the result.
-			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 3);
-		}
-	} catch (...) {
-		clear_error_source_data();
-		restore_current_frames();
-		MAXScript_signals = 0;
-		if (progress_bar_up)
-			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
-	}
-
-	// Magic Max Script stuff to clear the frame and locals.
-	pop_value_locals();
-	pop_alloc_frame();
-}
-
-void AnimationImport::MorpherSetName(Modifier* mod, int index, TSTR& name)
-{
-	// Magic initialization stuff for maxscript.
-	static bool script_initialized = false;
-	if (!script_initialized) {
-		init_MAXScript();
-		script_initialized = TRUE;
-	}
-	init_thread_locals();
-	push_alloc_frame();
-	six_value_locals(name, fn, mod, index, value, result);
-	save_current_frames();
-	trace_back_active = FALSE;
-	String* value = new String(name);
-
-	try	{
-		// Create the name of the maxscript function we want.
-		// and look it up in the global names
-		vl.name = Name::intern(_T("WM3_MC_SetName"));
-		vl.fn = globals->get(vl.name);
-
-		// For some reason we get a global thunk back, so lets
-		// check the cell which should point to the function.
-		// Just in case if it points to another global thunk
-		// try it again.
-		while (vl.fn != NULL && is_globalthunk(vl.fn))
-			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
-		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
-			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
-
-		// Now we should have a MAXScriptFunction, which we can
-		// call to do the actual conversion. If we didn't
-		// get a MAXScriptFunction, we can't convert.
-		// class_tag(MAXScriptFunction)
-		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
-			Value* args[3];
-
-			// Ok. WM3_MC_BuildFromNode takes three parameters
-			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
-			args[1] = vl.index = Integer::intern(index);
-			args[2] = vl.value = value;
-
-			// Call the function and save the result.
-			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 3);
-		}
-	} catch (...) {
-		value->collect();
-		clear_error_source_data();
-		restore_current_frames();
-		MAXScript_signals = 0;
-		if (progress_bar_up)
-			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
-	}
-
-	// Magic Max Script stuff to clear the frame and locals.
-	pop_value_locals();
-	pop_alloc_frame();
-}
-
-void AnimationImport::MorpherRebuild(Modifier* mod, int index)
-{
-	// Magic initialization stuff for maxscript.
-	static bool script_initialized = false;
-	if (!script_initialized) {
-		init_MAXScript();
-		script_initialized = TRUE;
-	}
-	init_thread_locals();
-	push_alloc_frame();
-	five_value_locals(name, fn, mod, index, result);
-	save_current_frames();
-	trace_back_active = FALSE;
-	try	{
-		// Create the name of the maxscript function we want.
-		// and look it up in the global names
-		vl.name = Name::intern(_T("WM3_MC_Rebuild"));
-		vl.fn = globals->get(vl.name);
-
-		// For some reason we get a global thunk back, so lets
-		// check the cell which should point to the function.
-		// Just in case if it points to another global thunk
-		// try it again.
-		while (vl.fn != NULL && is_globalthunk(vl.fn))
-			vl.fn = static_cast<GlobalThunk*>(vl.fn)->cell;
-		while (vl.fn != NULL && is_constglobalthunk(vl.fn))
-			vl.fn = static_cast<ConstGlobalThunk*>(vl.fn)->cell;
-
-		// Now we should have a MAXScriptFunction, which we can
-		// call to do the actual conversion. If we didn't
-		// get a MAXScriptFunction, we can't convert.
-		// class_tag(MAXScriptFunction)
-		if (vl.fn != NULL && vl.fn->tag == class_tag(Primitive)) {
-			Value* args[2];
-
-			// Ok. WM3_MC_BuildFromNode takes three parameters
-			args[0] = vl.mod = MAXModifier::intern(mod);	// The original material
-			args[1] = vl.index = Integer::intern(index);
-
-			// Call the function and save the result.
-			vl.result = static_cast<Primitive*>(vl.fn)->apply(args, 2);
-		}
-	} catch (...) {
-		clear_error_source_data();
-		restore_current_frames();
-		MAXScript_signals = 0;
-		if (progress_bar_up)
-			MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE;
-	}
-
-	// Magic Max Script stuff to clear the frame and locals.
-	pop_value_locals();
-	pop_alloc_frame();
-}
diff --git a/NifPlugins_VC80.vcproj b/NifPlugins_VC80.vcproj
index d127259..e351957 100644
--- a/NifPlugins_VC80.vcproj
+++ b/NifPlugins_VC80.vcproj
@@ -5113,6 +5113,10 @@
 					RelativePath=".\NifCommon\NifQHull.cpp"
 					>
 				</File>
+				<File
+					RelativePath=".\NifCommon\nimorph.cpp"
+					>
+				</File>
 				<File
 					RelativePath=".\NifCommon\niutils.cpp"
 					>
-- 
GitLab