blockbench/js/outliner/group.js

648 lines
17 KiB
JavaScript

class Group extends OutlinerNode {
constructor(data, uuid) {
super(uuid)
for (var key in Group.properties) {
Group.properties[key].reset(this);
}
this.name = Format.bone_rig ? 'bone' : 'group'
this.children = []
this.reset = false;
this.shade = true;
this.selected = false;
this.locked = false;
this.visibility = true;
this.export = true;
this.autouv = 0;
this.parent = 'root';
this.isOpen = false;
if (typeof data === 'object') {
this.extend(data)
} else if (typeof data === 'string') {
this.name = data
}
}
extend(object) {
for (var key in Group.properties) {
Group.properties[key].merge(this, object)
}
Merge.string(this, object, 'name')
this.sanitizeName();
Merge.boolean(this, object, 'shade')
Merge.boolean(this, object, 'mirror_uv')
Merge.boolean(this, object, 'reset')
/*
if (object.origin) {
Merge.number(this.origin, object.origin, 0)
Merge.number(this.origin, object.origin, 1)
Merge.number(this.origin, object.origin, 2)
}
if (object.rotation) {
Merge.number(this.rotation, object.rotation, 0)
Merge.number(this.rotation, object.rotation, 1)
Merge.number(this.rotation, object.rotation, 2)
}*/
Merge.number(this, object, 'autouv')
Merge.boolean(this, object, 'export')
Merge.boolean(this, object, 'locked')
Merge.boolean(this, object, 'visibility')
return this;
}
getMesh() {
return this.mesh;
}
get mesh() {
var bone = Canvas.bones[this.uuid]
if (!bone) {
bone = new THREE.Object3D()
bone.name = this.name
bone.isGroup = true
Canvas.bones[this.uuid] = bone
}
return bone;
}
init() {
super.init();
if (typeof this.parent !== 'object') {
this.addTo();
}
Canvas.updateAllBones([this]);
return this;
}
select(event) {
var scope = this;
if (Blockbench.hasFlag('renaming') || this.locked) return this;
if (!event) event = true
var allSelected = Group.selected === this && selected.length && this.matchesSelection()
//Clear Old Group
if (Group.selected) Group.selected.unselect()
if (event.shiftKey !== true && event.ctrlOrCmd !== true) {
selected.length = 0
}
//Select This Group
Group.all.forEach(function(s) {
s.selected = false
})
this.selected = true
Group.selected = this;
//Select / Unselect Children
if (allSelected && event.which === 1) {
//Select Only Group, unselect Children
selected.length = 0
} else {
scope.children.forEach(function(s) {
s.selectLow()
})
}
if (Animator.open) {
if (Animation.selected) {
Animation.selected.getBoneAnimator().select(true)
}
}
updateSelection()
return this;
}
selectChildren(event) {
var scope = this;
if (Blockbench.hasFlag('renaming')) return;
if (!event) event = {shiftKey: false}
var firstChildSelected = false
//Clear Old Group
if (Group.selected) Group.selected.unselect()
selected.length = 0
//Select This Group
Group.all.forEach(function(s) {
s.selected = false
})
this.selected = true
Group.selected = this
scope.children.forEach(function(s) {
s.selectLow()
})
updateSelection()
return this;
}
selectLow(highlight) {
//Group.selected = this;
//Only Select
if (highlight !== false) {
this.selected = true
}
this.children.forEach(function(s) {
s.selectLow(highlight)
})
TickUpdates.selection = true;
return this;
}
unselect() {
if (this.selected === false) return;
if (Animator.open && Animation.selected) {
var ba = Animation.selected.animators[this.uuid];
if (ba) {
ba.selected = false
}
}
Group.selected = undefined;
this.selected = false
TickUpdates.selection = true;
return this;
}
matchesSelection() {
var scope = this;
var match = true;
for (var i = 0; i < selected.length; i++) {
if (!selected[i].isChildOf(scope, 20)) {
return false
}
}
this.forEachChild(obj => {
if (!obj.selected) {
match = false
}
})
return match;
}
openUp() {
this.isOpen = true
this.updateElement()
if (this.parent && this.parent !== 'root') {
this.parent.openUp()
}
return this;
}
remove(undo) {
var scope = this;
if (undo) {
let elements = []
this.forEachChild(function(element) {
if (element.type !== 'group') {
elements.push(element)
}
})
Undo.initEdit({elements: elements, outliner: true, selection: true})
}
this.unselect()
var i = this.children.length-1
while (i >= 0) {
this.children[i].remove(false)
i--;
}
if (typeof this.parent === 'object') {
this.parent.children.remove(this)
} else {
Outliner.root.remove(this)
}
Animator.animations.forEach(animation => {
if (animation.animators && animation.animators[scope.uuid]) {
delete animation.animators[scope.uuid];
}
if (animation.selected && Animator.open) {
updateKeyframeSelection();
}
})
TickUpdates.selection = true
this.constructor.all.remove(this);
delete Canvas.bones[this.uuid];
delete OutlinerNode.uuids[this.uuid];
if (undo) {
Undo.finishEdit('Delete group')
}
}
resolve() {
var array = this.children.slice();
var index = this.getParentArray().indexOf(this)
let all_elements = [];
this.forEachChild(obj => {
if (obj instanceof Group == false) {
all_elements.push(obj);
}
})
Undo.initEdit({outliner: true, elements: all_elements})
array.forEach((obj, i) => {
obj.addTo(this.parent, index)
if ((obj instanceof Cube && Format.rotate_cubes) || (obj instanceof OutlinerElement && obj.rotatable) || (obj instanceof Group && Format.bone_rig)) {
let quat = new THREE.Quaternion().copy(obj.mesh.quaternion);
quat.premultiply(obj.mesh.parent.quaternion);
let e = new THREE.Euler().setFromQuaternion(quat, obj.mesh.rotation.order);
obj.extend({
rotation: [
Math.roundTo(Math.radToDeg(e.x), 4),
Math.roundTo(Math.radToDeg(e.y), 4),
Math.roundTo(Math.radToDeg(e.z), 4),
]
})
}
if (obj.mesh) {
let pos = new THREE.Vector3().copy(obj.mesh.position);
pos.applyQuaternion(this.mesh.quaternion).sub(obj.mesh.position);
let diff = pos.toArray();
if (obj.movable) obj.from.V3_add(diff);
if (obj.resizable) obj.to.V3_add(diff);
if (obj.rotatable || obj instanceof Group) obj.origin.V3_add(diff);
if (obj instanceof Group) {
obj.forEachChild(child => {
if (child.movable) child.from.V3_add(diff);
if (child.resizable) child.to.V3_add(diff);
if (child.rotatable || child instanceof Group) child.origin.V3_add(diff);
})
}
}
})
Canvas.updateAllPositions();
if (Format.bone_rig) {
Canvas.updateAllBones();
}
this.remove(false);
Undo.finishEdit('Resolve group')
return array;
}
showContextMenu(event) {
Prop.active_panel = 'outliner'
if (this.locked) return this;
if (Group.selected != this) this.select(event);
this.menu.open(event, this)
return this;
}
transferOrigin(origin) {
if (!this.mesh) return;
var q = new THREE.Quaternion().copy(this.mesh.quaternion)
var shift = new THREE.Vector3(
this.origin[0] - origin[0],
this.origin[1] - origin[1],
this.origin[2] - origin[2],
)
var dq = new THREE.Vector3().copy(shift)
dq.applyQuaternion(q)
shift.sub(dq)
shift.applyQuaternion(q.invert())
this.origin.V3_set(origin);
function iterateChild(obj) {
if (obj instanceof Group) {
obj.origin.V3_add(shift);
obj.children.forEach(child => iterateChild(child));
} else {
if (obj.movable) {
obj.from.V3_add(shift);
}
if (obj.resizable) {
obj.to.V3_add(shift);
}
if (obj.rotatable) {
obj.origin.V3_add(shift);
}
}
}
this.children.forEach(child => iterateChild(child));
Canvas.updatePositions()
return this;
}
sortContent() {
Undo.initEdit({outliner: true})
if (this.children.length < 1) return;
this.children.sort(function(a,b) {
return sort_collator.compare(a.name, b.name)
});
Undo.finishEdit('Sort group content')
return this;
}
duplicate() {
var copied_groups = [];
var copy = this.getChildlessCopy(false)
delete copy.parent;
copied_groups.push(copy)
copy.sortInBefore(this, 1).init()
if (Format.bone_rig) {
copy.createUniqueName()
}
for (var child of this.children) {
child.duplicate().addTo(copy)
}
copy.isOpen = true;
Canvas.updatePositions();
return copy;
}
getSaveCopy() {
var base_group = this.getChildlessCopy(true);
for (var child of this.children) {
base_group.children.push(child.getSaveCopy());
}
delete base_group.parent;
return base_group;
}
getChildlessCopy(keep_uuid) {
var base_group = new Group({name: this.name}, keep_uuid ? this.uuid : null);
for (var key in Group.properties) {
Group.properties[key].copy(this, base_group)
}
base_group.name = this.name;
base_group.origin.V3_set(this.origin);
base_group.rotation.V3_set(this.rotation);
base_group.shade = this.shade;
base_group.reset = this.reset;
base_group.locked = this.locked;
base_group.visibility = this.visibility;
base_group.export = this.export;
base_group.autouv = this.autouv;
base_group.isOpen = this.isOpen;
return base_group;
}
compile(undo) {
var obj = {
name: this.name
}
for (var key in Group.properties) {
Group.properties[key].copy(this, obj)
}
if (this.shade == false) {
obj.shade = false
}
if (undo) {
obj.uuid = this.uuid;
obj.export = this.export;
obj.isOpen = this.isOpen === true;
obj.locked = this.locked;
obj.visibility = this.visibility;
obj.autouv = this.autouv;
}
if (this.rotation.allEqual(0)) {
delete obj.rotation;
}
if (this.reset) {
obj.reset = true
}
obj.children = []
return obj;
}
forEachChild(cb, type, forSelf) {
var i = 0
if (forSelf) {
cb(this)
}
while (i < this.children.length) {
if (!type || this.children[i] instanceof type) {
cb(this.children[i])
}
if (this.children[i].type === 'group') {
this.children[i].forEachChild(cb, type)
}
i++;
}
}
setAutoUV(val) {
this.forEachChild(function(s) {
s.autouv = val;
s.updateElement()
})
this.autouv = val;
this.updateElement()
}
}
Group.prototype.title = tl('data.group');
Group.prototype.type = 'group';
Group.prototype.icon = 'fa fa-folder';
Group.prototype.isParent = true;
Group.prototype.name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false;
Group.prototype.buttons = [
Outliner.buttons.autouv,
Outliner.buttons.shade,
Outliner.buttons.export,
Outliner.buttons.locked,
Outliner.buttons.visibility,
];
Group.prototype.needsUniqueName = () => Format.bone_rig;
function setGroupColor(color) {
Undo.initEdit({outliner: true, elements: Cube.selected, selection: true})
Group.all.forEach(group => {
if (group.selected) {
group.color = color;
}
})
Cube.selected.forEach(cube => {
cube.setColor(color);
})
Undo.finishEdit('Change group marker color')
}
Group.prototype.menu = new Menu([
'copy',
'paste',
'duplicate',
'_',
'add_locator',
'_',
'rename',
'edit_bedrock_binding',
{name: 'menu.cube.color', icon: 'color_lens', children: [
{icon: 'bubble_chart', color: markerColors[0].standard, name: 'cube.color.'+markerColors[0].name, click: () => setGroupColor(0)},
{icon: 'bubble_chart', color: markerColors[1].standard, name: 'cube.color.'+markerColors[1].name, click: () => setGroupColor(1)},
{icon: 'bubble_chart', color: markerColors[2].standard, name: 'cube.color.'+markerColors[2].name, click: () => setGroupColor(2)},
{icon: 'bubble_chart', color: markerColors[3].standard, name: 'cube.color.'+markerColors[3].name, click: () => setGroupColor(3)},
{icon: 'bubble_chart', color: markerColors[4].standard, name: 'cube.color.'+markerColors[4].name, click: () => setGroupColor(4)},
{icon: 'bubble_chart', color: markerColors[5].standard, name: 'cube.color.'+markerColors[5].name, click: () => setGroupColor(5)},
{icon: 'bubble_chart', color: markerColors[6].standard, name: 'cube.color.'+markerColors[6].name, click: () => setGroupColor(6)},
{icon: 'bubble_chart', color: markerColors[7].standard, name: 'cube.color.'+markerColors[7].name, click: () => setGroupColor(7)}
]},
{icon: 'sort_by_alpha', name: 'menu.group.sort', condition: {modes: ['edit']}, click: function(group) {group.sortContent()}},
{icon: 'fa-leaf', name: 'menu.group.resolve', condition: {modes: ['edit']}, click: function(group) {group.resolve()}},
'delete'
]);
Group.selected;
Group.all = [];
new Property(Group, 'vector', 'origin', {default() {
return Format.centered_grid ? [0, 0, 0] : [8, 8, 8]
}});
new Property(Group, 'vector', 'rotation');
new Property(Group, 'string', 'bedrock_binding', {condition: () => Format.id == 'bedrock'});
new Property(Group, 'array', 'cem_animations', {condition: () => Format.id == 'optifine_entity'});
new Property(Group, 'number', 'color');
function getCurrentGroup() {
if (Group.selected) {
return Group.selected
} else if (selected.length) {
var g1 = selected[0].parent;
if (g1 instanceof Group) {
for (var obj of selected) {
if (obj.parent !== g1) {
return;
}
}
return g1;
}
}
}
function getAllGroups() {
var ta = []
function iterate(array) {
for (var obj of array) {
if (obj instanceof Group) {
ta.push(obj)
iterate(obj.children)
}
}
}
iterate(Outliner.root)
return ta;
}
window.__defineGetter__('selected_group', () => {
console.warn('selected_group is deprecated. Please use Group.selected instead.')
return Group.selected
})
BARS.defineActions(function() {
new Action({
id: 'add_group',
icon: 'create_new_folder',
category: 'edit',
condition: () => Modes.edit,
keybind: new Keybind({key: 'g', ctrl: true}),
click: function () {
Undo.initEdit({outliner: true});
var add_group = Group.selected
if (!add_group && selected.length) {
add_group = selected.last()
}
var base_group = new Group({
origin: add_group ? add_group.origin : undefined
})
base_group.addTo(add_group)
base_group.isOpen = true
if (Format.bone_rig) {
base_group.createUniqueName()
}
if (add_group instanceof OutlinerElement && selected.length > 1) {
selected.forEach(function(s, i) {
s.addTo(base_group)
})
}
base_group.init().select()
Undo.finishEdit('Add group');
Vue.nextTick(function() {
updateSelection()
if (settings.create_rename.value) {
base_group.rename()
}
base_group.showInOutliner()
Blockbench.dispatchEvent( 'add_group', {object: base_group} )
})
}
})
new Action({
id: 'group_elements',
icon: 'drive_file_move',
category: 'edit',
condition: () => Modes.edit && (selected.length || Group.selected),
keybind: new Keybind({key: 'g', ctrl: true, shift: true}),
click: function () {
Undo.initEdit({outliner: true});
var add_group = Group.selected
if (!add_group && selected.length) {
add_group = selected.last()
}
var base_group = new Group({
origin: add_group ? add_group.origin : undefined
})
base_group.sortInBefore(add_group);
base_group.isOpen = true
if (Format.bone_rig) {
base_group.createUniqueName()
}
if (add_group instanceof Group) {
add_group.addTo(base_group);
} else if (add_group instanceof OutlinerElement) {
selected.forEach(function(s, i) {
s.addTo(base_group);
})
}
base_group.init().select()
Undo.finishEdit('Add group');
Vue.nextTick(function() {
updateSelection()
if (settings.create_rename.value) {
base_group.rename()
}
base_group.showInOutliner()
Blockbench.dispatchEvent( 'group_elements', {object: base_group} )
})
}
})
new Action({
id: 'collapse_groups',
icon: 'format_indent_decrease',
category: 'edit',
condition: () => Group.all.length > 0,
click: function() {
Group.all.forEach(function(g) {
g.isOpen = false;
})
}
})
new Action({
id: 'unfold_groups',
icon: 'format_indent_increase',
category: 'edit',
condition: () => Group.all.length > 0,
click: function() {
Group.all.forEach(function(g) {
g.isOpen = true;
})
}
})
new Action({
id: 'edit_bedrock_binding',
icon: 'fa-paperclip',
category: 'edit',
condition: () => Format.id == 'bedrock' && Group.selected,
click: function() {
let dialog = new Dialog({
id: 'edit_bedrock_binding',
title: 'action.edit_bedrock_binding',
component: {
components: {VuePrismEditor},
data: {
binding: Group.selected.bedrock_binding,
},
template:
`<div class="dialog_bar">
<vue-prism-editor class="molang_input dark_bordered" v-model="binding" language="molang" :line-numbers="false" />
</div>`
},
onConfirm: form_data => {
dialog.hide().delete();
let value = dialog.component.data.binding.replace(/\n/g, '');
if (
value != Group.selected.bedrock_binding
) {
Undo.initEdit({group: Group.selected});
Group.selected.bedrock_binding = value;
Undo.finishEdit('Edit group binding');
}
},
onCancel() {
dialog.hide().delete();
}
}).show();
}
})
})