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; } init() { super.init(); Project.groups.push(this); if (typeof this.parent !== 'object') { this.addTo(); } if (!this.mesh || !this.mesh.parent) { this.constructor.preview_controller.setup(this); } 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 || Pressing.overrides.shift) !== true && (event.ctrlOrCmd || Pressing.overrides.ctrl) !== 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() super.remove(); var i = this.children.length-1 while (i >= 0) { this.children[i].remove(false) i--; } 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 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' ]); Object.defineProperty(Group, 'all', { get() { return Project.groups || []; }, set(arr) { Project.groups.replace(arr); } }) Object.defineProperty(Group, 'selected', { get() { return Project.selected_group }, set(group) { Project.selected_group = group; } }) 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'); new NodePreviewController(Group, { setup(group) { bone = new THREE.Object3D(); bone.name = group.name; bone.isGroup = true; Project.nodes_3d[group.uuid] = bone; }, updateTransform(group) { Canvas.updateAllBones([group]); } }) 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: `
` }, 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(); } }) })