Skip to content
Snippets Groups Projects
Commit 6eb5601d authored by Tazpn's avatar Tazpn
Browse files

0.2.4

  -----
o Exporter
  - Add animation export.
  
o Importer
  - Add option to ignore UPB buffers.  
    * Useful in preventing LOD Bone warnings from the Civ4 Exporter
  - Fixed problem with Animation Note Tracks not being cleared
  - Fixed issue with BSpline animation when too much data was present
    * Specifically the Civ4 Leaderheads now import animation very well
  - Import Animation Priority into user prop buffer
    
o NifProps Utility
  - Added Animation Priority
  - Removed unused Globals
parent 0cc5fc7f
No related branches found
No related tags found
No related merge requests found
Showing
with 947 additions and 283 deletions
MaxPlugins 0.2.3
MaxPlugins 0.2.4
================
......@@ -27,6 +27,23 @@
Change log
----------
0.2.4
-----
o Exporter
- Add animation export.
o Importer
- Add option to ignore UPB buffers.
* Useful in preventing LOD Bone warnings from the Civ4 Exporter
- Fixed problem with Animation Note Tracks not being cleared
- Fixed issue with BSpline animation when too much data was present
* Specifically the Civ4 Leaderheads now import animation very well
- Import Animation Priority into user prop buffer
o NifProps Utility
- Added Animation Priority
- Removed unused Globals
0.2.3
-----
o Exporter
......
......@@ -131,6 +131,30 @@ FloatKey MapKey<FloatKey, ILinFloatKey>(ILinFloatKey& key, float time)
return InitFromLinKey(rKey, key, time);
}
template<>
Vector3Key MapKey<Vector3Key, ILinPoint3Key>(ILinPoint3Key& key, float time)
{
Vector3Key rKey;
rKey.data = TOVECTOR3(key.val);
return InitFromLinKey(rKey, key, time);
}
template<>
QuatKey MapKey<QuatKey, ILinRotKey>(ILinRotKey& key, float time)
{
QuatKey rKey;
rKey.data = TOQUAT(key.val, true);
return InitFromLinKey(rKey, key, time);
}
template<>
FloatKey MapKey<FloatKey, ILinScaleKey>(ILinScaleKey& key, float time)
{
FloatKey rKey;
rKey.data = (key.val[0] + key.val[1] + key.val[2]) / 3.0f;
return InitFromLinKey(rKey, key, time);
}
// Specialized Bezier/Hybrid mappings
......@@ -154,7 +178,6 @@ IBezPoint3Key MapKey<IBezPoint3Key, Vector3Key>(Vector3Key& key, float time)
return InitBezKey(rKey, key, time);
}
template<>
IBezQuatKey MapKey<IBezQuatKey, QuatKey>(QuatKey& key, float time)
{
......@@ -172,7 +195,7 @@ IBezScaleKey MapKey<IBezScaleKey, FloatKey>(FloatKey& key, float time)
return InitBezKey(rKey, key, time);
}
// Specialized Linear Mappings
// Specialized Bezier/Hybrid Mappings
template<>
FloatKey MapKey<FloatKey, IBezFloatKey>(IBezFloatKey& key, float time)
......@@ -184,6 +207,37 @@ FloatKey MapKey<FloatKey, IBezFloatKey>(IBezFloatKey& key, float time)
return InitFromBezKey(rKey, key, time);
}
template<>
Vector3Key MapKey<Vector3Key, IBezPoint3Key>(IBezPoint3Key& key, float time)
{
Vector3Key rKey;
rKey.data = TOVECTOR3(key.val);
rKey.forward_tangent = TOVECTOR3(key.intan);
rKey.backward_tangent = TOVECTOR3(key.outtan);
return InitFromBezKey(rKey, key, time);
}
template<>
QuatKey MapKey<QuatKey, IBezQuatKey>(IBezQuatKey& key, float time)
{
QuatKey rKey;
rKey.data = TOQUAT(key.val, true);
//rKey.forward_tangent = TOQUAT(key.intan, true);
//rKey.backward_tangent = TOQUAT(key.outtan, true);
return InitFromBezKey(rKey, key, time);
}
template<>
FloatKey MapKey<FloatKey, IBezScaleKey>(IBezScaleKey& key, float time)
{
FloatKey rKey;
rKey.data = Average(key.val.s);
rKey.forward_tangent = Average(key.intan);
rKey.backward_tangent = Average(key.outtan);
return InitFromBezKey(rKey, key, time);
}
// Specialized TCB Mappings
template<>
......@@ -229,6 +283,32 @@ FloatKey MapKey<FloatKey, ITCBFloatKey>(ITCBFloatKey& key, float time)
return InitFromTCBKey(rKey, key, time);
}
template<>
Vector3Key MapKey<Vector3Key, ITCBPoint3Key>(ITCBPoint3Key& key, float time)
{
Vector3Key rKey;
rKey.data = TOVECTOR3(key.val);
return InitFromTCBKey(rKey, key, time);
}
template<>
QuatKey MapKey<QuatKey, ITCBRotKey>(ITCBRotKey& key, float time)
{
QuatKey rKey;
rKey.data = TOQUAT(key.val, true);
return InitFromTCBKey(rKey, key, time);
}
template<>
FloatKey MapKey<FloatKey, ITCBScaleKey>(ITCBScaleKey& key, float time)
{
FloatKey rKey;
rKey.data = Average(key.val.s);
return InitFromTCBKey(rKey, key, time);
}
// Merge Keys
template<> void MergeKey<ILinRotKey>(ILinRotKey& lhs, ILinRotKey& rhs) {
lhs.val *= rhs.val;
}
......
......@@ -120,17 +120,38 @@ inline void SetKeys(Control *subCtrl, vector<U>& keys, float time)
}
template<typename T, typename U>
inline void GetKeys(Control *subCtrl, vector<T>& keys, float time)
inline int GetKeys(Control *subCtrl, vector<T>& keys, float time)
{
if (IKeyControl *ikeys = GetKeyControlInterface(subCtrl)){
int n = ikeys->GetKeySize();
int n = ikeys->GetNumKeys();
keys.reserve(n);
for (int i=0; i<n; ++i){
AnyKey buf; U *key = reinterpret_cast<U*>((IKey*)buf);
ikeys->GetKey(i, key);
keys.push_back( MapKey<T>(*key, time) );
}
return keys.size();
}
return 0;
}
template<typename T, typename U>
inline int GetKeys(Control *subCtrl, vector<T>& keys, Interval range)
{
if (IKeyControl *ikeys = GetKeyControlInterface(subCtrl)){
float timeOffset = -FrameToTime(range.Start());
int n = ikeys->GetNumKeys();
keys.reserve(n);
for (int i=0; i<n; ++i){
AnyKey buf; U *key = reinterpret_cast<U*>((IKey*)buf);
ikeys->GetKey(i, key);
if (range.InInterval(key->time)) {
keys.push_back( MapKey<T>(*key, timeOffset) );
}
}
return keys.size();
}
return 0;
}
template<typename T, typename U>
......
......@@ -39,6 +39,8 @@ using Niflib::Vector3;
#define NP_FRN_POS _T("np_frn_pos")
#define NP_ANM_PRI _T("np_anm_pri")
/* default values */
#define NP_DEFAULT_HVK_MATERIAL 9
#define NP_DEFAULT_HVK_LAYER 1
......@@ -50,6 +52,8 @@ using Niflib::Vector3;
#define NP_DEFAULT_HVK_MOTION_SYSTEM 7
#define NP_DEFAULT_HVK_QUALITY_TYPE 1
#define NP_DEFAULT_ANM_PRI 0.0f
#define NP_DEFAULT_FRN_MARKER_TYPE NP_FRN_SLEEP_LEFT
/* other constants */
......
......@@ -351,6 +351,14 @@ static inline Point3 GetScale(const Matrix3& mtx){
return Point3( fabs(mtx.GetRow(0)[0]), fabs(mtx.GetRow(1)[1]), fabs(mtx.GetRow(2)[2]) );
}
static inline float Average(const Point3& val) {
return (val[0] + val[1] + val[2]) / 3.0f;
}
static inline float Average(const Niflib::Vector3& val) {
return (val.x + val.y + val.z) / 3.0f;
}
template <typename U, typename T>
inline Niflib::Ref<U> SelectFirstObjectOfType( vector<Niflib::Ref<T> > const & objs ) {
for (vector<Niflib::Ref<T> >::const_iterator itr = objs.begin(), end = objs.end(); itr != end; ++itr) {
......
This diff is collapsed.
......@@ -5,6 +5,7 @@
#define REGPATH "Software\\NifTools\\MaxPlugins"
static LPCTSTR NifExportSection = TEXT("MaxNifExport");
static LPCTSTR KfExportSection = TEXT("KfExport");
void regSet(HKEY hKey, const char *value, float f);
void regSet(HKEY hKey, const char *value, bool b);
......@@ -127,6 +128,33 @@ void Exporter::readConfig(Interface *i)
}
void Exporter::readKfConfig(Interface *i)
{
TCHAR iniName[MAX_PATH];
LPCTSTR pluginDir = i->GetDir(APP_PLUGCFG_DIR);
PathCombine(iniName, pluginDir, "MaxNifTools.ini");
mExportHidden = GetIniValue(KfExportSection, "IncludeHidden", false, iniName);
mExportLights = GetIniValue(KfExportSection, "Lights", false, iniName);
mExportCameras = GetIniValue(KfExportSection, "Cameras", false, iniName);
mExportTransforms = GetIniValue(KfExportSection, "Transforms", true, iniName);
mDefaultPriority = GetIniValue<float>(KfExportSection, "Priority", 0.0f, iniName);
}
void Exporter::writeKfConfig(Interface *i)
{
TCHAR iniName[MAX_PATH];
LPCTSTR pluginDir = i->GetDir(APP_PLUGCFG_DIR);
PathCombine(iniName, pluginDir, "MaxNifTools.ini");
SetIniValue(KfExportSection, "IncludeHidden", mExportHidden, iniName);
SetIniValue(KfExportSection, "Lights", mExportLights, iniName);
SetIniValue(KfExportSection, "Cameras", mExportCameras, iniName);
SetIniValue(KfExportSection, "Transforms", mExportTransforms, iniName);
SetIniValue<float>(KfExportSection, "Priority", mDefaultPriority, iniName);
}
void regSet(HKEY hKey, const char *value, float f)
{
DWORD dw = *((DWORD*)&f);
......
......@@ -60,11 +60,7 @@ __declspec( dllexport ) const TCHAR* LibDescription()
//TODO: Must change this number when adding a new class
__declspec( dllexport ) int LibNumberClasses()
{
#if _DEBUG
return 2;
#else
return 1;
#endif
return 2;
}
// This function returns the number of plug-in classes this DLL
......
......@@ -29,6 +29,8 @@ int Exporter::mNifUserVersion = 0;
bool Exporter::mSkeletonOnly=false;
bool Exporter::mExportCameras=false;
bool Exporter::mGenerateBoneCollision=false;
bool Exporter::mExportTransforms=true;
float Exporter::mDefaultPriority=0.0f;
Exporter::Exporter(Interface *i, AppSettings *appSettings)
: mI(i), mAppSettings(appSettings)
......
......@@ -57,6 +57,9 @@ public:
static bool mExportCameras;
static bool mGenerateBoneCollision;
static bool mExportTransforms;
static float mDefaultPriority;
Exporter(Interface *i, AppSettings *appSettings);
Result doExport(NiNodeRef &root, INode *node);
......@@ -70,11 +73,15 @@ public:
static void writeConfig(INode *node);
// writes config to registry
static void writeConfig(Interface *i);
// writes config to registry
static void writeKfConfig(Interface *i);
// reads config from root node
static void readConfig(INode *node);
// reads config from registry
static void readConfig(Interface *i);
// reads config from registry
static void readKfConfig(Interface *i);
public:
typedef vector<unsigned short> TriStrip;
......@@ -176,7 +183,7 @@ public:
bool exportSkin();
/* animation export */
bool doAnimExport();
Result doAnimExport(Ref<NiControllerSequence> root);
/* misc export */
bool exportUPB(NiNodeRef &root, INode *node);
......
......@@ -2,12 +2,12 @@
#include "AppSettings.h"
#include "niutils.h"
#include <io.h>
#include "obj/NiControllerSequence.h"
using namespace Niflib;
#define KFEXPORT_CLASS_ID Class_ID(0xa57ff0a4, 0xa0374ffc)
static LPCTSTR KfExportSection = TEXT("MaxKfExport");
static LPCTSTR KfExportSection = TEXT("KfExport");
class KfExport : public SceneExport
{
......@@ -43,12 +43,12 @@ class KfExportClassDesc : public ClassDesc2
public:
int IsPublic() { return TRUE; }
void *Create(BOOL loading = FALSE) { return new KfExport(); }
const TCHAR *ClassName() { return GetString(IDS_CLASS_NAME); }
const TCHAR *ClassName() { return GetString(IDS_KF_CLASS_NAME); }
SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
Class_ID ClassID() { return KFEXPORT_CLASS_ID; }
const TCHAR *Category() { return GetString(IDS_CATEGORY); }
const TCHAR *InternalName() { return _T("KfExport"); } // returns fixed parsable name (scripter-visible name)
const TCHAR *InternalName() { return _T("_KfExport"); } // returns fixed parsable name (scripter-visible name)
HINSTANCE HInstance() { return hInstance; } // returns owning module handle
......@@ -58,6 +58,128 @@ static KfExportClassDesc KfExportDesc;
ClassDesc2* GetKfExportDesc() { return &KfExportDesc; }
static BOOL CALLBACK KfExportOptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) {
static KfExport *imp = NULL;
switch (message)
{
case WM_INITDIALOG:
{
// Append file version to dialog
TSTR fileVersion = GetFileVersion(NULL);
if (!fileVersion.isNull()) {
char buffer[256];
GetWindowText(hWnd, buffer, _countof(buffer));
_tcscat(buffer, TEXT(" "));
_tcscat(buffer, fileVersion);
SetWindowText(hWnd, buffer);
}
imp = (KfExport *)lParam;
CenterWindow(hWnd,GetParent(hWnd));
CheckDlgButton(hWnd, IDC_CHK_HIDDEN, Exporter::mExportHidden);
CheckDlgButton(hWnd, IDC_CHK_LIGHTS, Exporter::mExportLights);
CheckDlgButton(hWnd, IDC_CHK_CAMERA, Exporter::mExportCameras);
CheckDlgButton(hWnd, IDC_CHK_TRANSFORMS, Exporter::mExportTransforms);
SetDlgItemText(hWnd, IDC_ED_PRIORITY, FormatText("%.4f", Exporter::mDefaultPriority));
string selection = Exporter::mGameName;
string version = Exporter::mNifVersion;
string userVer = FormatString("%d", Exporter::mNifUserVersion);
for (AppSettingsMap::iterator itr = TheAppSettings.begin(), end = TheAppSettings.end(); itr != end; ++itr)
SendDlgItemMessage(hWnd, IDC_CB_GAME, CB_ADDSTRING, 0, LPARAM(itr->Name.c_str()));
SendDlgItemMessage(hWnd, IDC_CB_GAME, CB_SELECTSTRING, WPARAM(-1), LPARAM(selection.c_str()));
SendDlgItemMessage(hWnd, IDC_CB_VERSION, WM_SETTEXT, 0, LPARAM(version.c_str()));
SendDlgItemMessage(hWnd, IDC_CB_USER_VERSION, WM_SETTEXT, 0, LPARAM(userVer.c_str()));
imp->mDlgResult = IDCANCEL;
}
return TRUE;
case WM_CLOSE:
EndDialog(hWnd, imp->mDlgResult);
return TRUE;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED)
{
char tmp[MAX_PATH], *end;
bool close = false;
switch (LOWORD(wParam))
{
case IDOK:
// Validity Check
GetDlgItemText(hWnd, IDC_CB_VERSION, tmp, MAX_PATH);
if (tmp[0] != 0)
{
int nifVersion = GetVersion(tmp);
if (!IsVersionSupported(nifVersion))
{
MessageBox(hWnd, FormatString("Version '%s' is not a supported version.", tmp).c_str(), "NifExport", MB_OK|MB_ICONSTOP);
return FALSE;
}
}
Exporter::mExportHidden = IsDlgButtonChecked(hWnd, IDC_CHK_HIDDEN);
Exporter::mExportLights = IsDlgButtonChecked(hWnd, IDC_CHK_LIGHTS);
Exporter::mExportCameras = IsDlgButtonChecked(hWnd, IDC_CHK_CAMERA);
Exporter::mExportTransforms = IsDlgButtonChecked(hWnd, IDC_CHK_TRANSFORMS);
GetDlgItemText(hWnd, IDC_ED_PRIORITY, tmp, MAX_PATH);
Exporter::mDefaultPriority = atof(tmp);
GetDlgItemText(hWnd, IDC_CB_GAME, tmp, MAX_PATH);
if (AppSettings *appSettings = FindAppSetting(tmp))
{
Exporter::mGameName = appSettings->Name;
GetDlgItemText(hWnd, IDC_CB_VERSION, tmp, MAX_PATH);
Exporter::mNifVersion = tmp;
GetDlgItemText(hWnd, IDC_CB_USER_VERSION, tmp, MAX_PATH);
Exporter::mNifUserVersion = strtol(tmp, &end, 0);
}
EndDialog(hWnd, imp->mDlgResult=IDOK);
close = true;
break;
case IDCANCEL:
EndDialog(hWnd, imp->mDlgResult=IDCANCEL);
close = true;
break;
}
if (close)
SendMessage(hWnd, WM_CLOSE, 0, 0);
}
else if (HIWORD(wParam) == STN_CLICKED)
{
if (LOWORD(wParam) == IDC_LBL_LINK)
{
ShellExecute(hWnd, "open", "http://www.niftools.org",
NULL, NULL, SW_SHOWDEFAULT);
}
}
else if (HIWORD(wParam) == CBN_SELCHANGE)
{
if (LOWORD(wParam) == IDC_CB_GAME)
{
char tmp[MAX_PATH];
GetDlgItemText(hWnd, IDC_CB_GAME, tmp, MAX_PATH);
if (AppSettings *appSettings = FindAppSetting(tmp))
{
string version = appSettings->NiVersion;
string userVer = FormatString("%d", appSettings->NiUserVersion);
SendDlgItemMessage(hWnd, IDC_CB_VERSION, WM_SETTEXT, 0, LPARAM(version.c_str()));
SendDlgItemMessage(hWnd, IDC_CB_USER_VERSION, WM_SETTEXT, 0, LPARAM(userVer.c_str()));
}
}
}
break;
}
return FALSE;
}
//--- KfExport -------------------------------------------------------
KfExport::KfExport()
{
......@@ -141,8 +263,103 @@ BOOL KfExport::SupportsOptions(int ext, DWORD options)
int KfExport::DoExport(const TCHAR *name, ExpInterface *ei, Interface *i, BOOL suppressPrompts, DWORD options)
{
// read application settings
AppSettings::Initialize(i);
try
{
// read application settings
AppSettings::Initialize(i);
TCHAR iniName[MAX_PATH];
LPCTSTR pluginDir = i->GetDir(APP_PLUGCFG_DIR);
PathCombine(iniName, pluginDir, "MaxNifTools.ini");
bool iniNameIsValid = (-1 != _taccess(iniName, 0));
// read config from registry
Exporter::readKfConfig(i);
AppSettings *appSettings = NULL;
if (iniNameIsValid)
{
string fname = name;
// Locate which application to use. If Auto, find first app where this file appears in the root path list
string curapp = GetIniValue<string>(KfExportSection, "CurrentApp", "AUTO", iniName);
if (0 == _tcsicmp(curapp.c_str(), "AUTO")) {
// Scan Root paths
for (AppSettingsMap::iterator itr = TheAppSettings.begin(), end = TheAppSettings.end(); itr != end; ++itr){
if ((*itr).IsFileInRootPaths(fname)) {
appSettings = &(*itr);
break;
}
}
} else {
appSettings = FindAppSetting(curapp);
}
}
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
Exporter::mGameName = appSettings->Name;
Exporter::mNifVersion = appSettings->NiVersion;
Exporter::mNifUserVersion = appSettings->NiUserVersion;
if(!suppressPrompts)
{
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_KF_PANEL), GetActiveWindow(), KfExportOptionsDlgProc, (LPARAM)this) != IDOK)
return true;
// write config to registry
Exporter::writeKfConfig(i);
// write config to root node
Exporter::writeConfig(i->GetRootNode());
// Update the current app version
appSettings = FindAppSetting(Exporter::mGameName);
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
appSettings->NiVersion = Exporter::mNifVersion;
appSettings->NiUserVersion = Exporter::mNifUserVersion;
appSettings->WriteSettings(i);
}
int nifVersion = VER_20_0_0_5;
int nifUserVer = Exporter::mNifUserVersion;
if (!Exporter::mNifVersion.empty())
{
nifVersion = GetVersion(Exporter::mNifVersion);
if (!IsVersionSupported(nifVersion))
throw exception(FormatString("Version '%s' is not a supported version.").c_str());
}
Exporter::mSelectedOnly = (options&SCENE_EXPORT_SELECTED) != 0;
Exporter exp(i, appSettings);
Ref<NiControllerSequence> root = new NiControllerSequence();
TCHAR fname[MAX_PATH];
_tcscpy(fname, PathFindFileName(name));
PathRemoveExtension(fname);
root->SetName(fname);
Exporter::Result result = exp.doAnimExport(root);
if (result!=Exporter::Ok)
throw exception("Unknown error.");
WriteNifTree(name, StaticCast<NiObject>(root), nifVersion, nifUserVer);
}
catch (exception &e)
{
MessageBox(NULL, e.what(), "Export Error", MB_OK);
return true;
}
catch (...)
{
MessageBox(NULL, "Unknown error.", "Export Error", MB_OK);
return true;
}
return TRUE;
}
\ No newline at end of file
......@@ -136,16 +136,16 @@ void Exporter::makeMaterial(NiAVObjectRef &parent, Mtl *mtl)
if (smtl->SupportsShaders()) {
if (Shader *s = smtl->GetShader()) {
if (smtl->GetWire()){
NiWireframePropertyRef wireProp = CreateNiObject<NiWireframeProperty>();
NiWireframePropertyRef wireProp = new NiWireframeProperty();
wireProp->SetFlags(1);
parent->AddProperty(wireProp);
}
if (smtl->GetTwoSided()){
NiStencilPropertyRef stencil = CreateNiObject<NiStencilProperty>();
stencil->SetStencilFunction(4);
NiStencilPropertyRef stencil = new NiStencilProperty();
stencil->SetStencilFunction(TEST_GREATER);
stencil->SetStencilEnabled(false);
stencil->SetPassAction(3);
stencil->SetDrawMode(3);
stencil->SetPassAction(ACTION_INCREMENT);
stencil->SetDrawMode(DRAW_BOTH);
parent->AddProperty(stencil);
}
if (smtl->IsFaceted()) {
......@@ -343,10 +343,10 @@ bool Exporter::exportCiv4Shader(NiAVObjectRef parent, Mtl* mtl)
}
if (smtl->GetTwoSided()){
NiStencilPropertyRef stencil = CreateNiObject<NiStencilProperty>();
stencil->SetStencilFunction(4);
stencil->SetStencilFunction(TEST_GREATER);
stencil->SetStencilEnabled(false);
stencil->SetPassAction(3);
stencil->SetDrawMode(3);
stencil->SetPassAction(ACTION_INCREMENT);
stencil->SetDrawMode(DRAW_BOTH);
parent->AddProperty(stencil);
}
if (smtl->IsFaceted()) {
......
......@@ -7,7 +7,7 @@ using namespace Niflib;
#define NifExport_CLASS_ID Class_ID(0xa57ff0a4, 0xa0374ffb)
static LPCTSTR NifExportSection = TEXT("MaxNifExport");
LPCTSTR NifExportSection = TEXT("MaxNifExport");
class NifExport : public SceneExport
{
......@@ -41,7 +41,7 @@ class NifExportClassDesc : public ClassDesc2
public:
int IsPublic() { return TRUE; }
void *Create(BOOL loading = FALSE) { return new NifExport(); }
const TCHAR *ClassName() { return GetString(IDS_CLASS_NAME); }
const TCHAR *ClassName() { return GetString(IDS_NIF_CLASS_NAME); }
SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
Class_ID ClassID() { return NifExport_CLASS_ID; }
const TCHAR *Category() { return GetString(IDS_CATEGORY); }
......@@ -283,68 +283,69 @@ BOOL NifExport::SupportsOptions(int ext, DWORD options)
int NifExport::DoExport(const TCHAR *name, ExpInterface *ei, Interface *i, BOOL suppressPrompts, DWORD options)
{
// read application settings
AppSettings::Initialize(i);
TCHAR iniName[MAX_PATH];
LPCTSTR pluginDir = i->GetDir(APP_PLUGCFG_DIR);
PathCombine(iniName, pluginDir, "MaxNifTools.ini");
bool iniNameIsValid = (-1 != _taccess(iniName, 0));
// Set whether Config should use registry or not
Exporter::mUseRegistry = !iniNameIsValid || GetIniValue<bool>(NifExportSection, "UseRegistry", false, iniName);
// read config from registry
Exporter::readConfig(i);
// read config from root node
Exporter::readConfig(i->GetRootNode());
// locate the "default" app setting
AppSettings *appSettings = NULL;
if (iniNameIsValid)
{
string fname = name;
// Locate which application to use. If Auto, find first app where this file appears in the root path list
string curapp = GetIniValue<string>(NifExportSection, "CurrentApp", "AUTO", iniName);
if (0 == _tcsicmp(curapp.c_str(), "AUTO")) {
// Scan Root paths
for (AppSettingsMap::iterator itr = TheAppSettings.begin(), end = TheAppSettings.end(); itr != end; ++itr){
if ((*itr).IsFileInRootPaths(fname)) {
appSettings = &(*itr);
break;
try
{
// read application settings
AppSettings::Initialize(i);
TCHAR iniName[MAX_PATH];
LPCTSTR pluginDir = i->GetDir(APP_PLUGCFG_DIR);
PathCombine(iniName, pluginDir, "MaxNifTools.ini");
bool iniNameIsValid = (-1 != _taccess(iniName, 0));
// Set whether Config should use registry or not
Exporter::mUseRegistry = !iniNameIsValid || GetIniValue<bool>(NifExportSection, "UseRegistry", false, iniName);
// read config from registry
Exporter::readConfig(i);
// read config from root node
Exporter::readConfig(i->GetRootNode());
// locate the "default" app setting
AppSettings *appSettings = NULL;
if (iniNameIsValid)
{
string fname = name;
// Locate which application to use. If Auto, find first app where this file appears in the root path list
string curapp = GetIniValue<string>(NifExportSection, "CurrentApp", "AUTO", iniName);
if (0 == _tcsicmp(curapp.c_str(), "AUTO")) {
// Scan Root paths
for (AppSettingsMap::iterator itr = TheAppSettings.begin(), end = TheAppSettings.end(); itr != end; ++itr){
if ((*itr).IsFileInRootPaths(fname)) {
appSettings = &(*itr);
break;
}
}
} else {
appSettings = FindAppSetting(curapp);
}
} else {
appSettings = FindAppSetting(curapp);
}
}
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
if(!suppressPrompts)
{
Exporter::mGameName = appSettings->Name;
Exporter::mNifVersion = appSettings->NiVersion;
Exporter::mNifUserVersion = appSettings->NiUserVersion;
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_PANEL), GetActiveWindow(), NifExportOptionsDlgProc, (LPARAM)this) != IDOK)
return true;
// write config to registry
Exporter::writeConfig(i);
// write config to root node
Exporter::writeConfig(i->GetRootNode());
// Update the current app version
appSettings = FindAppSetting(Exporter::mGameName);
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
appSettings->NiVersion = Exporter::mNifVersion;
appSettings->NiUserVersion = Exporter::mNifUserVersion;
appSettings->WriteSettings(i);
}
if(!suppressPrompts)
{
if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_NIF_PANEL), GetActiveWindow(), NifExportOptionsDlgProc, (LPARAM)this) != IDOK)
return true;
// write config to registry
Exporter::writeConfig(i);
// write config to root node
Exporter::writeConfig(i->GetRootNode());
// Update the current app version
appSettings = FindAppSetting(Exporter::mGameName);
if (appSettings == NULL && !TheAppSettings.empty())
appSettings = &TheAppSettings.front();
appSettings->NiVersion = Exporter::mNifVersion;
appSettings->NiUserVersion = Exporter::mNifUserVersion;
appSettings->WriteSettings(i);
}
try
{
int nifVersion = VER_20_0_0_5;
int nifUserVer = Exporter::mNifUserVersion;
......
......@@ -73,7 +73,32 @@ END
// Dialog
//
IDD_PANEL DIALOGEX 0, 0, 205, 203
IDD_KF_PANEL DIALOGEX 0, 0, 205, 130
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "Export KF"
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "Export:",IDC_STATIC,7,7,81,66
CONTROL "&Hidden Nodes",IDC_CHK_HIDDEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,17,67,10
CONTROL "Transforms",IDC_CHK_TRANSFORMS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,31,67,10
CONTROL "&Lights",IDC_CHK_LIGHTS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,44,67,10
CONTROL "Cameras",IDC_CHK_CAMERA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,58,67,10
GROUPBOX "Behaviors:",IDC_STATIC,94,7,104,66
LTEXT "Priority:",IDC_LBL_PRIORITY,104,24,37,8
EDITTEXT IDC_ED_PRIORITY,145,22,39,12,ES_AUTOHSCROLL
LTEXT "Game",IDC_STATIC,7,78,66,8
COMBOBOX IDC_CB_GAME,7,89,105,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Version",IDC_STATIC,117,78,39,8
EDITTEXT IDC_CB_VERSION,117,89,45,12,ES_AUTOHSCROLL
LTEXT "User",IDC_STATIC,167,78,25,8
EDITTEXT IDC_CB_USER_VERSION,167,89,30,12,ES_AUTOHSCROLL
DEFPUSHBUTTON "&Export",IDOK,5,109,34,14
PUSHBUTTON "&Cancel",IDCANCEL,45,109,33,14
LTEXT "http://www.niftools.org",IDC_LBL_LINK,103,109,95,14,SS_NOTIFY | SS_CENTERIMAGE
END
IDD_NIF_PANEL DIALOGEX 0, 0, 205, 203
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "Export Nif"
......@@ -122,7 +147,15 @@ END
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_PANEL, DIALOG
IDD_KF_PANEL, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 198
TOPMARGIN, 7
BOTTOMMARGIN, 123
END
IDD_NIF_PANEL, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 198
......@@ -139,8 +172,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,2,3,0
PRODUCTVERSION 0,2,3,0
FILEVERSION 0,2,4,0
PRODUCTVERSION 0,2,4,0
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
......@@ -156,12 +189,12 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "3ds Max Nif Exporter"
VALUE "FileVersion", "0, 2, 3, 0"
VALUE "FileVersion", "0, 2, 4, 0"
VALUE "InternalName", "NifExport.dle"
VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved."
VALUE "OriginalFilename", "NifExport.dle"
VALUE "ProductName", "3ds Max Nif Exporter"
VALUE "ProductVersion", "0, 2, 3, 0"
VALUE "ProductVersion", "0, 2, 4, 0"
END
END
BLOCK "VarFileInfo"
......@@ -178,9 +211,10 @@ END
STRINGTABLE
BEGIN
IDS_CLASS_NAME "Nif Exporter"
IDS_NIF_CLASS_NAME "Niftools NIF Exporter"
IDS_PARAMS "Parameters"
IDS_SPIN "Spin"
IDS_KF_CLASS_NAME "Niftools KF Exporter"
END
#endif // English (U.S.) resources
......
......@@ -5,9 +5,13 @@
#define IDS_LIBDESCRIPTION 1
#define IDS_CATEGORY 2
#define IDS_CLASS_NAME 3
#define IDS_NIF_CLASS_NAME 3
#define IDS_PARAMS 4
#define IDS_SPIN 5
#define IDS_KF_CLASS_NAME 6
#define IDD_PANEL 101
#define IDD_NIF_PANEL 101
#define IDD_KF_PANEL 102
#define IDC_CLOSEBUTTON 1000
#define IDC_DOSTUFF 1000
#define IDC_CHK_HIDDEN 1003
......@@ -18,8 +22,10 @@
#define IDC_ED_WELDTHRESH 1008
#define IDC_LBL_WELDTHRESH 1009
#define IDC_CHK_COLL 1010
#define IDC_ED_PRIORITY 1010
#define IDC_LBL_LINK 1011
#define IDC_CHK_VCOLORS 1012
#define IDC_LBL_PRIORITY 1012
#define IDC_CHK_REMAP 1013
#define IDC_CB_GAME 1014
#define IDC_CB_VERSION 1015
......@@ -36,6 +42,8 @@
#define IDC_CHK_CAMERA 1025
#define IDC_CHK_SKEL_ONLY2 1026
#define IDC_CHK_BONE_COLL 1026
#define IDC_CHK_TRANSFORMS 1026
#define IDC_EDIT1 1027
#define IDC_COLOR 1456
#define IDC_EDIT 1490
#define IDC_SPIN 1496
......@@ -46,7 +54,7 @@
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1025
#define _APS_NEXT_CONTROL_VALUE 1028
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
......@@ -107,8 +107,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,2,3,0
PRODUCTVERSION 0,2,3,0
FILEVERSION 0,2,4,0
PRODUCTVERSION 0,2,4,0
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
......@@ -124,12 +124,12 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "3ds Max Nif Furniture Plugin"
VALUE "FileVersion", "0, 2, 3, 0"
VALUE "FileVersion", "0, 2, 4, 0"
VALUE "InternalName", "NifFurniture.dlu"
VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved."
VALUE "OriginalFilename", "NifFurniture.dlu"
VALUE "ProductName", "3ds Max Nif Furniture Plugin"
VALUE "ProductVersion", "0, 2, 3, 0"
VALUE "ProductVersion", "0, 2, 4, 0"
END
END
BLOCK "VarFileInfo"
......
......@@ -18,6 +18,7 @@ HISTORY:
#include "KFMImporter.h"
#include "KFImporter.h"
#include "AnimKey.h"
#include "NifPlugins.h"
#include <obj/NiInterpolator.h>
#include <obj/NiTransformInterpolator.h>
#include <obj/NiTransformData.h>
......@@ -136,11 +137,20 @@ void NifImporter::ClearAnimation(INode *node)
{
if (node != NULL)
{
if (node->HasNoteTracks()){
for (int i = node->NumNoteTracks()-1; i>=0; --i ){
if (NoteTrack *nt = node->GetNoteTrack(i))
node->DeleteNoteTrack(nt, TRUE);
}
}
node->DeleteKeys(TRACK_DOALL);
::ClearAnimation(node->GetTMController());
for (int i=0, n=node->NumberOfChildren(); i<n; ++i){
ClearAnimation(node->GetChildNode(i));
}
// Clear animation priority
node->SetUserPropFloat(NP_ANM_PRI, 0.0f);
}
}
void NifImporter::ClearAnimation()
......@@ -304,7 +314,48 @@ bool KFMImporter::ImportAnimation()
if (addNoteTracks) {
string target = cntr->GetTargetName();
if ( INode *n = gi->GetINodeByName(target.c_str()) ) {
#if 1
DefNoteTrack* nt = (DefNoteTrack*)NewDefaultNoteTrack();
n->AddNoteTrack(nt);
for (vector<StringKey>::iterator itr=textKeys.begin(); itr != textKeys.end(); ++itr) {
TimeValue t = TimeToFrame(time + (*itr).time);
if (wildmatch("start*", (*itr).data)){
stringlist args = TokenizeCommandLine((*itr).data.c_str(), true);
if (args.empty()) continue;
bool hasName = false;
bool hasLoop = false;
CycleType ct = cntr->GetCycleType();
for (stringlist::iterator itr = args.begin(); itr != args.end(); ++itr) {
if (strmatch("-name", *itr)) {
if (++itr == args.end()) break;
hasName = true;
} else if (strmatch("-loop", *itr)) {
hasLoop = true;
}
}
if (!hasName) {
string name = cntr->GetName();
if (name.empty())
name = FormatString("EMPTY_SEQUENCE_AT_%df", int(t * FramesPerSecond / TicksPerFrame) );
args.push_back("-name");
args.push_back(name);
}
if (!hasLoop && ct == CYCLE_LOOP) {
args.push_back("-loop");
}
string line = JoinCommandLine(args);
NoteKey *key = new NoteKey(t, line.c_str(), 0);
nt->keys.Append(1, &key);
} else {
NoteKey *key = new NoteKey(t, (*itr).data.c_str(), 0);
nt->keys.Append(1, &key);
}
}
#else
TSTR script;
script +=
"fn getActorManager obj = (\n"
......@@ -326,7 +377,7 @@ bool KFMImporter::ImportAnimation()
script += FormatText("nt = getActorManager $'%s'\n", target.c_str());
for (vector<StringKey>::iterator itr=textKeys.begin(); itr != textKeys.end(); ++itr) {
TimeValue t = TimeToFrame(time + (*itr).time) + 1;
TimeValue t = TimeToFrame(time + (*itr).time);
if (wildmatch("start*", (*itr).data)){
stringlist args = TokenizeCommandLine((*itr).data.c_str(), true);
......@@ -363,6 +414,7 @@ bool KFMImporter::ImportAnimation()
//nt->keys.Append(1, &key);
}
ExecuteMAXScriptScript(script, TRUE, NULL);
#endif
}
}
......@@ -396,6 +448,10 @@ bool KFMImporter::ImportAnimation()
INode *n = gi->GetINodeByName(name.c_str());
if ((*lnk).priority_ != 0.0f) {
npSetProp(n, NP_ANM_PRI, (*lnk).priority_);
}
NiKeyframeDataRef data;
Point3 p; Quat q; float s;
if (ai.GetTransformData(*lnk, name, data, p, q, s)) {
......
......@@ -686,6 +686,9 @@ void NifImporter::ImportBones(NiNodeRef node, bool recurse)
bool NifImporter::ImportUPB(INode *node, Niflib::NiNodeRef block)
{
if (!importUPB)
return false;
bool ok = false;
if (node && block)
{
......
......@@ -203,20 +203,16 @@ int MaxNifImport::DoImport(const TCHAR *filename,ImpInterface *i, Interface *gi,
return FALSE;
ok = importer.DoImport();
}
}
catch (exception &e)
{
MessageBox(NULL, e.what(), "Import Error", MB_OK);
return TRUE;
}
catch( exception & e )
catch (...)
{
e=e;
ok = false;
}
catch( exception * e )
{
e=e;
ok = false;
}
catch( ... )
{
ok = false;
MessageBox(NULL, "Unknown error.", "Import Error", MB_OK);
return TRUE;
}
return ok ? TRUE : FALSE;
}
......
......@@ -36,27 +36,28 @@ BEGIN
CONTROL "&Skeleton",IDC_CHK_BONES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,16,67,10
CONTROL "S&kin Modifier",IDC_CHK_SKIN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,27,67,10
CONTROL "Vertex &Colors",IDC_CHK_VCOLORS,"Button",BS_AUTO3STATE | WS_TABSTOP,14,38,67,10
CONTROL "Co&llision",IDC_CHK_COLL,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,14,86,67,10
CONTROL "&Animation",IDC_CHK_ANIMATION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,50,67,10
CONTROL "&Lights",IDC_CHK_LIGHTS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,62,67,10
CONTROL "Cameras",IDC_CHK_CAMERA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,74,67,10
CONTROL "Co&llision",IDC_CHK_COLL,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,14,86,67,10
CONTROL "Furniture &Markers",IDC_CHK_FURN,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,14,97,67,10
GROUPBOX "Behaviors:",IDC_STATIC,94,6,101,92
CONTROL "Flip U&V",IDC_CHK_FLIP_UV,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,16,88,10
CONTROL "&Render Textures in View",IDC_CHK_SHOW_TEX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,27,88,10
CONTROL "Auto Sm&ooth Mesh",IDC_CHK_AUTOSMOOTH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,38,88,11
CONTROL "Remove &Illegal Faces",IDC_CHK_ILLEGAL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,50,88,11
CONTROL "Remove &Unused Bones",IDC_CHK_REM_BONES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,62,88,10
CONTROL "Use &Biped",IDC_CHK_BIPED,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,102,84,87,10
CONTROL "Clear Animation",IDC_CHK_CLEARANIM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,73,88,10
CONTROL "Ignore User Prop Buffers",IDC_CHK_UPB,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,86,89,10
GROUPBOX "Behaviors:",IDC_STATIC,94,6,101,105
CONTROL "Use &Biped",IDC_CHK_BIPED,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,102,98,87,10
LTEXT "Game:",IDC_STATIC,7,129,31,8
COMBOBOX IDC_CB_GAME,47,127,131,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Skeleton:",IDC_STC_SKELETON,7,143,31,8
EDITTEXT IDC_ED_SKELETON,7,155,171,12,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_BTN_BROWSE,192,155,19,13
LTEXT "Game:",IDC_STATIC,7,129,31,8
COMBOBOX IDC_CB_GAME,47,127,131,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
DEFPUSHBUTTON "&Import",IDOK,5,172,34,14
PUSHBUTTON "&Cancel",IDCANCEL,45,172,33,14
LTEXT "http://www.niftools.org",IDC_LBL_LINK,93,172,95,14,SS_NOTIFY | SS_CENTERIMAGE
CONTROL "Clear Animation",IDC_CHK_CLEARANIM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,102,73,88,10
CONTROL "&Lights",IDC_CHK_LIGHTS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,62,67,10
CONTROL "Cameras",IDC_CHK_CAMERA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,74,67,10
END
IDD_KF_PANEL DIALOGEX 0, 0, 118, 99
......@@ -66,12 +67,12 @@ CAPTION "Import KF"
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "Import:",-1,7,6,104,51
DEFPUSHBUTTON "&Import",IDOK,5,78,34,14
PUSHBUTTON "&Cancel",IDCANCEL,45,78,33,14
LTEXT "http://www.niftools.org",IDC_LBL_LINK,7,61,95,14,SS_NOTIFY | SS_CENTERIMAGE
CONTROL "Clear Animation",IDC_CHK_CLEARANIM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,18,72,10
CONTROL "Add Key Notes",IDC_CHK_KEYNOTES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,31,72,10
CONTROL "Add Time Tags",IDC_CHK_TIMETAGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,44,72,10
LTEXT "http://www.niftools.org",IDC_LBL_LINK,7,61,95,14,SS_NOTIFY | SS_CENTERIMAGE
DEFPUSHBUTTON "&Import",IDOK,5,78,34,14
PUSHBUTTON "&Cancel",IDCANCEL,45,78,33,14
END
......@@ -134,8 +135,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,2,3,0
PRODUCTVERSION 0,2,3,0
FILEVERSION 0,2,4,0
PRODUCTVERSION 0,2,4,0
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
......@@ -151,12 +152,12 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "3ds Max Nif Importer"
VALUE "FileVersion", "0, 2, 3, 0"
VALUE "FileVersion", "0, 2, 4, 0"
VALUE "InternalName", "MaxNifImport.dli"
VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved."
VALUE "OriginalFilename", "MaxNifImport.dli"
VALUE "ProductName", "3ds Max Nif Importer"
VALUE "ProductVersion", "0, 2, 3, 0"
VALUE "ProductVersion", "0, 2, 4, 0"
END
END
BLOCK "VarFileInfo"
......@@ -175,7 +176,7 @@ STRINGTABLE
BEGIN
IDS_LIBDESCRIPTION "Importer for NIF files"
IDS_CATEGORY "NifTools"
IDS_CLASS_NAME "MaxNifImport"
IDS_CLASS_NAME "Niftools_Nif_Importer"
IDS_PARAMS "Parameters"
IDS_SPIN "Spin"
END
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment