//Actions function origin2geometry() { if (Format.bone_rig && Group.selected) { Undo.initEdit({group: Group.selected}) if (!Group.selected || Group.selected.children.length === 0) return; var position = new THREE.Vector3(); let amount = 0; Group.selected.children.forEach(function(obj) { if (obj.getWorldCenter) { position.add(obj.getWorldCenter()); amount++; } }) position.divideScalar(amount); Group.selected.mesh.parent.worldToLocal(position); if (Group.selected.parent instanceof Group) { position.x += Group.selected.parent.origin[0]; position.y += Group.selected.parent.origin[1]; position.z += Group.selected.parent.origin[2]; } Group.selected.transferOrigin(position.toArray()); } else if (Outliner.selected[0]) { Undo.initEdit({elements: Outliner.selected}) var center = getSelectionCenter(); var original_center = center.slice(); Outliner.selected.forEach(element => { if (!element.transferOrigin) return; if (Format.bone_rig && element.parent instanceof Group) { var v = new THREE.Vector3().fromArray(original_center); element.parent.mesh.worldToLocal(v); v.x += element.parent.origin[0]; v.y += element.parent.origin[1]; v.z += element.parent.origin[2]; center = v.toArray(); element.transferOrigin(center) } else { element.transferOrigin(original_center) } }) } Canvas.updateView({ elements: Outliner.selected, element_aspects: {transform: true, geometry: true}, groups: Group.selected && [Group.selected], selection: true }); Undo.finishEdit('Center pivot') } function getSelectionCenter(all = false) { if (Group.selected && selected.length == 0 && !all) { let vec = THREE.fastWorldPosition(Group.selected.mesh, new THREE.Vector3()); return vec.toArray(); } var center = [0, 0, 0] var i = 0; let items = (selected.length == 0 || all) ? elements : selected; items.forEach(obj => { if (obj.getWorldCenter) { var pos = obj.getWorldCenter(); center[0] += pos.x center[1] += pos.y center[2] += pos.z } }) if (items.length) { for (var i = 0; i < 3; i++) { center[i] = center[i] / items.length } } if (!Format.centered_grid) { center.V3_add(8, 8, 8) } return center; } function limitToBox(val, inflate) { if (typeof inflate != 'number') inflate = 0; if (!(Format.canvas_limit && !settings.deactivate_size_limit.value)) { return val; } else if (val + inflate > 32) { return 32 - inflate; } else if (val - inflate < -16) { return -16 + inflate; } else { return val; } } //Movement function moveElementsRelative(difference, index, event) { //Multiple if (!quad_previews.current || !Outliner.selected.length) { return; } var _has_groups = Format.bone_rig && Group.selected && Group.selected.matchesSelection() && Toolbox.selected.transformerMode == 'translate'; Undo.initEdit({elements: Outliner.selected, outliner: _has_groups}) var axes = [] // < > // PageUpDown // ^ v var facing = quad_previews.current.getFacingDirection() var height = quad_previews.current.getFacingHeight() switch (facing) { case 'north': axes = [0, 2, 1]; break; case 'south': axes = [0, 2, 1]; break; case 'west': axes = [2, 0, 1]; break; case 'east': axes = [2, 0, 1]; break; } if (height !== 'middle') { if (index === 1) { index = 2 } else if (index === 2) { index = 1 } } if (facing === 'south' && (index === 0 || index === 1)) difference *= -1 if (facing === 'west' && index === 0) difference *= -1 if (facing === 'east' && index === 1) difference *= -1 if (index === 2 && height !== 'down') difference *= -1 if (index === 1 && height === 'up') difference *= -1 if (event) { difference *= canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl); } moveElementsInSpace(difference, axes[index]); updateSelection(); Undo.finishEdit('Move elements') } //Rotate function rotateSelected(axis, steps) { let affected = [...Cube.selected, ...Mesh.selected]; if (!affected.length) return; Undo.initEdit({elements: affected}); if (!steps) steps = 1 var origin = [8, 8, 8] if (Group.selected && Format.bone_rig) { origin = Group.selected.origin.slice() } else if (Format.centered_grid) { origin = [0, 0, 0] } else { origin = affected[0].origin.slice() } affected.forEach(function(obj) { obj.roll(axis, steps, origin) }) updateSelection(); Undo.finishEdit('Rotate elements') } //Mirror function mirrorSelected(axis) { if (Modes.animate && Timeline.selected.length) { Undo.initEdit({keyframes: Timeline.selected}) for (var kf of Timeline.selected) { kf.flip(axis) } Undo.finishEdit('Flipped keyframes'); updateKeyframeSelection(); Animator.preview(); } else if (Modes.edit && selected.length) { Undo.initEdit({elements: selected, outliner: Format.bone_rig}) var center = Format.centered_grid ? 0 : 8; if (Format.bone_rig) { if (Group.selected && Group.selected.matchesSelection()) { function flipGroup(group) { if (group.type === 'group') { for (var i = 0; i < 3; i++) { if (i === axis) { group.origin[i] *= -1 } else { group.rotation[i] *= -1 } } if (axis == 0 && group.name.includes('right')) { let name = group.name.replace(/right/g, 'left').replace(/2/, ''); if (!Group.all.find(g => g.name == name)) group.name = name; } else if (axis == 0 && group.name.includes('left')) { let name = group.name.replace(/left/g, 'right').replace(/2/, ''); if (!Group.all.find(g => g.name == name)) group.name = name; } } Canvas.updateAllBones([group]); } flipGroup(Group.selected) Group.selected.forEachChild(flipGroup) } } selected.forEach(function(obj) { obj.flip(axis, center, false) if (Project.box_uv && obj instanceof Cube && axis === 0) { obj.shade = !obj.shade Canvas.updateUV(obj) } }) updateSelection() Undo.finishEdit('Flip selection') } } const Vertexsnap = { step1: true, vertex_gizmos: new THREE.Object3D(), line: new THREE.Line(new THREE.BufferGeometry(), Canvas.outlineMaterial), elements_with_vertex_gizmos: [], hovering: false, addVertices: function(element) { if (Vertexsnap.elements_with_vertex_gizmos.includes(element)) return; if (element.visibility === false) return; let {mesh} = element; $('#preview').get(0).removeEventListener("mousemove", Vertexsnap.hoverCanvas) $('#preview').get(0).addEventListener("mousemove", Vertexsnap.hoverCanvas) if (!mesh.vertex_points) { mesh.updateMatrixWorld() let vectors = []; let positions = mesh.geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { let vec = [positions[i], positions[i+1], positions[i+2]]; if (!vectors.find(vec2 => vec.equals(vec2))) { vectors.push(vec); } } vectors.push([0, 0, 0]); let points = new THREE.Points(new THREE.BufferGeometry(), new THREE.PointsMaterial().copy(Canvas.meshVertexMaterial)); points.vertices = vectors; let vector_positions = []; vectors.forEach(vector => vector_positions.push(...vector)); let vector_colors = []; vectors.forEach(vector => vector_colors.push(gizmo_colors.grid.r, gizmo_colors.grid.g, gizmo_colors.grid.b)); points.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vector_positions), 3)); points.geometry.setAttribute('color', new THREE.Float32BufferAttribute(new Float32Array(vector_colors), 3)); points.material.transparent = true; mesh.vertex_points = points; mesh.outline.add(points); } mesh.vertex_points.visible = true; mesh.vertex_points.renderOrder = 900; Vertexsnap.elements_with_vertex_gizmos.push(element) }, clearVertexGizmos: function() { Project.model_3d.remove(Vertexsnap.line); Vertexsnap.elements_with_vertex_gizmos.forEach(element => { if (element.mesh && element.mesh.vertex_points) { element.mesh.vertex_points.visible = false; if (element instanceof Mesh == false) { element.mesh.vertex_points.parent.remove(element.mesh.vertex_points); delete element.mesh.vertex_points; } } }) Vertexsnap.elements_with_vertex_gizmos.empty(); $('#preview').get(0).removeEventListener("mousemove", Vertexsnap.hoverCanvas) }, hoverCanvas: function(event) { let data = Canvas.raycast(event) if (Vertexsnap.hovering) { Project.model_3d.remove(Vertexsnap.line); Vertexsnap.elements_with_vertex_gizmos.forEach(el => { let points = el.mesh.vertex_points; let colors = []; for (let i = 0; i < points.geometry.attributes.position.count; i++) { let color; if (data && data.element == el && data.type == 'vertex' && data.vertex_index == i) { color = gizmo_colors.outline; } else { color = gizmo_colors.grid; } colors.push(color.r, color.g, color.b); } points.material.depthTest = !(data.element == el); points.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); }) } if (!data || data.type !== 'vertex') { Blockbench.setStatusBarText() return; } Vertexsnap.hovering = true if (Vertexsnap.step1 === false) { let {line} = Vertexsnap; let {geometry} = line; let vertex_pos = Vertexsnap.getGlobalVertexPos(data.element, data.vertex); geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([...Vertexsnap.vertex_pos.toArray(), ...vertex_pos.toArray()]), 3)); line.renderOrder = 900 Project.model_3d.add(Vertexsnap.line); Vertexsnap.line.position.copy(scene.position).multiplyScalar(-1); //Measure var diff = new THREE.Vector3().copy(Vertexsnap.vertex_pos); diff.sub(vertex_pos); Blockbench.setStatusBarText(tl('status_bar.vertex_distance', [trimFloatNumber(diff.length())] )); } }, select: function() { Vertexsnap.clearVertexGizmos() Outliner.selected.forEach(function(element) { Vertexsnap.addVertices(element) }) if (Outliner.selected.length) { $('#preview').css('cursor', (Vertexsnap.step1 ? 'copy' : 'alias')) } }, canvasClick: function(data) { if (!data || data.type !== 'vertex') return; if (Vertexsnap.step1) { Vertexsnap.step1 = false Vertexsnap.vertex_pos = Vertexsnap.getGlobalVertexPos(data.element, data.vertex); Vertexsnap.vertex_index = data.vertex_index; Vertexsnap.move_origin = typeof data.vertex !== 'string' && data.vertex.allEqual(0); Vertexsnap.elements = Outliner.selected.slice(); Vertexsnap.selected_vertices = JSON.parse(JSON.stringify(Project.selected_vertices)); Vertexsnap.clearVertexGizmos() $('#preview').css('cursor', (Vertexsnap.step1 ? 'copy' : 'alias')) } else { Vertexsnap.snap(data) $('#preview').css('cursor', (Vertexsnap.step1 ? 'copy' : 'alias')) } Blockbench.setStatusBarText() }, getGlobalVertexPos(element, vertex) { let vector = new THREE.Vector3(); vector.fromArray(vertex instanceof Array ? vertex : element.vertices[vertex]); element.mesh.localToWorld(vector); return vector; }, snap: function(data) { Undo.initEdit({elements: Vertexsnap.elements}) let mode = BarItems.vertex_snap_mode.get() if (Vertexsnap.move_origin) { Vertexsnap.elements.forEach(function(element) { let vec = Vertexsnap.getGlobalVertexPos(data.element, data.vertex); if (Format.bone_rig && element.parent instanceof Group && element.mesh.parent) { element.mesh.parent.worldToLocal(vec); } let vec_array = vec.toArray() vec_array.V3_add(element.parent.origin); element.transferOrigin(vec_array) }) } else { var global_delta = Vertexsnap.getGlobalVertexPos(data.element, data.vertex); global_delta.sub(Vertexsnap.vertex_pos) if (mode === 'scale' && !Format.integer_size && Vertexsnap.elements[0] instanceof Cube) { //Scale var m; switch (Vertexsnap.vertex_index) { case 0: m=[ 1,1,1 ]; break; case 1: m=[ 1,1,0 ]; break; case 2: m=[ 1,0,1 ]; break; case 3: m=[ 1,0,0 ]; break; case 4: m=[ 0,1,0 ]; break; case 5: m=[ 0,1,1 ]; break; case 6: m=[ 0,0,0 ]; break; case 7: m=[ 0,0,1 ]; break; } Vertexsnap.elements.forEach(function(obj) { if (obj instanceof Cube == false) return; var q = obj.mesh.getWorldQuaternion(new THREE.Quaternion()).invert() var cube_pos = new THREE.Vector3().copy(global_delta).applyQuaternion(q) for (i=0; i<3; i++) { if (m[i] === 1) { obj.to[i] = limitToBox(obj.to[i] + cube_pos.getComponent(i), obj.inflate) } else { obj.from[i] = limitToBox(obj.from[i] + cube_pos.getComponent(i), -obj.inflate) } } if (Project.box_uv && obj.visibility) { Canvas.updateUV(obj) } }) } else if (mode === 'move') { Vertexsnap.elements.forEach(function(obj) { var cube_pos = new THREE.Vector3().copy(global_delta) if (obj instanceof Mesh && Vertexsnap.selected_vertices && Vertexsnap.selected_vertices[obj.uuid]) { let vertices = Vertexsnap.selected_vertices[obj.uuid]; var q = obj.mesh.getWorldQuaternion(Reusable.quat1).invert(); cube_pos.applyQuaternion(q); let cube_pos_array = cube_pos.toArray(); vertices.forEach(vkey => { if (obj.vertices[vkey]) obj.vertices[vkey].V3_add(cube_pos_array); }) } else { if (Format.bone_rig && obj.parent instanceof Group) { var q = obj.mesh.parent.getWorldQuaternion(Reusable.quat1).invert(); cube_pos.applyQuaternion(q); } if (obj instanceof Cube && Format.rotate_cubes) { obj.origin.V3_add(cube_pos); } var in_box = obj.moveVector(cube_pos.toArray()); if (!in_box && Format.canvas_limit && !settings.deactivate_size_limit.value) { Blockbench.showMessageBox({translateKey: 'canvas_limit_error'}) } } }) } } Vertexsnap.clearVertexGizmos() Canvas.updateAllPositions() Undo.finishEdit('Use vertex snap') Vertexsnap.step1 = true } } //Scale function getScaleAllGroups() { let groups = []; if (!Format.bone_rig) return groups; if (Group.selected) { Group.selected.forEachChild((g) => { groups.push(g); }, Group) } else if (Outliner.selected.length == Outliner.elements.length && Group.all.length) { groups = Group.all; } return groups; } function scaleAll(save, size) { if (save === true) { hideDialog() } if (size === undefined) { size = $('#model_scale_label').val() } var origin = [ parseFloat($('#scaling_origin_x').val())||0, parseFloat($('#scaling_origin_y').val())||0, parseFloat($('#scaling_origin_z').val())||0, ] var overflow = []; Outliner.selected.forEach(function(obj) { obj.autouv = 0; origin.forEach(function(ogn, i) { if ($('#model_scale_'+getAxisLetter(i)+'_axis').is(':checked')) { if (obj.from) { obj.from[i] = (obj.before.from[i] - obj.inflate - ogn) * size; if (obj.from[i] + ogn > 32 || obj.from[i] + ogn < -16) overflow.push(obj); obj.from[i] = limitToBox(obj.from[i] + obj.inflate + ogn, -obj.inflate); } if (obj.to) { obj.to[i] = (obj.before.to[i] + obj.inflate - ogn) * size; if (obj.to[i] + ogn > 32 || obj.to[i] + ogn < -16) overflow.push(obj); obj.to[i] = limitToBox(obj.to[i] - obj.inflate + ogn, obj.inflate); if (Format.integer_size) { obj.to[i] = obj.from[i] + Math.round(obj.to[i] - obj.from[i]) } } if (obj.origin) { obj.origin[i] = (obj.before.origin[i] - ogn) * size; obj.origin[i] = obj.origin[i] + ogn; } if (obj instanceof Mesh) { for (let key in obj.vertices) { obj.vertices[key][i] = (obj.before.vertices[key][i] - ogn) * size + ogn; } } } else { if (obj.from) obj.from[i] = obj.before.from[i]; if (obj.to) obj.to[i] = obj.before.to[i]; if (obj.origin) obj.origin[i] = obj.before.origin[i]; if (obj instanceof Mesh) { for (let key in obj.vertices) { obj.vertices[key][i] = obj.before.vertices[key][i]; } } } }) if (save === true) { delete obj.before } if (Project.box_uv) { Canvas.updateUV(obj) } }) getScaleAllGroups().forEach((g) => { g.origin[0] = g.old_origin[0] * size g.origin[1] = g.old_origin[1] * size g.origin[2] = g.old_origin[2] * size if (save === true) { delete g.old_origin } }, Group) if (overflow.length && Format.canvas_limit && !settings.deactivate_size_limit.value) { scaleAll.overflow = overflow; $('#scaling_clipping_warning').text('Model clipping: Your model is too large for the canvas') $('#scale_overflow_btn').css('display', 'inline-block') } else { $('#scaling_clipping_warning').text('') $('#scale_overflow_btn').hide() } Canvas.updateView({ elements: Outliner.selected, element_aspects: {geometry: true, transform: true}, groups: getScaleAllGroups(), group_aspects: {transform: true}, }) if (save === true) { Undo.finishEdit('Scale model') } } function modelScaleSync(label) { if (label) { var size = $('#model_scale_label').val() $('#model_scale_range').val(size) } else { var size = $('#model_scale_range').val() $('#model_scale_label').val(size) } scaleAll(false, size) } function cancelScaleAll() { Outliner.selected.forEach(function(obj) { if (obj === undefined) return; if (obj.from) obj.from.V3_set(obj.before.from); if (obj.to) obj.to.V3_set(obj.before.to); if (obj.origin) obj.origin.V3_set(obj.before.origin); if (obj instanceof Mesh) { for (let key in obj.vertices) { obj.vertices[key].V3_set(obj.before.vertices[key]); } } delete obj.before if (Project.box_uv) { Canvas.updateUV(obj) } }) getScaleAllGroups().forEach((g) => { g.origin[0] = g.old_origin[0] g.origin[1] = g.old_origin[1] g.origin[2] = g.old_origin[2] delete g.old_origin }, Group) Canvas.updateView({ elements: Outliner.selected, element_aspects: {geometry: true, transform: true}, groups: getScaleAllGroups(), group_aspects: {transform: true}, }) hideDialog() } function setScaleAllPivot(mode) { if (mode === 'selection') { var center = getSelectionCenter() } else { var center = Cube.selected[0] && Cube.selected[0].origin; } if (center) { $('input#scaling_origin_x').val(center[0]); $('input#scaling_origin_y').val(center[1]); $('input#scaling_origin_z').val(center[2]); } } function scaleAllSelectOverflow() { cancelScaleAll() selected.empty(); scaleAll.overflow.forEach(obj => { obj.selectLow() }) updateSelection(); } //Center function centerElementsAll(axis) { centerElements(0, false) centerElements(1, false) centerElements(2, false) Canvas.updatePositions() } function centerElements(axis, update) { if (!Outliner.selected.length) return; let center = getSelectionCenter()[axis]; var difference = (Format.centered_grid ? 0 : 8) - center Outliner.selected.forEach(function(obj) { if (obj.movable) obj.origin[axis] += difference; if (obj.to) obj.to[axis] = limitToBox(obj.to[axis] + difference, obj.inflate); if (obj instanceof Cube) obj.from[axis] = limitToBox(obj.from[axis] + difference, obj.inflate); }) if (update !== false) { Canvas.updatePositions() } } //Move function moveElementsInSpace(difference, axis) { let space = Transformer.getTransformSpace() let group = Format.bone_rig && Group.selected && Group.selected.matchesSelection() && Group.selected; var group_m; let quaternion = new THREE.Quaternion(); let vector = new THREE.Vector3(); if (group) { if (space === 0) { group_m = vector.set(0, 0, 0); group_m[getAxisLetter(axis)] = difference; var rotation = new THREE.Quaternion(); group.mesh.parent.getWorldQuaternion(rotation); group_m.applyQuaternion(rotation.invert()); group.forEachChild(g => { g.origin.V3_add(group_m.x, group_m.y, group_m.z); }, Group, true) } else if (space === 2) { group_m = new THREE.Vector3(); group_m[getAxisLetter(axis)] = difference; group_m.applyQuaternion(group.mesh.quaternion); group.forEachChild(g => { g.origin.V3_add(group_m.x, group_m.y, group_m.z); }, Group, true) } else { group.forEachChild(g => { g.origin[axis] += difference }, Group, true) } Canvas.updateAllBones([Group.selected]); } Outliner.selected.forEach(el => { if (!group_m && el instanceof Mesh && (el.getSelectedVertices().length > 0 || space >= 2)) { let selection_rotation = space == 3 && el.getSelectionRotation(); let selected_vertices = el.getSelectedVertices(); if (!selected_vertices.length) selected_vertices = Object.keys(el.vertices) selected_vertices.forEach(key => { if (space == 2) { el.vertices[key][axis] += difference; } else if (space == 3) { let m = vector.set(0, 0, 0); m[getAxisLetter(axis)] = difference; m.applyEuler(selection_rotation); el.vertices[key].V3_add(m.x, m.y, m.z); } else { let m = vector.set(0, 0, 0); m[getAxisLetter(axis)] = difference; m.applyQuaternion(el.mesh.getWorldQuaternion(quaternion).invert()); el.vertices[key].V3_add(m.x, m.y, m.z); } }) } else { if (space == 2 && !group_m) { if (el instanceof Locator) { let m = vector.set(0, 0, 0); m[getAxisLetter(axis)] = difference; m.applyQuaternion(el.mesh.quaternion); el.from.V3_add(m.x, m.y, m.z); } else if (el instanceof TextureMesh) { el.local_pivot[axis] += difference; } else { if (el.movable) el.from[axis] += difference; if (el.resizable && el.to) el.to[axis] += difference; } } else if (space instanceof Group) { if (el.movable && el instanceof Mesh == false) el.from[axis] += difference; if (el.resizable && el.to) el.to[axis] += difference; if (el.rotatable && el instanceof Locator == false) el.origin[axis] += difference; } else { let move_origin = !!group; if (group_m) { var m = group_m } else { var m = vector.set(0, 0, 0); m[getAxisLetter(axis)] = difference; let parent = el.parent; while (parent instanceof Group) { if (!parent.rotation.allEqual(0)) break; parent = parent.parent; } if (parent == 'root') { // If none of the parent groups are rotated, move origin. move_origin = true; } else { var rotation = new THREE.Quaternion(); if (el.mesh && el instanceof Locator == false) { el.mesh.getWorldQuaternion(rotation); } else if (el.parent instanceof Group) { el.parent.mesh.getWorldQuaternion(rotation); } m.applyQuaternion(rotation.invert()); } } if (el.movable && el instanceof Mesh == false) el.from.V3_add(m.x, m.y, m.z); if (el.resizable && el.to) el.to.V3_add(m.x, m.y, m.z); if (move_origin) { if (el.rotatable && el instanceof Locator == false && el instanceof TextureMesh == false) el.origin.V3_add(m.x, m.y, m.z); } } } if (el instanceof Cube) { el.mapAutoUV() } }) Canvas.updateView({ elements: Outliner.selected, element_aspects: {transform: true, geometry: true}, groups: Group.all.filter(g => g.selected), group_aspects: {transform: true} }) } //Rotate function getRotationInterval(event) { if (Format.rotation_limit) { return 22.5; } else if ((event.shiftKey || Pressing.overrides.shift) && (event.ctrlOrCmd || Pressing.overrides.ctrl)) { return 0.25; } else if (event.shiftKey || Pressing.overrides.shift) { return 22.5; } else if (event.ctrlOrCmd || Pressing.overrides.ctrl) { return 1; } else { return 2.5; } } function getRotationObject() { if (Format.bone_rig && Group.selected) return Group.selected; let elements = Outliner.selected.filter(element => { return element.rotatable && (element instanceof Cube == false || Format.rotate_cubes); }) if (elements.length) return elements; } function rotateOnAxis(modify, axis, slider) { var things = getRotationObject(); if (!things) return; if (things instanceof Array == false) things = [things]; /* if (Format.bone_rig && Group.selected) { if (!Group.selected) return; let obj = Group.selected.mesh if (typeof space == 'object') { let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0))) rotWorldMatrix.multiply(obj.matrix) obj.matrix.copy(rotWorldMatrix) obj.setRotationFromMatrix(rotWorldMatrix) let e = obj.rotation; Group.selected.rotation[0] = Math.radToDeg(e.x); Group.selected.rotation[1] = Math.radToDeg(e.y); Group.selected.rotation[2] = Math.radToDeg(e.z); Canvas.updateAllBones() } else if (space == 0) { let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0))) rotWorldMatrix.multiply(obj.matrixWorld) let inverse = new THREE.Matrix4().copy(obj.parent.matrixWorld).invert() rotWorldMatrix.premultiply(inverse) obj.matrix.copy(rotWorldMatrix) obj.setRotationFromMatrix(rotWorldMatrix) let e = obj.rotation; Group.selected.rotation[0] = Math.radToDeg(e.x); Group.selected.rotation[1] = Math.radToDeg(e.y); Group.selected.rotation[2] = Math.radToDeg(e.z); Canvas.updateAllBones() } else { var value = modify(Group.selected.rotation[axis]); Group.selected.rotation[axis] = Math.trimDeg(value) Canvas.updateAllBones() } return; } */ //Warning if (Format.rotation_limit && settings.dialog_rotation_limit.value) { var i = 0; while (i < Cube.selected.length) { if (Cube.selected[i].rotation[(axis+1)%3] || Cube.selected[i].rotation[(axis+2)%3] ) { i = Infinity Blockbench.showMessageBox({ title: tl('message.rotation_limit.title'), icon: 'rotate_right', message: tl('message.rotation_limit.message'), buttons: [tl('dialog.ok'), tl('dialog.dontshowagain')] }, function(r) { if (r === 1) { settings.dialog_rotation_limit.value = false Settings.save() } }) return; //Gotta stop the numslider here } i++; } } var axis_letter = getAxisLetter(axis) var origin = things[0].origin things.forEach(function(obj, i) { if (!obj.rotation.allEqual(0)) { origin = obj.origin } }) let space = Transformer.getTransformSpace() if (axis instanceof THREE.Vector3) space = 0; things.forEach(obj => { let mesh = obj.mesh; if (obj instanceof Cube && !Format.bone_rig) { if (obj.origin.allEqual(0)) { obj.origin.V3_set(origin) } } if (!Group.selected && obj instanceof Mesh && Project.selected_vertices[obj.uuid] && Project.selected_vertices[obj.uuid].length > 0) { let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0))) if (space instanceof Group || space == 'root') { rotWorldMatrix.multiply(mesh.matrix); } else if (space == 0) { rotWorldMatrix.multiply(mesh.matrixWorld); } let q = new THREE.Quaternion().setFromRotationMatrix(rotWorldMatrix); if (space instanceof Group || space == 'root') { q.premultiply(mesh.quaternion.invert()); mesh.quaternion.invert(); } else if (space == 0) { let quat = mesh.getWorldQuaternion(new THREE.Quaternion()).invert(); q.premultiply(quat); } let vector = new THREE.Vector3(); let local_pivot = obj.mesh.worldToLocal(new THREE.Vector3().copy(Transformer.position)) Project.selected_vertices[obj.uuid].forEach(key => { vector.fromArray(obj.vertices[key]); vector.sub(local_pivot); vector.applyQuaternion(q); vector.add(local_pivot); obj.vertices[key].V3_set(vector.x, vector.y, vector.z); }) } else if (slider || (space == 2 && Format.rotation_limit)) { var obj_val = modify(obj.rotation[axis]); obj_val = Math.trimDeg(obj_val) if (Format.rotation_limit) { //Limit To 1 Axis obj.rotation[(axis+1)%3] = 0 obj.rotation[(axis+2)%3] = 0 //Limit Angle obj_val = Math.round(obj_val/22.5)*22.5 if (obj_val > 45 || obj_val < -45) { let f = obj_val > 45 let can_roll = obj.roll(axis, f!=(axis==1) ? 1 : 3); if (can_roll) { obj_val = f ? -22.5 : 22.5; } else { obj_val = Math.clamp(obj_val, -45, 45); } } } obj.rotation[axis] = obj_val if (obj instanceof Cube) { obj.rotation_axis = axis_letter } } else if (space == 2) { let old_order = mesh.rotation.order; mesh.rotation.reorder(axis == 0 ? 'ZYX' : (axis == 1 ? 'ZXY' : 'XYZ')) var obj_val = modify(Math.radToDeg(mesh.rotation[axis_letter])); obj_val = Math.trimDeg(obj_val) mesh.rotation[axis_letter] = Math.degToRad(obj_val); mesh.rotation.reorder(old_order); obj.rotation[0] = Math.radToDeg(mesh.rotation.x); obj.rotation[1] = Math.radToDeg(mesh.rotation.y); obj.rotation[2] = Math.radToDeg(mesh.rotation.z); } else if (space instanceof Group) { let normal = axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0))) rotWorldMatrix.multiply(mesh.matrix) mesh.matrix.copy(rotWorldMatrix) mesh.setRotationFromMatrix(rotWorldMatrix) let e = mesh.rotation; obj.rotation[0] = Math.radToDeg(e.x); obj.rotation[1] = Math.radToDeg(e.y); obj.rotation[2] = Math.radToDeg(e.z); } else if (space == 0) { let normal = axis instanceof THREE.Vector3 ? axis : axis == 0 ? THREE.NormalX : (axis == 1 ? THREE.NormalY : THREE.NormalZ) let rotWorldMatrix = new THREE.Matrix4(); rotWorldMatrix.makeRotationAxis(normal, Math.degToRad(modify(0))) rotWorldMatrix.multiply(mesh.matrixWorld) let inverse = new THREE.Matrix4().copy(mesh.parent.matrixWorld).invert() rotWorldMatrix.premultiply(inverse) mesh.matrix.copy(rotWorldMatrix) mesh.setRotationFromMatrix(rotWorldMatrix) let e = mesh.rotation; obj.rotation[0] = Math.radToDeg(e.x); obj.rotation[1] = Math.radToDeg(e.y); obj.rotation[2] = Math.radToDeg(e.z); } if (obj instanceof Group) { Canvas.updateView({groups: [obj]}); } }) } BARS.defineActions(function() { new BarSelect('transform_space', { condition: { modes: ['edit', 'animate'], tools: ['move_tool', 'pivot_tool', 'resize_tool'], method: () => !(Toolbox && Toolbox.selected.id === 'resize_tool' && Mesh.all.length === 0) }, category: 'transform', value: 'local', options: { global: true, bone: {condition: () => Format.bone_rig, name: true}, local: true, normal: {condition: () => Mesh.selected.length, name: true} }, onChange() { updateSelection(); } }) new BarSelect('rotation_space', { condition: {modes: ['edit', 'animate', 'pose'], tools: ['rotate_tool']}, category: 'transform', value: 'local', options: { global: 'action.transform_space.global', bone: {condition: () => Format.bone_rig, name: true, name: 'action.transform_space.bone'}, local: 'action.transform_space.local' }, onChange() { updateSelection(); } }) let grid_locked_interval = function(event) { event = event||0; return canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl); } function moveOnAxis(modify, axis) { selected.forEach(function(obj, i) { if (obj instanceof Mesh && obj.getSelectedVertices().length) { let vertices = obj.getSelectedVertices(); vertices.forEach(vkey => { obj.vertices[vkey][axis] = modify(obj.vertices[vkey][axis]); }) obj.preview_controller.updateGeometry(obj); } else if (obj.movable) { var val = modify(obj.from[axis]); if (Format.canvas_limit && !settings.deactivate_size_limit.value) { var size = obj.to ? obj.size(axis) : 0; val = limitToBox(limitToBox(val, -obj.inflate) + size, obj.inflate) - size } var before = obj.from[axis]; obj.from[axis] = val; if (obj.to) { obj.to[axis] += (val - before); } if (obj instanceof Cube) { obj.mapAutoUV() } obj.preview_controller.updateTransform(obj); if (obj.preview_controller.updateGeometry) obj.preview_controller.updateGeometry(obj); } }) TickUpdates.selection = true; } function getPos(axis) { let element = Outliner.selected[0]; if (element instanceof Mesh && element.getSelectedVertices().length) { let vertices = element.getSelectedVertices(); let sum = 0; vertices.forEach(vkey => sum += element.vertices[vkey][axis]); return sum / vertices.length; } else if (element instanceof Cube) { return element.from[axis]; } else { return element.origin[axis] } } new NumSlider('slider_pos_x', { name: tl('action.slider_pos', ['X']), description: tl('action.slider_pos.desc', ['X']), color: 'x', category: 'transform', condition: () => (selected.length && Modes.edit), getInterval: grid_locked_interval, get: function() { return getPos(0); }, change: function(modify) { moveOnAxis(modify, 0) }, onBefore: function() { Undo.initEdit({elements: selected}) }, onAfter: function() { Undo.finishEdit('Change element position') } }) new NumSlider('slider_pos_y', { name: tl('action.slider_pos', ['Y']), description: tl('action.slider_pos.desc', ['Y']), color: 'y', category: 'transform', condition: () => (selected.length && Modes.edit), getInterval: grid_locked_interval, get: function() { return getPos(1); }, change: function(modify) { moveOnAxis(modify, 1) }, onBefore: function() { Undo.initEdit({elements: selected}) }, onAfter: function() { Undo.finishEdit('Change element position') } }) new NumSlider('slider_pos_z', { name: tl('action.slider_pos', ['Z']), description: tl('action.slider_pos.desc', ['Z']), color: 'z', category: 'transform', condition: () => (selected.length && Modes.edit), getInterval: grid_locked_interval, get: function() { return getPos(2); }, change: function(modify) { moveOnAxis(modify, 2) }, onBefore: function() { Undo.initEdit({elements: selected}) }, onAfter: function() { Undo.finishEdit('Change element position') } }) function resizeOnAxis(modify, axis) { selected.forEach(function(obj, i) { if (obj.resizable) { obj.resize(modify, axis, false, true) } else if (obj.scalable) { obj.scale[axis] = modify(obj.scale[axis]); obj.preview_controller.updateTransform(obj); if (obj.preview_controller.updateGeometry) obj.preview_controller.updateGeometry(obj); } }) } new NumSlider('slider_size_x', { name: tl('action.slider_size', ['X']), description: tl('action.slider_size.desc', ['X']), color: 'x', category: 'transform', condition: () => (Outliner.selected[0] && (Outliner.selected[0].resizable || Outliner.selected[0].scalable) && Outliner.selected[0] instanceof Mesh == false && Modes.edit), getInterval: grid_locked_interval, get: function() { if (Outliner.selected[0].scalable) { return Outliner.selected[0].scale[0] } else if (Outliner.selected[0].resizable) { return Outliner.selected[0].to[0] - Outliner.selected[0].from[0] } }, change: function(modify) { resizeOnAxis(modify, 0) }, onBefore: function() { Undo.initEdit({elements: Cube.selected}) }, onAfter: function() { Undo.finishEdit('Change element size') } }) new NumSlider('slider_size_y', { name: tl('action.slider_size', ['Y']), description: tl('action.slider_size.desc', ['Y']), color: 'y', category: 'transform', condition: () => (Outliner.selected[0] && (Outliner.selected[0].resizable || Outliner.selected[0].scalable) && Outliner.selected[0] instanceof Mesh == false && Modes.edit), getInterval: grid_locked_interval, get: function() { if (Outliner.selected[0].scalable) { return Outliner.selected[0].scale[1] } else if (Outliner.selected[0].resizable) { return Outliner.selected[0].to[1] - Outliner.selected[0].from[1] } }, change: function(modify) { resizeOnAxis(modify, 1) }, onBefore: function() { Undo.initEdit({elements: Cube.selected}) }, onAfter: function() { Undo.finishEdit('Change element size') } }) new NumSlider('slider_size_z', { name: tl('action.slider_size', ['Z']), description: tl('action.slider_size.desc', ['Z']), color: 'z', category: 'transform', condition: () => (Outliner.selected[0] && (Outliner.selected[0].resizable || Outliner.selected[0].scalable)&& Outliner.selected[0] instanceof Mesh == false && Modes.edit), getInterval: grid_locked_interval, get: function() { if (Outliner.selected[0].scalable) { return Outliner.selected[0].scale[2] } else if (Outliner.selected[0].resizable) { return Outliner.selected[0].to[2] - Outliner.selected[0].from[2] } }, change: function(modify) { resizeOnAxis(modify, 2) }, onBefore: function() { Undo.initEdit({elements: Cube.selected}) }, onAfter: function() { Undo.finishEdit('Change element size') } }) //Inflate new NumSlider('slider_inflate', { category: 'transform', condition: function() {return Cube.selected.length && Modes.edit}, getInterval: grid_locked_interval, get: function() { return Cube.selected[0].inflate }, change: function(modify) { Cube.selected.forEach(function(obj, i) { var v = modify(obj.inflate) if (Format.canvas_limit && !settings.deactivate_size_limit.value) { v = obj.from[0] - Math.clamp(obj.from[0]-v, -16, 32); v = obj.from[1] - Math.clamp(obj.from[1]-v, -16, 32); v = obj.from[2] - Math.clamp(obj.from[2]-v, -16, 32); v = Math.clamp(obj.to[0]+v, -16, 32) - obj.to[0]; v = Math.clamp(obj.to[1]+v, -16, 32) - obj.to[1]; v = Math.clamp(obj.to[2]+v, -16, 32) - obj.to[2]; } obj.inflate = v }) Canvas.updatePositions() }, onBefore: function() { Undo.initEdit({elements: Cube.selected}) }, onAfter: function() { Undo.finishEdit('Inflate elements') } }) //Rotation new NumSlider('slider_rotation_x', { name: tl('action.slider_rotation', ['X']), description: tl('action.slider_rotation.desc', ['X']), color: 'x', category: 'transform', condition: () => ((Modes.edit || Modes.pose) && getRotationObject()), get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.rotation[0]; } let ref = Outliner.selected.find(el => { return el.rotatable && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.rotation[0]; }, change: function(modify) { rotateOnAxis(modify, 0, true) Canvas.updatePositions() }, onBefore: function() { Undo.initEdit({elements: Cube.selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit(getRotationObject() instanceof Group ? 'Rotate group' : 'Rotate elements'); }, getInterval: getRotationInterval }) new NumSlider('slider_rotation_y', { name: tl('action.slider_rotation', ['Y']), description: tl('action.slider_rotation.desc', ['Y']), color: 'y', category: 'transform', condition: () => ((Modes.edit || Modes.pose) && getRotationObject()), get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.rotation[1]; } let ref = Outliner.selected.find(el => { return el.rotatable && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.rotation[1]; }, change: function(modify) { rotateOnAxis(modify, 1, true) Canvas.updatePositions() }, onBefore: function() { Undo.initEdit({elements: selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit(getRotationObject() instanceof Group ? 'Rotate group' : 'Rotate elements'); }, getInterval: getRotationInterval }) new NumSlider('slider_rotation_z', { name: tl('action.slider_rotation', ['Z']), description: tl('action.slider_rotation.desc', ['Z']), color: 'z', category: 'transform', condition: () => ((Modes.edit || Modes.pose) && getRotationObject()), get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.rotation[2]; } let ref = Outliner.selected.find(el => { return el.rotatable && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.rotation[2]; }, change: function(modify) { rotateOnAxis(modify, 2, true) Canvas.updatePositions() }, onBefore: function() { Undo.initEdit({elements: selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit(getRotationObject() instanceof Group ? 'Rotate group' : 'Rotate elements'); }, getInterval: getRotationInterval }) function rotateCondition() { return (Modes.edit && ( (Format.bone_rig && Group.selected) || (Format.rotate_cubes && Cube.selected.length) )) } //Origin function moveOriginOnAxis(modify, axis) { var rotation_object = getRotationObject() if (rotation_object instanceof Group) { var val = modify(rotation_object.origin[axis]); rotation_object.origin[axis] = val; let elements_to_update = []; rotation_object.forEachChild(element => elements_to_update.push(element), OutlinerElement); Canvas.updateView({ groups: [rotation_object], group_aspects: {transform: true}, elements: elements_to_update, element_aspects: {transform: true}, selection: true }); if (Format.bone_rig) { Canvas.updateAllBones(); } } else { rotation_object.forEach(function(obj, i) { var val = modify(obj.origin[axis]); obj.origin[axis] = val; }) Canvas.updateView({elements: rotation_object, element_aspects: {transform: true, geometry: true}, selection: true}) } if (Modes.animate) { Animator.preview(); } } new NumSlider('slider_origin_x', { name: tl('action.slider_origin', ['X']), description: tl('action.slider_origin.desc', ['X']), color: 'x', category: 'transform', condition: () => (Modes.edit || Modes.animate) && getRotationObject() && (Group.selected || Outliner.selected.length > Locator.selected.length), getInterval: grid_locked_interval, get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.origin[0]; } let ref = Outliner.selected.find(el => { return el.rotatable && el.origin && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.origin[0]; }, change: function(modify) { moveOriginOnAxis(modify, 0) }, onBefore: function() { Undo.initEdit({elements: selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit('Change pivot point') } }) new NumSlider('slider_origin_y', { name: tl('action.slider_origin', ['Y']), description: tl('action.slider_origin.desc', ['Y']), color: 'y', category: 'transform', condition: () => (Modes.edit || Modes.animate) && getRotationObject() && (Group.selected || Outliner.selected.length > Locator.selected.length), getInterval: grid_locked_interval, get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.origin[1]; } let ref = Outliner.selected.find(el => { return el.rotatable && el.origin && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.origin[1]; }, change: function(modify) { moveOriginOnAxis(modify, 1) }, onBefore: function() { Undo.initEdit({elements: selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit('Change pivot point') } }) new NumSlider('slider_origin_z', { name: tl('action.slider_origin', ['Z']), description: tl('action.slider_origin.desc', ['Z']), color: 'z', category: 'transform', condition: () => (Modes.edit || Modes.animate) && getRotationObject() && (Group.selected || Outliner.selected.length > Locator.selected.length), getInterval: grid_locked_interval, get: function() { if (Format.bone_rig && Group.selected) { return Group.selected.origin[2]; } let ref = Outliner.selected.find(el => { return el.rotatable && el.origin && (Format.rotate_cubes || el instanceof Cube == false) }) if (ref) return ref.origin[2]; }, change: function(modify) { moveOriginOnAxis(modify, 2) }, onBefore: function() { Undo.initEdit({elements: selected, group: Group.selected}) }, onAfter: function() { Undo.finishEdit('Change pivot point') } }) new Action('scale', { icon: 'settings_overscan', category: 'transform', condition: () => (Modes.edit && selected.length), click: function () { $('#model_scale_range, #model_scale_label').val(1) $('#scaling_clipping_warning').text('') Undo.initEdit({elements: Outliner.selected, outliner: Format.bone_rig}) Outliner.selected.forEach(function(obj) { obj.before = { from: obj.from ? obj.from.slice() : undefined, to: obj.to ? obj.to.slice() : undefined, origin: obj.origin ? obj.origin.slice() : undefined } if (obj instanceof Mesh) { obj.before.vertices = {}; for (let key in obj.vertices) { obj.before.vertices[key] = obj.vertices[key].slice(); } } }) getScaleAllGroups().forEach((g) => { g.old_origin = g.origin.slice(); }, Group, true) showDialog('scaling') var v = Format.centered_grid ? 0 : 8; var origin = Group.selected ? Group.selected.origin : [v, 0, v]; $('#scaling_origin_x').val(origin[0]) $('#scaling_origin_y').val(origin[1]) $('#scaling_origin_z').val(origin[2]) scaleAll(false, 1) } }) new Action('rotate_x_cw', { name: tl('action.rotate_cw', 'X'), icon: 'rotate_right', color: 'x', category: 'transform', click: function () { rotateSelected(0, 1); } }) new Action('rotate_x_ccw', { name: tl('action.rotate_ccw', 'X'), icon: 'rotate_left', color: 'x', category: 'transform', click: function () { rotateSelected(0, 3); } }) new Action('rotate_y_cw', { name: tl('action.rotate_cw', 'Y'), icon: 'rotate_right', color: 'y', category: 'transform', click: function () { rotateSelected(1, 1); } }) new Action('rotate_y_ccw', { name: tl('action.rotate_ccw', 'Y'), icon: 'rotate_left', color: 'y', category: 'transform', click: function () { rotateSelected(1, 3); } }) new Action('rotate_z_cw', { name: tl('action.rotate_cw', 'Z'), icon: 'rotate_right', color: 'z', category: 'transform', click: function () { rotateSelected(2, 1); } }) new Action('rotate_z_ccw', { name: tl('action.rotate_ccw', 'Z'), icon: 'rotate_left', color: 'z', category: 'transform', click: function () { rotateSelected(2, 3); } }) new Action('flip_x', { name: tl('action.flip', 'X'), icon: 'icon-mirror_x', color: 'x', category: 'transform', click: function () { mirrorSelected(0); } }) new Action('flip_y', { name: tl('action.flip', 'Y'), icon: 'icon-mirror_y', color: 'y', category: 'transform', click: function () { mirrorSelected(1); } }) new Action('flip_z', { name: tl('action.flip', 'Z'), icon: 'icon-mirror_z', color: 'z', category: 'transform', click: function () { mirrorSelected(2); } }) new Action('center_x', { name: tl('action.center', 'X'), icon: 'vertical_align_center', color: 'x', category: 'transform', click: function () { Undo.initEdit({elements: selected}); centerElements(0); Undo.finishEdit('Center selection on X axis') } }) new Action('center_y', { name: tl('action.center', 'Y'), icon: 'vertical_align_center', color: 'y', category: 'transform', click: function () { Undo.initEdit({elements: selected}); centerElements(1); Undo.finishEdit('Center selection on Y axis') } }) new Action('center_z', { name: tl('action.center', 'Z'), icon: 'vertical_align_center', color: 'z', category: 'transform', click: function () { Undo.initEdit({elements: selected}); centerElements(2); Undo.finishEdit('Center selection on Z axis') } }) new Action('center_all', { icon: 'filter_center_focus', category: 'transform', click: function () { Undo.initEdit({elements: selected}); centerElementsAll(); Undo.finishEdit('Center selection') } }) //Move Cube Keys new Action('move_up', { icon: 'arrow_upward', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 38, ctrl: null, shift: null}), click: function (e) { if (Prop.active_panel === 'uv') { UVEditor.moveSelection([0, -1], e) } else { moveElementsRelative(-1, 2, e) } } }) new Action('move_down', { icon: 'arrow_downward', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 40, ctrl: null, shift: null}), click: function (e) { if (Prop.active_panel === 'uv') { UVEditor.moveSelection([0, 1], e) } else { moveElementsRelative(1, 2, e) } } }) new Action('move_left', { icon: 'arrow_back', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 37, ctrl: null, shift: null}), click: function (e) { if (Prop.active_panel === 'uv') { UVEditor.moveSelection([-1, 0], e) } else { moveElementsRelative(-1, 0, e) } } }) new Action('move_right', { icon: 'arrow_forward', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 39, ctrl: null, shift: null}), click: function (e) { if (Prop.active_panel === 'uv') { UVEditor.moveSelection([1, 0], e) } else { moveElementsRelative(1, 0, e) } } }) new Action('move_forth', { icon: 'keyboard_arrow_up', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 33, ctrl: null, shift: null}), click: function (e) {moveElementsRelative(-1, 1, e)} }) new Action('move_back', { icon: 'keyboard_arrow_down', category: 'transform', condition: {modes: ['edit'], method: () => (!open_menu && selected.length)}, keybind: new Keybind({key: 34, ctrl: null, shift: null}), click: function (e) {moveElementsRelative(1, 1, e)} }) new Action('toggle_visibility', { icon: 'visibility', category: 'transform', click: function () {toggleCubeProperty('visibility')} }) new Action('toggle_locked', { icon: 'fas.fa-lock', category: 'transform', click: function () {toggleCubeProperty('locked')} }) new Action('toggle_export', { icon: 'save', category: 'transform', click: function () {toggleCubeProperty('export')} }) new Action('toggle_autouv', { icon: 'fullscreen_exit', category: 'transform', condition: {modes: ['edit']}, click: function () {toggleCubeProperty('autouv')} }) new Action('toggle_shade', { icon: 'wb_sunny', category: 'transform', condition: () => !Project.box_uv && Modes.edit, click: function () {toggleCubeProperty('shade')} }) new Action('toggle_mirror_uv', { icon: 'icon-mirror_x', category: 'transform', condition: () => Project.box_uv && (Modes.edit || Modes.paint), click: function () {toggleCubeProperty('shade')} }) new Action('update_autouv', { icon: 'brightness_auto', category: 'transform', condition: () => !Project.box_uv && Modes.edit, click: function () { if (Cube.selected.length) { Undo.initEdit({elements: Cube.selected[0].forSelected(), selection: true}) Cube.selected[0].forSelected(function(cube) { cube.mapAutoUV() }) Undo.finishEdit('Update auto UV') } } }) new Action('origin_to_geometry', { icon: 'filter_center_focus', category: 'transform', condition: {modes: ['edit', 'animate']}, click: function () {origin2geometry()} }) new Action('rescale_toggle', { icon: 'check_box_outline_blank', category: 'transform', condition: function() {return Format.rotation_limit && Cube.selected.length;}, click: function () { Undo.initEdit({elements: Cube.selected}) var value = !Cube.selected[0].rescale Cube.selected.forEach(function(cube) { cube.rescale = value }) Canvas.updatePositions() updateNslideValues() Undo.finishEdit('Toggle cube rescale') } }) new Action('bone_reset_toggle', { icon: 'check_box_outline_blank', category: 'transform', condition: function() {return Format.bone_rig && Group.selected;}, click: function () { Undo.initEdit({group: Group.selected}) Group.selected.reset = !Group.selected.reset updateNslideValues() Undo.finishEdit('Toggle bone reset') } }) new Action('remove_blank_faces', { icon: 'cancel_presentation', condition: () => !Format.box_uv, click: function () { let elements = Outliner.selected.filter(el => el.faces); Undo.initEdit({elements}) var arr = elements.slice() var empty_elements = []; var cleared_total = 0; unselectAll() arr.forEach(element => { var clear_count = 0; var original_face_count = Object.keys(element.faces).length for (var face in element.faces) { var face_tag = element.faces[face]; if (face_tag.texture == false) { if (element instanceof Cube) { face_tag.texture = null; } else { delete element.faces[face]; } clear_count++; cleared_total++; } } if (clear_count == original_face_count) { empty_elements.push(element); } }) updateSelection(); Blockbench.showQuickMessage(tl('message.removed_faces', [cleared_total])) if (empty_elements.length) { Blockbench.showMessageBox({ title: tl('message.cleared_blank_faces.title'), icon: 'rotate_right', message: tl('message.cleared_blank_faces.message', [empty_elements.length]), buttons: ['generic.remove', 'dialog.cancel'], confirm: 0, cancel: 1, }, function(r) { empty_elements.forEach(element => { if (r == 0) { element.remove(); elements.remove(element) } else { for (var face in element.faces) { element.faces[face].texture = false; } } }) updateSelection(); Canvas.updateView({elements, element_aspects: {geometry: true, faces: true, uv: true}}) Undo.finishEdit('Remove blank faces'); }) } else { Canvas.updateView({elements, element_aspects: {geometry: true, faces: true, uv: true}}) Undo.finishEdit('Remove blank faces'); } } }) })