3DCoat  3D-COAT 4.9.xx
3DCoat is the one application that has all the tools you need to take your 3D idea from a block of digital clay all the way to a production ready, fully textured organic or hard surface model.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Регистрация своего класса

Правила регистрации членов класса

Для регистрации членов класса с целью последующего показа класса в UI или сериализации необходимо, чтобы класс был наследником BaseClass. Регистрацию делай так

implement_class_dyn_auto( MyClass ) {
// здесь регистрируем элементы
// Можно использовать динамические условия, т. е. if ... else ... при регистрации.
// Это динамическая регистрация.
}

Или в секции SAVE ... ENDSAVE так (архаичный способ, для нового класса так не надо)

class MyClass : public BaseClass {
public:
SAVE( MyClass ) {
// здесь регистрируем элементы
} ENDSAVE
};

Или так (тоже по древнему, но лучше, чем тулить всё в хидер)

class MyClass : public BaseClass {
public:
declare_class( MyClass );
}
// где то в cpp файле...
implement_class( MyClass ) {
// здесь регистрируем элементы
}implement_end;

Директивы в секции регистрации членов класса

Используем, если тип переменной variable_name не является наследником BaseClass.

REG_MEMBER( class_type, variable_name );

Соответствие типов данных c++ и class_type глянь в этой табличке.

c++ type class_type
float _float
int _int
bool _bool
DWORD _DWORD
DWORD _color (когда нужно интерпретировать DWORD как цвет)
short _short
BYTE _byte
char _char

Используй в случае, если тип переменной является наследником BaseClass.

REG_AUTO( variable_name );

Если хотишь, чтобы переменные типа int и float появлялись в UI как слайдеры (ползунки).

// floating slider
REG_FSLIDER( float_variable_name, minimal_value, maximal_value, default_value );
// integer slider
REG_ISLIDER( int_variable_name, minimal_value, maximal_value, default_value );

Можешь регистрировать функции и члены класса. В UI они появятся как кнопки, при нажатии на кнопку стартует заданная тобой функция.

REG_CMETHOD( MyClass, functionName );

В команде выше функцию объявляешь так

class MyClass : public BaseClass {
public:
void functionName();
};

При регистрации можно модифицировать идентификатор в UI, который соответствует переменной. При использовании предыдущих команд этот идентификатор такой же, как имя переменной.

С помощью специальных директив можно управлять различными свойствами объекта в UI. Директивы располагаются как префикс перед идентификатором. Если тип переменной variable_name не является наследником BaseClass, используй команду

REG_MEMBER_EX( class_type, variable_name, modified_name_for_UI );

Например

REG_MEMBER_EX( _int, integer_variable, );

В этом случае имя переменной в UI ты не увидишь, только её значение. Однако, есть проблема: если несколько переменных вдруг окажутся зарегистрированы с пустым именем, то при сохранении данных класса в виде XML получишь два пустых тега: данные о значении переменной потеряешь. Поэтому лучше сильно не умничать, а регистрировать так

REG_MEMBER_EX( _int, integer_variable, $integer_variable );

В этом случае имя переменной также не увидишь в UI, но в XML у неё будет ID $integer_variable. Это пример, зачем нужны приставки в определении переменных.

Для регистрации вызова функций.

REG_REG_METHODCL_EX1( ClassName, FunctionName, name_for_UI );

Используем для функций, если необходимо динамически формировать идентификатор UI.

REG_REG_METHODCL_EX2( ClassName, FunctionName, name_for_UI_with_quotes );

При этом name_for_UI_with_quotes будет char*.

Набор приставок перед идентификаторами. Приставки используй в том же порядке, как они приведены в таблице.

Приставка Что делает
{0xHEXADECMAIL_VALUE} Цвет подложки для этого поля в UI
{fc0xHEXADECMAIL_VALUE} Цвет шрифта для поля
[scale=floating_value] Дополнительный масштабный коэффициент для редактируемого поля
integer_value <td> integer_value - идентификатор группы для чекбоксов. В этом случае чекбокс показывается как radio-button. Чекбоксы одной группы должны использовать одинаковые integer_value. Например <tr><td>^ <td> REG_MEMBER_EX( _bool, Choice1,1Choice1 );
^ REG_MEMBER_EX( _bool, Choice2, <tt>1Choice2 ); <tr><td>^ <td> REG_MEMBER_EX( _bool, Choice3,1Choice3 );
^ В этом случае мы получим 3 radio buttons и только один из низ может быть выделен.
% Применять масштаб сцены к этому контролу (обычно это поле float или float-slider)
{hotkey hotkey_id} hotkey будет назначен хоткееем по умолчанию для кнопки. Например
^ {hotkey CTRL ALT SHIFT A}
^ {hotkey SHIFT NUM_PLUS}
^ Список всех идентификаторов
^ 0 1 2 3 4 5 6 7 8 9
^ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
^ ENTER ESC NUM0 NUM1 NUM2 NUM3 NUM4 NUM5 NUM6 NUM7 NUM8 NUM9 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 PGDN PGUP HOME END INS NUM_PLUS NUM_MINUS NUM* NUM/ < > ?
^ F11 F12 ~ SPACE BACK PAUSE DELETE [ ] ; ' , . /
^ Left Right Up Down
^ Lwin Rwin App Lmemu Rmenu Tab Back - +
^ LMB RMB MMB CTRL ALT SHIFT X1 X2
# Не показывать подсказку (HINT)
~ Функция недоступна в Demo-режиме
* Принудительное выравнивание влево
$ Не показывать имя поля, только значение
! Показывать только значение, read only
[^] Иконка Move_area_16.tga
^ Read only
{icon filename_path} Показать иконку

Использование энумераторов

Если в UI необходим выпадающий список, то регистрируем переменную типа int так

int variable;
REG_ENUM( _index, variable, ENUM_ID );

Или

REG_ENUM_EX( _index, valiable, ENUM_ID, custom_name_for_UI );

где ENUM_ID - енумератор (набор значений для выпадающего списка).

Обычно, енумератор создаётся так

Enumerator* E = ENUM.Get( "ENUM_ID" );
if ( E->GetAmount()==0 ) {
E->Add( "Case1" );
E->Add( "Case2" );
E->Add( "Case3" );
}

Этот код лучше всего размещать внутри секции SAVE ... ENDSAVE.

Warning
Енумератор создавай до использования его в UI или до сериализации класса.

Пример

class MyClass : public BaseClass {
public:
int variable;
SAVE ( MyClass ) {
Enumerator* E = ENUM.Get( "ENUM_ID" );
if ( E->GetAmount()==0 ) {
E->Add( "Case1" );
E->Add( "Case2" );
E->Add( "Case3" );
}
REG_ENUM( _index, variable, ENUM_ID );
} ENDSAVE
};

Маскирование элементов класса

При сохранении или же показе данных экземпляра класса, структура представления может динамически меняться. Для этого раньше использовали маскирование элементов с помощью битовых масок. Ты ведь помнишь, что теперь классы мы регистрируем динамически и маски нам не нужны, да? Нет? См. выше.

Например

class MyClass : public BaseClass {
public:
bool ShowField2and3;
int Field1;
int Field2;
int Field3;
SAVE( MyClass ) {
REG_MEMBER( _bool, ShowField2and3 );
// show only when GeClassMask returns 1
SAVE_SECTION( 1 );
REG_MEMBER( _int, Field1 );
// show only when GetClassMask returns 2
SAVE_SECTION( 2 );
REG_MEMBER( _int, Field2 );
// show if GetClassMask returns 2 or 1
SAVE_SECTION( 1 + 2 );
REG_MEMBER( _int, Field3 );
} ENDSAVE;
DWORD GetClassMask() {
return ShowField2and3 ? 2 : 1;
}
};

Обрати внимание на функцию GetClassMask(): она возвращает битовую маску элементов, которые разрешено показывать. SAVE_SECTION определяет битовую маску для всех последующих элементов до следующей директивы SAVE_SECTION. Если операция побитового AND для GetClassMask и SAVE_SECTION возвращает ненулевой результат, то элемент показывается в UI или будет принимать участие в сериализации.

Форматирование элемента UI

Элементы в UI можно размещать в несколько столбиков, контролировать, будет ли ширина поля фиксированной, или же подбираться автоматически. За это отвечает директива

COLUMNS( ... )

Внутри COLUMNS размещаются (без кавычек ") относительная или абсолютная ширина последующих элементов. Например:

COLUMNS([16] 2 1);
REG_MEMBER_EX( _bool, bool_field, $bool_field );
REG_MEMBER_EX( _int, IntField1, $IntField1 );
REG_MEMBER_EX( _int, IntField2, $IntField2 );

Вариант команды COLUMNS

COLUMNS([16];2;1);
COLUMNS([16],2,1);

Ещё вариант

TCOLUMNS("[16];2;1");
TCOLUMNS("[16],2,1");
TCOLUMNS("[16] 2 1");

Все три поля в данном случае выводятся без названия, только значения. Ширина контрола, соответствующего bool_field будет 16 пкс, а ширина поля IntField1 будет вдвое больше ширины IntField2.

В качестве дополнительных элементов форматирования удобно использовать

// просто горизонтальная линия-разделитель
// текстовый нередактируемый идентификатор
// Если в файле English.xml азместить тэг text_there и его перевод, то перевод появится в UI.
TEXTMSG( text_there );

Модификаторы при регистрации

Существует несколько модификаторов при регистрации членов класса. Все модификаторы пишутся *перед* командой регистрации элемента.

Указывает на то, что данных элемент не участвует в сериализации класса, то есть не сохраняется и не считывается из XML-структуры.

Элемент может участвовать в сериализации, но будет невидим в UI.

Модификаторы NOSAVE и INVISIBLE полезны для согласования как класс увидит художник и как класс сохранится в XML.

При регистрации статических переменных класса или же глобальных переменных не членов класса, обязательно указывай, что они являются статическими.

STATIC

Классы со специальным интерфейсом

Если класс является наследником BaseClass, то регистрируем класс так

REG_AUTO( var_name );

Или так

REG_AUTO_EX( var_vame, name_in_UI );

В 3DCoat существует несколько полезных классов, для которых уже написан сравнительно удобный редактор. Вот табличка этих удобств.

Класс / Тип Как регистрировать Что творит
_str REG_AUTO/REG_AUTO_EX Редактирование строки, однострочный редактор.
_int REG_MEMBER( _TextureID, ... ) Выбор текстуры, в результате в переменной типа int будет либо -1 , либо идентификатор текстуры.
One2DCurve REG_AUTO Редактирование кривых.
ClassArray<One2DCurve> REG_AUTO Редактирование группы кривых.
_str REG_AUTO_EX(path,save:*.ext1;*.ext2;) Строка используется как путь к файлу. В зависимости от использования load или save при выбое пути открывается файловый диалог для загрузки или же сохранения файлов.
^ REG_AUTO_EX(path,load:*.ext1;*.ext2;) ^
_bool REG_MEMBER(_bool,Visible); Если элемент класса зарегистрирован как _bool и называется Visible, то он будет отображаться в виде иконки с глазиком.

Создание собственного представления класса в UI

В некоторых случаях необходимо создать собственный редактор класса, **отличный** от стандартного. В этом случае смело перегружай функцию

virtual void OnCreateControl( const char* FieldName, BaseWidget* W )

При создании интерфейса эта функция вызывается для каждого зарегистрированного элемента класса. Ты можешь идентифицировать этот член по его имени в FieldName. Контролы можешь строить внутри W. До вызова OnCreateControl() в W уже создано стандартное представление данного элемента. Его размер и расположение записаны в BaseWidget::Rect, но ты всё можешь поменять.

Посмотри примеры реализации собственного редактора в этих классах

MultiMtlLayer::OnCreateControl()
RetopoGroup::OnCreateControl()
VoxTreeBranch::OnCreateControl()

Полезные виртуальные функции BaseClass

Вызывается на каждом кадре при редактировании класса.

virtual void ProcessInEditor( BaseClass* Parent )

Вызывается после изменения пользователем элемента класса. В настоящий момент возвращаемое значение не играет роли.

virtual bool OnChangeMember( BaseClass* MembClass, void* MembPtr, void* MembExtra, const char* MembName )

Вызывается непосредственно перед изменением пользователем элемента класса. Полезна для Undo.

virtual void BeforeChangeMember( BaseClass* MembClass, void* MembPtr, void* MembExtra, const char* MembName )
See Also
Как создать своё полноценное Анду

Используются для реализации Drag & Drop. Для углублённого понимания смотри примеры в коде Кота.

virtual bool CanBeDragged( const char* MemberID, int& dx, int& dy )
virtual bool CanAcceptDrag( const char* MemberID )
virtual bool OnStartDrag( const char* MemberID )
virtual bool OnEndDrag( const char* MemberID )
virtual bool OnAcceptDrag( BaseClass* DraggedItemParent, const char* DraggedMemberID, const char* AcceptorMemberID, iRct MyRect )

Чтение / запись данных экземпляра класса на диск, в поток или строку

Каждый наследник BaseClass может быть сохранён в поток данных или же считан из него. Примеры сохранения и считывания

MyClass C;
MyClass C1;
// в поток
MemoryBinStream BS;
C.ToBS( BS, true );
// скопировали из C в C1, используя поток
C1.FromBS( BS );
// в файл
C.WriteToFile( "file.xml" );
C1.SafeReadFromFile( "file.xml" );
// в строку
_str s;
C.ToStr( s );
C1.FromStr( s );
See Also
Элементы интерфейса (виджеты)
Видео от Шпагина 0:10