mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-25 05:07:12 +01:00
Backported from Godot master: Made low level changes to the Skeleton3D class and Skeleton3D inspector. Changes listed below:
* Added helper functions to Skeleton3D for converting transforms from bone space to global space, and vice versa.
* Updated the Skeleton3D class reference.
* Changed the icon used for bones in the Skeleton3D inspector to use BoneAttachement3D's icon.
* Changed the Skeleton3D inspector to use EditorPropertyTransform and EditorPropertyVector3 when possible.
* Placed the Transform/Matrix for each bone in a sub-section, so it is visually similar to the Node3D inspector.
- TwistedTwigleg
24905becb2
- Also fixed smaller issues.
This commit is contained in:
parent
3d4a55a1a3
commit
a478334d4a
@ -28,6 +28,16 @@
|
|||||||
[i]Deprecated soon.[/i]
|
[i]Deprecated soon.[/i]
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="bone_transform_to_world_transform">
|
||||||
|
<return type="Transform">
|
||||||
|
</return>
|
||||||
|
<argument index="0" name="bone_transform" type="Transform">
|
||||||
|
</argument>
|
||||||
|
<description>
|
||||||
|
Takes the given bone pose/transform and converts it to a world transform, relative to the [Skeleton3D] node.
|
||||||
|
This is useful for using the bone transform in calculations with transforms from [Node3D]-based nodes.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="clear_bones">
|
<method name="clear_bones">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
@ -37,6 +47,7 @@
|
|||||||
<method name="clear_bones_global_pose_override">
|
<method name="clear_bones_global_pose_override">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
|
Removes the global pose override on all bones in the skeleton.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="find_bone" qualifiers="const">
|
<method name="find_bone" qualifiers="const">
|
||||||
@ -125,38 +136,56 @@
|
|||||||
[i]Deprecated soon.[/i]
|
[i]Deprecated soon.[/i]
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="is_bone_rest_disabled" qualifiers="const">
|
||||||
|
<return type="bool">
|
||||||
|
</return>
|
||||||
|
<argument index="0" name="bone_idx" type="int">
|
||||||
|
</argument>
|
||||||
|
<description>
|
||||||
|
Returns whether the bone rest for the bone at [code]bone_idx[/code] is disabled.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="localize_rests">
|
<method name="localize_rests">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
|
Returns all bones in the skeleton to their rest poses.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="physical_bones_add_collision_exception">
|
<method name="physical_bones_add_collision_exception">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="exception" type="RID" />
|
<argument index="0" name="exception" type="RID" />
|
||||||
<description>
|
<description>
|
||||||
|
Adds a collision exception to the physical bone.
|
||||||
|
Works just like the [RigidBody3D] node.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="physical_bones_remove_collision_exception">
|
<method name="physical_bones_remove_collision_exception">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="exception" type="RID" />
|
<argument index="0" name="exception" type="RID" />
|
||||||
<description>
|
<description>
|
||||||
|
Removes a collision exception to the physical bone.
|
||||||
|
Works just like the [RigidBody3D] node.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="physical_bones_start_simulation">
|
<method name="physical_bones_start_simulation">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="bones" type="Array" default="[ ]" />
|
<argument index="0" name="bones" type="Array" default="[ ]" />
|
||||||
<description>
|
<description>
|
||||||
|
Tells the [PhysicalBone3D] nodes in the Skeleton to start simulating and reacting to the physics world.
|
||||||
|
Optionally, a list of bone names can be passed-in, allowing only the passed-in bones to be simulated.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="physical_bones_stop_simulation">
|
<method name="physical_bones_stop_simulation">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
|
Tells the [PhysicalBone3D] nodes in the Skeleton to stop simulating.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="register_skin">
|
<method name="register_skin">
|
||||||
<return type="SkinReference" />
|
<return type="SkinReference" />
|
||||||
<argument index="0" name="skin" type="Skin" />
|
<argument index="0" name="skin" type="Skin" />
|
||||||
<description>
|
<description>
|
||||||
|
Binds the given Skin to the Skeleton.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="remove_bone">
|
<method name="remove_bone">
|
||||||
@ -172,6 +201,9 @@
|
|||||||
<argument index="2" name="amount" type="float" />
|
<argument index="2" name="amount" type="float" />
|
||||||
<argument index="3" name="persistent" type="bool" default="false" />
|
<argument index="3" name="persistent" type="bool" default="false" />
|
||||||
<description>
|
<description>
|
||||||
|
Sets the global pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code].
|
||||||
|
[code]amount[/code] is the interpolation strengh that will be used when applying the pose, and [code]persistent[/code] determines if the applied pose will remain.
|
||||||
|
[b]Note[/b]: The pose transform needs to be in bone space. Use [method world_transform_to_bone_transform] to convert a world transform, like one you can get from a [Node3D], to bone space.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="set_bone_name">
|
<method name="set_bone_name">
|
||||||
@ -241,6 +273,17 @@
|
|||||||
<return type="void" />
|
<return type="void" />
|
||||||
<argument index="0" name="bone_idx" type="int" />
|
<argument index="0" name="bone_idx" type="int" />
|
||||||
<description>
|
<description>
|
||||||
|
Unparents the bone at [code]bone_idx[/code] and sets its rest position to that of it's parent prior to being reset.
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
|
<method name="world_transform_to_bone_transform">
|
||||||
|
<return type="Transform">
|
||||||
|
</return>
|
||||||
|
<argument index="0" name="world_transform" type="Transform">
|
||||||
|
</argument>
|
||||||
|
<description>
|
||||||
|
Takes the given world transform, relative to the [Skeleton3D], and converts it to a bone pose/transform.
|
||||||
|
This is useful for using setting bone poses using transforms from [Node3D]-based nodes.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
|
@ -1878,13 +1878,25 @@ void EditorPropertyVector3::_value_changed(double val, const String &p_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EditorPropertyVector3::update_property() {
|
void EditorPropertyVector3::update_property() {
|
||||||
Vector3 val = get_edited_object()->get(get_edited_property());
|
update_using_vector(get_edited_object()->get(get_edited_property()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyVector3::update_using_vector(Vector3 p_vector) {
|
||||||
setting = true;
|
setting = true;
|
||||||
spin[0]->set_value(val.x);
|
spin[0]->set_value(p_vector.x);
|
||||||
spin[1]->set_value(val.y);
|
spin[1]->set_value(p_vector.y);
|
||||||
spin[2]->set_value(val.z);
|
spin[2]->set_value(p_vector.z);
|
||||||
setting = false;
|
setting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3 EditorPropertyVector3::get_vector() {
|
||||||
|
Vector3 v3;
|
||||||
|
v3.x = spin[0]->get_value();
|
||||||
|
v3.y = spin[1]->get_value();
|
||||||
|
v3.z = spin[2]->get_value();
|
||||||
|
return v3;
|
||||||
|
}
|
||||||
|
|
||||||
void EditorPropertyVector3::_notification(int p_what) {
|
void EditorPropertyVector3::_notification(int p_what) {
|
||||||
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
|
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
|
||||||
Color base = get_color("accent_color", "Editor");
|
Color base = get_color("accent_color", "Editor");
|
||||||
@ -2441,23 +2453,27 @@ void EditorPropertyTransform::_value_changed(double val, const String &p_name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EditorPropertyTransform::update_property() {
|
void EditorPropertyTransform::update_property() {
|
||||||
Transform val = get_edited_object()->get(get_edited_property());
|
update_using_transform(get_edited_object()->get(get_edited_property()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorPropertyTransform::update_using_transform(Transform p_transform) {
|
||||||
setting = true;
|
setting = true;
|
||||||
spin[0]->set_value(val.basis[0][0]);
|
spin[0]->set_value(p_transform.basis[0][0]);
|
||||||
spin[1]->set_value(val.basis[1][0]);
|
spin[1]->set_value(p_transform.basis[1][0]);
|
||||||
spin[2]->set_value(val.basis[2][0]);
|
spin[2]->set_value(p_transform.basis[2][0]);
|
||||||
spin[3]->set_value(val.basis[0][1]);
|
spin[3]->set_value(p_transform.basis[0][1]);
|
||||||
spin[4]->set_value(val.basis[1][1]);
|
spin[4]->set_value(p_transform.basis[1][1]);
|
||||||
spin[5]->set_value(val.basis[2][1]);
|
spin[5]->set_value(p_transform.basis[2][1]);
|
||||||
spin[6]->set_value(val.basis[0][2]);
|
spin[6]->set_value(p_transform.basis[0][2]);
|
||||||
spin[7]->set_value(val.basis[1][2]);
|
spin[7]->set_value(p_transform.basis[1][2]);
|
||||||
spin[8]->set_value(val.basis[2][2]);
|
spin[8]->set_value(p_transform.basis[2][2]);
|
||||||
spin[9]->set_value(val.origin[0]);
|
spin[9]->set_value(p_transform.origin[0]);
|
||||||
spin[10]->set_value(val.origin[1]);
|
spin[10]->set_value(p_transform.origin[1]);
|
||||||
spin[11]->set_value(val.origin[2]);
|
spin[11]->set_value(p_transform.origin[2]);
|
||||||
|
|
||||||
setting = false;
|
setting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditorPropertyTransform::_notification(int p_what) {
|
void EditorPropertyTransform::_notification(int p_what) {
|
||||||
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
|
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
|
||||||
Color base = get_color("accent_color", "Editor");
|
Color base = get_color("accent_color", "Editor");
|
||||||
|
@ -494,6 +494,8 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void update_property();
|
virtual void update_property();
|
||||||
|
virtual void update_using_vector(Vector3 p_vector);
|
||||||
|
virtual Vector3 get_vector();
|
||||||
void setup(double p_min, double p_max, double p_step, bool p_no_slider);
|
void setup(double p_min, double p_max, double p_step, bool p_no_slider);
|
||||||
EditorPropertyVector3();
|
EditorPropertyVector3();
|
||||||
};
|
};
|
||||||
@ -606,6 +608,7 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void update_property();
|
virtual void update_property();
|
||||||
|
virtual void update_using_transform(Transform p_transform);
|
||||||
void setup(double p_min, double p_max, double p_step, bool p_no_slider);
|
void setup(double p_min, double p_max, double p_step, bool p_no_slider);
|
||||||
EditorPropertyTransform();
|
EditorPropertyTransform();
|
||||||
};
|
};
|
||||||
|
@ -98,76 +98,46 @@ void ModuleBoneTransformEditor::create_editors() {
|
|||||||
enabled_checkbox->set_visible(toggle_enabled);
|
enabled_checkbox->set_visible(toggle_enabled);
|
||||||
section->get_vbox()->add_child(enabled_checkbox);
|
section->get_vbox()->add_child(enabled_checkbox);
|
||||||
|
|
||||||
Label *l1 = memnew(Label(TTR("Translation")));
|
// Translation property
|
||||||
section->get_vbox()->add_child(l1);
|
translation_property = memnew(EditorPropertyVector3());
|
||||||
|
translation_property->setup(-10000, 10000, 0.001f, true);
|
||||||
|
translation_property->set_label("Translation");
|
||||||
|
translation_property->set_use_folding(true);
|
||||||
|
translation_property->set_read_only(false);
|
||||||
|
translation_property->connect("property_changed", this, "_value_changed_vector3");
|
||||||
|
section->get_vbox()->add_child(translation_property);
|
||||||
|
|
||||||
translation_grid = memnew(GridContainer());
|
// Rotation property
|
||||||
translation_grid->set_columns(TRANSLATION_COMPONENTS);
|
rotation_property = memnew(EditorPropertyVector3());
|
||||||
section->get_vbox()->add_child(translation_grid);
|
rotation_property->setup(-10000, 10000, 0.001f, true);
|
||||||
|
rotation_property->set_label("Rotation Degrees");
|
||||||
|
rotation_property->set_use_folding(true);
|
||||||
|
rotation_property->set_read_only(false);
|
||||||
|
rotation_property->connect("property_changed", this, "_value_changed_vector3");
|
||||||
|
section->get_vbox()->add_child(rotation_property);
|
||||||
|
|
||||||
Label *l2 = memnew(Label(TTR("Rotation Degrees")));
|
// Scale property
|
||||||
section->get_vbox()->add_child(l2);
|
scale_property = memnew(EditorPropertyVector3());
|
||||||
|
scale_property->setup(-10000, 10000, 0.001f, true);
|
||||||
|
scale_property->set_label("Scale");
|
||||||
|
scale_property->set_use_folding(true);
|
||||||
|
scale_property->set_read_only(false);
|
||||||
|
scale_property->connect("property_changed", this, "_value_changed_vector3");
|
||||||
|
section->get_vbox()->add_child(scale_property);
|
||||||
|
|
||||||
rotation_grid = memnew(GridContainer());
|
// Transform/Matrix section
|
||||||
rotation_grid->set_columns(ROTATION_DEGREES_COMPONENTS);
|
transform_section = memnew(EditorInspectorSection);
|
||||||
section->get_vbox()->add_child(rotation_grid);
|
transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true);
|
||||||
|
section->get_vbox()->add_child(transform_section);
|
||||||
|
|
||||||
Label *l3 = memnew(Label(TTR("Scale")));
|
// Transform/Matrix property
|
||||||
section->get_vbox()->add_child(l3);
|
transform_property = memnew(EditorPropertyTransform());
|
||||||
|
transform_property->setup(-10000, 10000, 0.001f, true);
|
||||||
scale_grid = memnew(GridContainer());
|
transform_property->set_label("Transform");
|
||||||
scale_grid->set_columns(SCALE_COMPONENTS);
|
transform_property->set_use_folding(true);
|
||||||
section->get_vbox()->add_child(scale_grid);
|
transform_property->set_read_only(false);
|
||||||
|
transform_property->connect("property_changed", this, "_value_changed_transform");
|
||||||
Label *l4 = memnew(Label(TTR("Transform")));
|
transform_section->get_vbox()->add_child(transform_property);
|
||||||
section->get_vbox()->add_child(l4);
|
|
||||||
|
|
||||||
transform_grid = memnew(GridContainer());
|
|
||||||
transform_grid->set_columns(TRANSFORM_CONTROL_COMPONENTS);
|
|
||||||
section->get_vbox()->add_child(transform_grid);
|
|
||||||
|
|
||||||
static const char *desc[TRANSFORM_COMPONENTS] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" };
|
|
||||||
float snap = EDITOR_GET("interface/inspector/default_float_step");
|
|
||||||
|
|
||||||
for (int i = 0; i < TRANSFORM_CONTROL_COMPONENTS; ++i) {
|
|
||||||
translation_slider[i] = memnew(EditorSpinSlider());
|
|
||||||
translation_slider[i]->set_label(desc[i]);
|
|
||||||
translation_slider[i]->set_step(snap);
|
|
||||||
setup_spinner(translation_slider[i], false);
|
|
||||||
translation_grid->add_child(translation_slider[i]);
|
|
||||||
|
|
||||||
rotation_slider[i] = memnew(EditorSpinSlider());
|
|
||||||
rotation_slider[i]->set_label(desc[i]);
|
|
||||||
rotation_slider[i]->set_step(snap);
|
|
||||||
setup_spinner(rotation_slider[i], false);
|
|
||||||
rotation_grid->add_child(rotation_slider[i]);
|
|
||||||
|
|
||||||
scale_slider[i] = memnew(EditorSpinSlider());
|
|
||||||
scale_slider[i]->set_label(desc[i]);
|
|
||||||
scale_slider[i]->set_step(snap);
|
|
||||||
setup_spinner(scale_slider[i], false);
|
|
||||||
scale_grid->add_child(scale_slider[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < TRANSFORM_COMPONENTS; ++i) {
|
|
||||||
transform_slider[i] = memnew(EditorSpinSlider());
|
|
||||||
transform_slider[i]->set_label(desc[i]);
|
|
||||||
transform_slider[i]->set_step(snap);
|
|
||||||
setup_spinner(transform_slider[i], true);
|
|
||||||
transform_grid->add_child(transform_slider[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner) {
|
|
||||||
spinner->set_flat(true);
|
|
||||||
spinner->set_min(-10000);
|
|
||||||
spinner->set_max(10000);
|
|
||||||
spinner->set_hide_slider(true);
|
|
||||||
spinner->set_allow_greater(true);
|
|
||||||
spinner->set_allow_lesser(true);
|
|
||||||
spinner->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
||||||
|
|
||||||
spinner->connect("value_changed", this, "_value_changed", varray(is_transform_spinner));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::_notification(int p_what) {
|
void ModuleBoneTransformEditor::_notification(int p_what) {
|
||||||
@ -178,49 +148,6 @@ void ModuleBoneTransformEditor::_notification(int p_what) {
|
|||||||
enabled_checkbox->connect("toggled", this, "_checkbox_toggled");
|
enabled_checkbox->connect("toggled", this, "_checkbox_toggled");
|
||||||
FALLTHROUGH;
|
FALLTHROUGH;
|
||||||
}
|
}
|
||||||
case NOTIFICATION_THEME_CHANGED: {
|
|
||||||
const Color base = get_color("accent_color", "Editor");
|
|
||||||
const Color bg_color = get_color("property_color", "Editor");
|
|
||||||
const Color bg_lbl_color(bg_color.r, bg_color.g, bg_color.b, 0.5);
|
|
||||||
|
|
||||||
for (int i = 0; i < TRANSLATION_COMPONENTS; i++) {
|
|
||||||
Color c = base;
|
|
||||||
c.set_hsv(float(i % TRANSLATION_COMPONENTS) / TRANSLATION_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v());
|
|
||||||
if (!translation_slider[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
translation_slider[i]->set_custom_label_color(true, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) {
|
|
||||||
Color c = base;
|
|
||||||
c.set_hsv(float(i % ROTATION_DEGREES_COMPONENTS) / ROTATION_DEGREES_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v());
|
|
||||||
if (!rotation_slider[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rotation_slider[i]->set_custom_label_color(true, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < SCALE_COMPONENTS; i++) {
|
|
||||||
Color c = base;
|
|
||||||
c.set_hsv(float(i % SCALE_COMPONENTS) / SCALE_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v());
|
|
||||||
if (!scale_slider[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
scale_slider[i]->set_custom_label_color(true, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < TRANSFORM_COMPONENTS; i++) {
|
|
||||||
Color c = base;
|
|
||||||
c.set_hsv(float(i % TRANSFORM_COMPONENTS) / TRANSFORM_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v());
|
|
||||||
if (!transform_slider[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
transform_slider[i]->set_custom_label_color(true, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NOTIFICATION_SORT_CHILDREN: {
|
case NOTIFICATION_SORT_CHILDREN: {
|
||||||
const Ref<Font> font = get_font("font", "Tree");
|
const Ref<Font> font = get_font("font", "Tree");
|
||||||
|
|
||||||
@ -229,12 +156,13 @@ void ModuleBoneTransformEditor::_notification(int p_what) {
|
|||||||
buffer.y += font->get_height();
|
buffer.y += font->get_height();
|
||||||
buffer.y += get_constant("vseparation", "Tree");
|
buffer.y += get_constant("vseparation", "Tree");
|
||||||
|
|
||||||
const float vector_height = translation_grid->get_size().y;
|
const float vector_height = translation_property->get_size().y;
|
||||||
const float transform_height = transform_grid->get_size().y;
|
const float transform_height = transform_property->get_size().y;
|
||||||
const float button_height = key_button->get_size().y;
|
const float button_height = key_button->get_size().y;
|
||||||
|
|
||||||
const float width = get_size().x - get_constant("inspector_margin", "Editor");
|
const float width = get_size().x - get_constant("inspector_margin", "Editor");
|
||||||
Vector<Rect2> input_rects;
|
Vector<Rect2> input_rects;
|
||||||
|
|
||||||
if (keyable && section->get_vbox()->is_visible()) {
|
if (keyable && section->get_vbox()->is_visible()) {
|
||||||
input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height)));
|
input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height)));
|
||||||
} else {
|
} else {
|
||||||
@ -242,10 +170,10 @@ void ModuleBoneTransformEditor::_notification(int p_what) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (section->get_vbox()->is_visible()) {
|
if (section->get_vbox()->is_visible()) {
|
||||||
input_rects.push_back(Rect2(translation_grid->get_position() + buffer, Size2(width, vector_height)));
|
input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height)));
|
||||||
input_rects.push_back(Rect2(rotation_grid->get_position() + buffer, Size2(width, vector_height)));
|
input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height)));
|
||||||
input_rects.push_back(Rect2(scale_grid->get_position() + buffer, Size2(width, vector_height)));
|
input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height)));
|
||||||
input_rects.push_back(Rect2(transform_grid->get_position() + buffer, Size2(width, transform_height)));
|
input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height)));
|
||||||
} else {
|
} else {
|
||||||
const int32_t start = input_rects.size();
|
const int32_t start = input_rects.size();
|
||||||
const int32_t empty_input_rect_elements = 4;
|
const int32_t empty_input_rect_elements = 4;
|
||||||
@ -274,47 +202,67 @@ void ModuleBoneTransformEditor::_notification(int p_what) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::_value_changed(const double p_value, const bool p_from_transform) {
|
void ModuleBoneTransformEditor::_value_changed(const double p_value) {
|
||||||
if (updating)
|
if (updating)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") {
|
Transform tform = compute_transform_from_vector3s();
|
||||||
const Transform tform = compute_transform(p_from_transform);
|
_change_transform(tform);
|
||||||
|
|
||||||
undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS);
|
|
||||||
undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int()));
|
|
||||||
undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), tform);
|
|
||||||
undo_redo->commit_action();
|
|
||||||
} else if (property.get_slicec('/', 0) == "bones") {
|
|
||||||
const Transform tform = compute_transform(p_from_transform);
|
|
||||||
|
|
||||||
undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
|
|
||||||
undo_redo->add_undo_property(skeleton, property, skeleton->get(property));
|
|
||||||
undo_redo->add_do_property(skeleton, property, tform);
|
|
||||||
undo_redo->commit_action();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform ModuleBoneTransformEditor::compute_transform(const bool p_from_transform) const {
|
void ModuleBoneTransformEditor::_value_changed_vector3(const String &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
|
||||||
// Last modified was a raw transform column...
|
if (updating) {
|
||||||
if (p_from_transform) {
|
return;
|
||||||
Transform tform;
|
|
||||||
|
|
||||||
for (int i = 0; i < BASIS_COMPONENTS; ++i) {
|
|
||||||
tform.basis[i / BASIS_SPLIT_COMPONENTS][i % BASIS_SPLIT_COMPONENTS] = transform_slider[i]->get_value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < TRANSLATION_COMPONENTS; ++i) {
|
Transform tform = compute_transform_from_vector3s();
|
||||||
tform.origin[i] = transform_slider[i + BASIS_COMPONENTS]->get_value();
|
_change_transform(tform);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tform;
|
Transform ModuleBoneTransformEditor::compute_transform_from_vector3s() const {
|
||||||
}
|
// Convert rotation from degrees to radians.
|
||||||
|
Vector3 prop_rotation = rotation_property->get_vector();
|
||||||
|
prop_rotation.x = Math::deg2rad(prop_rotation.x);
|
||||||
|
prop_rotation.y = Math::deg2rad(prop_rotation.y);
|
||||||
|
prop_rotation.z = Math::deg2rad(prop_rotation.z);
|
||||||
|
|
||||||
return Transform(
|
return Transform(
|
||||||
Basis(Vector3(Math::deg2rad(rotation_slider[0]->get_value()), Math::deg2rad(rotation_slider[1]->get_value()), Math::deg2rad(rotation_slider[2]->get_value())),
|
Basis(prop_rotation, scale_property->get_vector()),
|
||||||
Vector3(scale_slider[0]->get_value(), scale_slider[1]->get_value(), scale_slider[2]->get_value())),
|
translation_property->get_vector());
|
||||||
Vector3(translation_slider[0]->get_value(), translation_slider[1]->get_value(), translation_slider[2]->get_value()));
|
}
|
||||||
|
|
||||||
|
void ModuleBoneTransformEditor::_value_changed_transform(const String &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform transform = p_value;
|
||||||
|
|
||||||
|
_change_transform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleBoneTransformEditor::_change_transform(Transform p_new_transform) {
|
||||||
|
if (property.get_slicec('/', 0) != "bones") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String s = property.get_slicec('/', 2);
|
||||||
|
|
||||||
|
if (s == "pose") {
|
||||||
|
undo_redo->create_action(TTR("Set Pose Transform"), UndoRedo::MERGE_ENDS);
|
||||||
|
undo_redo->add_undo_method(skeleton, "set_bone_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_pose(property.get_slicec('/', 1).to_int()));
|
||||||
|
undo_redo->add_do_method(skeleton, "set_bone_pose", property.get_slicec('/', 1).to_int(), p_new_transform);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
} else if (s == "custom_pose") {
|
||||||
|
undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS);
|
||||||
|
undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int()));
|
||||||
|
undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform);
|
||||||
|
undo_redo->commit_action();
|
||||||
|
} else if (s == "rest") {
|
||||||
|
undo_redo->create_action(TTR("Set Bone Rest Transform"), UndoRedo::MERGE_ENDS);
|
||||||
|
undo_redo->add_undo_property(skeleton, property, skeleton->get(property));
|
||||||
|
undo_redo->add_do_property(skeleton, property, p_new_transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::update_enabled_checkbox() {
|
void ModuleBoneTransformEditor::update_enabled_checkbox() {
|
||||||
@ -330,9 +278,13 @@ void ModuleBoneTransformEditor::update_enabled_checkbox() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::_bind_methods() {
|
void ModuleBoneTransformEditor::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("_value_changed", "value"), &ModuleBoneTransformEditor::_value_changed);
|
|
||||||
ClassDB::bind_method(D_METHOD("_key_button_pressed"), &ModuleBoneTransformEditor::_key_button_pressed);
|
ClassDB::bind_method(D_METHOD("_key_button_pressed"), &ModuleBoneTransformEditor::_key_button_pressed);
|
||||||
ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &ModuleBoneTransformEditor::_checkbox_toggled);
|
ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &ModuleBoneTransformEditor::_checkbox_toggled);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("_value_changed"), &ModuleBoneTransformEditor::_value_changed);
|
||||||
|
ClassDB::bind_method(D_METHOD("_value_changed_vector3"), &ModuleBoneTransformEditor::_value_changed_vector3);
|
||||||
|
ClassDB::bind_method(D_METHOD("_value_changed_transform"), &ModuleBoneTransformEditor::_value_changed_transform);
|
||||||
|
ClassDB::bind_method(D_METHOD("_change_transform"), &ModuleBoneTransformEditor::_change_transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::_update_properties() {
|
void ModuleBoneTransformEditor::_update_properties() {
|
||||||
@ -368,47 +320,22 @@ void ModuleBoneTransformEditor::_update_custom_pose_properties() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::_update_transform_properties(Transform tform) {
|
void ModuleBoneTransformEditor::_update_transform_properties(Transform tform) {
|
||||||
Quat rot = tform.get_basis().orthonormalized();
|
Basis rotation_basis = tform.get_basis();
|
||||||
Vector3 rot_rad = rot.get_euler();
|
Vector3 rotation_radians = rotation_basis.get_rotation_euler();
|
||||||
Vector3 rot_degrees = Vector3(Math::rad2deg(rot_rad.x), Math::rad2deg(rot_rad.y), Math::rad2deg(rot_rad.z));
|
Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z));
|
||||||
Vector3 tr = tform.get_origin();
|
Vector3 translation = tform.get_origin();
|
||||||
Vector3 scale = tform.basis.get_scale();
|
Vector3 scale = tform.basis.get_scale();
|
||||||
|
|
||||||
for (int i = 0; i < TRANSLATION_COMPONENTS; i++) {
|
translation_property->update_using_vector(translation);
|
||||||
translation_slider[i]->set_value(tr[i]);
|
rotation_property->update_using_vector(rotation_degrees);
|
||||||
}
|
scale_property->update_using_vector(scale);
|
||||||
|
transform_property->update_using_transform(tform);
|
||||||
for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) {
|
|
||||||
rotation_slider[i]->set_value(rot_degrees[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < SCALE_COMPONENTS; i++) {
|
|
||||||
scale_slider[i]->set_value(scale[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
transform_slider[0]->set_value(tform.get_basis()[Vector3::AXIS_X].x);
|
|
||||||
transform_slider[1]->set_value(tform.get_basis()[Vector3::AXIS_Y].x);
|
|
||||||
transform_slider[2]->set_value(tform.get_basis()[Vector3::AXIS_Z].x);
|
|
||||||
transform_slider[3]->set_value(tform.get_basis()[Vector3::AXIS_X].y);
|
|
||||||
transform_slider[4]->set_value(tform.get_basis()[Vector3::AXIS_Y].y);
|
|
||||||
transform_slider[5]->set_value(tform.get_basis()[Vector3::AXIS_Z].y);
|
|
||||||
transform_slider[6]->set_value(tform.get_basis()[Vector3::AXIS_X].z);
|
|
||||||
transform_slider[7]->set_value(tform.get_basis()[Vector3::AXIS_Y].z);
|
|
||||||
transform_slider[8]->set_value(tform.get_basis()[Vector3::AXIS_Z].z);
|
|
||||||
|
|
||||||
for (int i = 0; i < TRANSLATION_COMPONENTS; i++) {
|
|
||||||
transform_slider[BASIS_COMPONENTS + i]->set_value(tform.get_origin()[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_enabled_checkbox();
|
update_enabled_checkbox();
|
||||||
updating = false;
|
updating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleBoneTransformEditor::ModuleBoneTransformEditor(Skeleton *p_skeleton) :
|
ModuleBoneTransformEditor::ModuleBoneTransformEditor(Skeleton *p_skeleton) :
|
||||||
translation_slider(),
|
|
||||||
rotation_slider(),
|
|
||||||
scale_slider(),
|
|
||||||
transform_slider(),
|
|
||||||
skeleton(p_skeleton),
|
skeleton(p_skeleton),
|
||||||
key_button(nullptr),
|
key_button(nullptr),
|
||||||
enabled_checkbox(nullptr),
|
enabled_checkbox(nullptr),
|
||||||
@ -447,7 +374,7 @@ void ModuleBoneTransformEditor::_key_button_pressed() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Need to normalize the basis before you key it
|
// Need to normalize the basis before you key it
|
||||||
Transform tform = compute_transform(true);
|
Transform tform = compute_transform_from_vector3s();
|
||||||
tform.orthonormalize();
|
tform.orthonormalize();
|
||||||
AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform);
|
AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform);
|
||||||
}
|
}
|
||||||
@ -459,21 +386,6 @@ void ModuleBoneTransformEditor::_checkbox_toggled(const bool p_toggled) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleBoneTransformEditor::set_read_only(const bool p_read_only) {
|
|
||||||
for (int i = 0; i < TRANSLATION_COMPONENTS; i++) {
|
|
||||||
translation_slider[i]->set_read_only(p_read_only);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) {
|
|
||||||
rotation_slider[i]->set_read_only(p_read_only);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < SCALE_COMPONENTS; i++) {
|
|
||||||
scale_slider[i]->set_read_only(p_read_only);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < TRANSFORM_COMPONENTS; i++) {
|
|
||||||
transform_slider[i]->set_read_only(p_read_only);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModuleSkeletonEditor::set_keyable(const bool p_keyable) {
|
void ModuleSkeletonEditor::set_keyable(const bool p_keyable) {
|
||||||
keyable = p_keyable;
|
keyable = p_keyable;
|
||||||
options->get_popup()->set_item_disabled(MENU_OPTION_INSERT_KEYS, !p_keyable);
|
options->get_popup()->set_item_disabled(MENU_OPTION_INSERT_KEYS, !p_keyable);
|
||||||
@ -1067,12 +979,7 @@ void ModuleSkeletonEditor::set_rest_mode_toggled(const bool pressed) {
|
|||||||
for (int i = 0; i < bone_len; i++) {
|
for (int i = 0; i < bone_len; i++) {
|
||||||
skeleton->set_bone_enabled(i, !rest_mode);
|
skeleton->set_bone_enabled(i, !rest_mode);
|
||||||
}
|
}
|
||||||
if (pose_editor) {
|
|
||||||
pose_editor->set_read_only(rest_mode);
|
|
||||||
}
|
|
||||||
if (custom_pose_editor) {
|
|
||||||
custom_pose_editor->set_read_only(rest_mode);
|
|
||||||
}
|
|
||||||
set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying() && !rest_mode);
|
set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying() && !rest_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@
|
|||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
|
|
||||||
#include "core/os/input_event.h"
|
#include "core/os/input_event.h"
|
||||||
|
#include "editor/editor_inspector.h"
|
||||||
#include "editor/editor_node.h"
|
#include "editor/editor_node.h"
|
||||||
#include "editor/editor_plugin.h"
|
#include "editor/editor_plugin.h"
|
||||||
#include "editor/plugins/spatial_editor_plugin.h"
|
#include "editor/plugins/spatial_editor_plugin.h"
|
||||||
#include "scene/3d/camera.h"
|
#include "scene/3d/camera.h"
|
||||||
#include "scene/3d/mesh_instance.h"
|
#include "scene/3d/mesh_instance.h"
|
||||||
#include "scene/3d/skeleton.h"
|
#include "scene/3d/skeleton.h"
|
||||||
#include "editor/editor_inspector.h"
|
|
||||||
|
|
||||||
class ModuleEditorInspectorPluginSkeleton;
|
class ModuleEditorInspectorPluginSkeleton;
|
||||||
class Joint;
|
class Joint;
|
||||||
@ -53,30 +53,19 @@ class Label;
|
|||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
class CheckBox;
|
class CheckBox;
|
||||||
class VSeparator;
|
class VSeparator;
|
||||||
|
class EditorPropertyTransform;
|
||||||
|
class EditorPropertyVector3;
|
||||||
|
|
||||||
class ModuleBoneTransformEditor : public VBoxContainer {
|
class ModuleBoneTransformEditor : public VBoxContainer {
|
||||||
GDCLASS(ModuleBoneTransformEditor, VBoxContainer);
|
GDCLASS(ModuleBoneTransformEditor, VBoxContainer);
|
||||||
|
|
||||||
static const int32_t TRANSLATION_COMPONENTS = 3;
|
|
||||||
static const int32_t ROTATION_DEGREES_COMPONENTS = 3;
|
|
||||||
static const int32_t SCALE_COMPONENTS = 3;
|
|
||||||
static const int32_t BASIS_COMPONENTS = 9;
|
|
||||||
static const int32_t BASIS_SPLIT_COMPONENTS = 3;
|
|
||||||
static const int32_t TRANSFORM_COMPONENTS = 12;
|
|
||||||
static const int32_t TRANSFORM_SPLIT_COMPONENTS = 3;
|
|
||||||
static const int32_t TRANSFORM_CONTROL_COMPONENTS = 3;
|
|
||||||
|
|
||||||
EditorInspectorSection *section;
|
EditorInspectorSection *section;
|
||||||
|
|
||||||
GridContainer *translation_grid;
|
EditorPropertyVector3 *translation_property;
|
||||||
GridContainer *rotation_grid;
|
EditorPropertyVector3 *rotation_property;
|
||||||
GridContainer *scale_grid;
|
EditorPropertyVector3 *scale_property;
|
||||||
GridContainer *transform_grid;
|
EditorInspectorSection *transform_section;
|
||||||
|
EditorPropertyTransform *transform_property;
|
||||||
EditorSpinSlider *translation_slider[TRANSLATION_COMPONENTS];
|
|
||||||
EditorSpinSlider *rotation_slider[ROTATION_DEGREES_COMPONENTS];
|
|
||||||
EditorSpinSlider *scale_slider[SCALE_COMPONENTS];
|
|
||||||
EditorSpinSlider *transform_slider[TRANSFORM_COMPONENTS];
|
|
||||||
|
|
||||||
Rect2 background_rects[5];
|
Rect2 background_rects[5];
|
||||||
|
|
||||||
@ -95,11 +84,17 @@ class ModuleBoneTransformEditor : public VBoxContainer {
|
|||||||
String label;
|
String label;
|
||||||
|
|
||||||
void create_editors();
|
void create_editors();
|
||||||
void setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner);
|
|
||||||
|
|
||||||
void _value_changed(const double p_value, const bool p_from_transform);
|
// Called when one of the EditorSpinSliders are changed.
|
||||||
|
void _value_changed(const double p_value);
|
||||||
Transform compute_transform(const bool p_from_transform) const;
|
// Called when the one of the EditorPropertyVector3 are updated.
|
||||||
|
void _value_changed_vector3(const String &p_path, const Variant &p_value, const String &p_name = "", bool changing = false);
|
||||||
|
// Called when the transform_property is updated.
|
||||||
|
void _value_changed_transform(const String &p_path, const Variant &p_value, const String &p_name = "", bool changing = false);
|
||||||
|
// Changes the transform to the given transform and updates the UI accordingly.
|
||||||
|
void _change_transform(Transform p_new_transform);
|
||||||
|
// Creates a Transform using the EditorPropertyVector3 properties.
|
||||||
|
Transform compute_transform_from_vector3s() const;
|
||||||
|
|
||||||
void update_enabled_checkbox();
|
void update_enabled_checkbox();
|
||||||
|
|
||||||
@ -118,9 +113,6 @@ public:
|
|||||||
void _update_custom_pose_properties();
|
void _update_custom_pose_properties();
|
||||||
void _update_transform_properties(Transform p_transform);
|
void _update_transform_properties(Transform p_transform);
|
||||||
|
|
||||||
// Can/cannot modify the spinner values for the Transform
|
|
||||||
void set_read_only(const bool p_read_only);
|
|
||||||
|
|
||||||
// Transform can be keyed, whether or not to show the button
|
// Transform can be keyed, whether or not to show the button
|
||||||
void set_keyable(const bool p_keyable);
|
void set_keyable(const bool p_keyable);
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ bool Skeleton::_set(const StringName &p_path, const Variant &p_value) {
|
|||||||
set_bone_parent(which, p_value);
|
set_bone_parent(which, p_value);
|
||||||
} else if (what == "rest") {
|
} else if (what == "rest") {
|
||||||
set_bone_rest(which, p_value);
|
set_bone_rest(which, p_value);
|
||||||
|
|
||||||
} else if (what == "enabled") {
|
} else if (what == "enabled") {
|
||||||
set_bone_enabled(which, p_value);
|
set_bone_enabled(which, p_value);
|
||||||
} else if (what == "position") {
|
} else if (what == "position") {
|
||||||
@ -100,6 +101,8 @@ bool Skeleton::_set(const StringName &p_path, const Variant &p_value) {
|
|||||||
set_bone_pose_rotation(which, p_value);
|
set_bone_pose_rotation(which, p_value);
|
||||||
} else if (what == "scale") {
|
} else if (what == "scale") {
|
||||||
set_bone_pose_scale(which, p_value);
|
set_bone_pose_scale(which, p_value);
|
||||||
|
} else if (what == "pose") {
|
||||||
|
set_bone_pose(which, p_value);
|
||||||
} else if (what == "bound_children") {
|
} else if (what == "bound_children") {
|
||||||
Array children = p_value;
|
Array children = p_value;
|
||||||
|
|
||||||
@ -147,6 +150,8 @@ bool Skeleton::_get(const StringName &p_path, Variant &r_ret) const {
|
|||||||
r_ret = get_bone_pose_rotation(which);
|
r_ret = get_bone_pose_rotation(which);
|
||||||
} else if (what == "scale") {
|
} else if (what == "scale") {
|
||||||
r_ret = get_bone_pose_scale(which);
|
r_ret = get_bone_pose_scale(which);
|
||||||
|
} else if (what == "pose") {
|
||||||
|
r_ret = get_bone_pose(which);
|
||||||
} else if (what == "bound_children") {
|
} else if (what == "bound_children") {
|
||||||
Array children;
|
Array children;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user