mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-27 04:21:46 +08:00
Implement new start screen + new start screen tech
Implement mesh painting grid, close #1014 Implement mesh mirror painting, close #1013 Fix issue displaying mesh without faces Implement mesh UV mirroring
This commit is contained in:
parent
6b5a5f5cc0
commit
4c9d9acd5d
@ -4,23 +4,32 @@
|
||||
"text_color": "var(--color-bright_ui_text)",
|
||||
"graphic": {
|
||||
"type": "image",
|
||||
"source": "https://web.blockbench.net/content/splash_art.png?39",
|
||||
"width": 630,
|
||||
"height": 420
|
||||
"source": "./content/splash_art.png?39",
|
||||
"width": 1000,
|
||||
"aspect_ratio": "16/9",
|
||||
"description": "Splash Art Placeholder by [DerBauersSohn](https://twitter.com/DerBauersSohn__)"
|
||||
},
|
||||
"layout": "vertical",
|
||||
"text": [
|
||||
{"text": "## Blockbench 3.9"},
|
||||
{"text": "### Highlights of this update:"},
|
||||
{"type": "list", "list": [
|
||||
"Load, export and share keymaps and settings!",
|
||||
"Travel through time using the new Edit History!",
|
||||
"Hold Alt to resize cubes in both directions",
|
||||
"Get creative with the new gradient tool",
|
||||
"Transform spaces are now available in animation mode",
|
||||
"Dozens of quality-of-life features and improvements!"
|
||||
]},
|
||||
{"text": "Make sure to read the [full changelog](https://github.com/JannisX11/blockbench/releases/tag/v3.9.0)!"},
|
||||
{"text": "Splash Art by [LorenOLoren](https://twitter.com/LorenOLoren1) and [Dankbarkeit](https://twitter.com/Dxnkbarkeit)"}
|
||||
{"text": "Welcome to Blockbench 4.0, the Mesh Update!", "type": "h1"},
|
||||
{"text": "Check out the [full changelog](https://github.com/JannisX11/blockbench/releases/tag/v4.0.0)!"}
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"image": "./content/splash_art.png",
|
||||
"title": "Poly Mesh Editing!",
|
||||
"text": "Blockbench now supports poly mesh editing, which let's you create any shapes in generic models."
|
||||
},
|
||||
{
|
||||
"image": "./content/splash_art.png",
|
||||
"title": "Tabs!",
|
||||
"text": "Open multiple models at the same time and easily switch between them using the new project tabs!"
|
||||
},
|
||||
{
|
||||
"image": "./content/splash_art.png",
|
||||
"title": "Theme Browser",
|
||||
"text": "You can now discover and switch to new themes directly inside Blockbench. Also, the theme menu and the other settings menus have been re-designed and cleaned up!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 668 KiB |
@ -710,6 +710,9 @@
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
#start_screen > content > section.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
#start_screen section div.start_screen_left, #start_screen section div.start_screen_right {
|
||||
display: block;
|
||||
padding: 24px;
|
||||
@ -723,8 +726,17 @@
|
||||
grid-gap: 5px;
|
||||
}
|
||||
#start_screen div.start_screen_left {
|
||||
max-width: 100%;
|
||||
flex-grow: 0;
|
||||
}
|
||||
#start_screen div.start_screen_left.graphic {
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
#start_screen div.start_screen_left.graphic p {
|
||||
position: absolute;
|
||||
font-size: 0.96em;
|
||||
}
|
||||
#start_screen div.start_screen_left i.graphic_icon {
|
||||
font-size: 40px;
|
||||
@ -735,6 +747,10 @@
|
||||
flex-grow: 1;
|
||||
width: 62%;
|
||||
}
|
||||
#start_screen section.vertical div.start_screen_right {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
#start_screen i.start_screen_close_button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
@ -744,6 +760,33 @@
|
||||
#start_screen i.start_screen_close_button:not(:hover) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
#start_screen section.vertical.bright_ui .start_screen_right {
|
||||
box-shadow: 0 0 14px #00103030;
|
||||
}
|
||||
#start_screen .start_screen_features {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
background: #00004006;
|
||||
}
|
||||
#start_screen .start_screen_features > li {
|
||||
width: 30%;
|
||||
flex-grow: 1;
|
||||
min-width: 250px;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
margin: 7px;
|
||||
padding: 7px;
|
||||
}
|
||||
#start_screen .start_screen_features > li > * {
|
||||
max-width: 100%;
|
||||
font-weight: 300;
|
||||
}
|
||||
#start_screen .start_screen_features > li > h3 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
@media (max-device-width: 640px) {
|
||||
#start_screen {
|
||||
width: 100%;
|
||||
|
@ -1564,6 +1564,7 @@ const BARS = {
|
||||
|
||||
for (let key in mesh.faces) {
|
||||
let face = mesh.faces[key];
|
||||
if (!face.vertices.includes(vertex_key)) continue;
|
||||
if (face.vertices.length > 2) {
|
||||
face.vertices.remove(vertex_key);
|
||||
delete face.uv[vertex_key];
|
||||
|
@ -559,7 +559,10 @@ function addStartScreenSection(id, data) {
|
||||
left.css('background-image', `url('${data.graphic.source}')`)
|
||||
}
|
||||
if (data.graphic.width) {
|
||||
left.css('width', data.graphic.width+'px').css('flex-shrink', '0');
|
||||
left.css('width', data.graphic.width+'px');
|
||||
}
|
||||
if (data.graphic.width && data.text) {
|
||||
left.css('flex-shrink', '0');
|
||||
}
|
||||
if (data.graphic.width && data.graphic.height && Blockbench.isMobile) {
|
||||
left.css('height', '0')
|
||||
@ -567,7 +570,17 @@ function addStartScreenSection(id, data) {
|
||||
.css('padding-bottom', (data.graphic.height/data.graphic.width*100)+'%')
|
||||
} else {
|
||||
if (data.graphic.height) left.css('height', data.graphic.height+'px');
|
||||
if (data.graphic.width && !data.graphic.height) left.css('height', data.graphic.width+'px');
|
||||
if (data.graphic.width && !data.graphic.height && !data.graphic.aspect_ratio) left.css('height', data.graphic.width+'px');
|
||||
if (data.graphic.aspect_ratio) left.css('aspect-ratio', data.graphic.aspect_ratio);
|
||||
}
|
||||
if (data.graphic.description) {
|
||||
let content = $(marked(data.graphic.description));
|
||||
content.css({
|
||||
'bottom': '15px',
|
||||
'right': '15px',
|
||||
'color': data.graphic.description_color || '#ffffff',
|
||||
});
|
||||
left.append(content);
|
||||
}
|
||||
}
|
||||
if (data.text instanceof Array) {
|
||||
@ -576,8 +589,8 @@ function addStartScreenSection(id, data) {
|
||||
data.text.forEach(line => {
|
||||
var content = line.text ? marked(tl(line.text)) : '';
|
||||
switch (line.type) {
|
||||
case 'h1': var tag = 'h3'; break;
|
||||
case 'h2': var tag = 'h4'; break;
|
||||
case 'h1': var tag = 'h2'; break;
|
||||
case 'h2': var tag = 'h3'; break;
|
||||
case 'list':
|
||||
var tag = 'ul class="list_style"';
|
||||
line.list.forEach(string => {
|
||||
@ -594,6 +607,24 @@ function addStartScreenSection(id, data) {
|
||||
right.append(l);
|
||||
})
|
||||
}
|
||||
if (data.layout == 'vertical') {
|
||||
obj.addClass('vertical');
|
||||
}
|
||||
|
||||
if (data.features instanceof Array) {
|
||||
let features_section = document.createElement('ul');
|
||||
features_section.className = 'start_screen_features'
|
||||
data.features.forEach(feature => {
|
||||
let li = document.createElement('li');
|
||||
let img = new Image(); img.src = feature.image;
|
||||
let title = document.createElement('h3'); title.textContent = feature.title;
|
||||
let text = document.createElement('p'); text.textContent = feature.text;
|
||||
li.append(img, title, text);
|
||||
features_section.append(li);
|
||||
})
|
||||
obj.append(features_section);
|
||||
}
|
||||
|
||||
if (data.closable !== false) {
|
||||
obj.append(`<i class="material-icons start_screen_close_button">clear</i>`);
|
||||
obj.find('i.start_screen_close_button').click((e) => {
|
||||
@ -625,6 +656,10 @@ function addStartScreenSection(id, data) {
|
||||
|
||||
|
||||
(function() {
|
||||
/*$.getJSON('./content/news.json').then(data => {
|
||||
addStartScreenSection('new_version', data.new_version)
|
||||
})*/
|
||||
|
||||
var news_call = $.getJSON('https://web.blockbench.net/content/news.json')
|
||||
Promise.all([news_call, documentReady]).then((data) => {
|
||||
if (!data || !data[0]) return;
|
||||
|
@ -228,6 +228,7 @@ class ModelProject {
|
||||
|
||||
Blockbench.dispatchEvent('select_project', {project: this});
|
||||
|
||||
if (Preview.selected) Preview.selected.occupyTransformer();
|
||||
setProjectTitle(this.name);
|
||||
setStartScreen(!Project);
|
||||
updateInterface();
|
||||
|
@ -183,8 +183,8 @@ BARS.defineActions(function() {
|
||||
if (Modes.previous_id == 'animate') {
|
||||
Animator.preview();
|
||||
}
|
||||
Cube.all.forEach(cube => {
|
||||
Canvas.buildGridBox(cube)
|
||||
Outliner.elements.forEach(cube => {
|
||||
if (cube.preview_controller.updatePaintingGrid) cube.preview_controller.updatePaintingGrid(cube);
|
||||
})
|
||||
$('#main_colorpicker').spectrum('set', ColorPanel.vue._data.main_color);
|
||||
BarItems.slider_color_h.update();
|
||||
@ -196,8 +196,8 @@ BARS.defineActions(function() {
|
||||
},
|
||||
onUnselect: () => {
|
||||
Canvas.updateAllBones()
|
||||
Cube.all.forEach(cube => {
|
||||
Canvas.buildGridBox(cube)
|
||||
Outliner.elements.forEach(cube => {
|
||||
if (cube.preview_controller.updatePaintingGrid) cube.preview_controller.updatePaintingGrid(cube);
|
||||
})
|
||||
UVEditor.vue.setMode('uv');
|
||||
three_grid.visible = true;
|
||||
|
@ -829,7 +829,7 @@ new NodePreviewController(Cube, {
|
||||
}
|
||||
|
||||
if (Modes.paint) {
|
||||
Canvas.buildGridBox(element);
|
||||
element.preview_controller.updatePaintingGrid(element);
|
||||
}
|
||||
},
|
||||
updateGeometry(element) {
|
||||
@ -1035,6 +1035,119 @@ new NodePreviewController(Cube, {
|
||||
mesh.geometry.attributes.highlight.array.set(Array(mesh.geometry.attributes.highlight.count).fill(highlighted));
|
||||
mesh.geometry.attributes.highlight.needsUpdate = true;
|
||||
}
|
||||
},
|
||||
updatePaintingGrid(cube) {
|
||||
var mesh = cube.mesh;
|
||||
if (mesh === undefined) return;
|
||||
mesh.remove(mesh.grid_box);
|
||||
if (cube.visibility == false) return;
|
||||
|
||||
if (!Modes.paint || !settings.painting_grid.value) return;
|
||||
|
||||
var from = cube.from.slice();
|
||||
var to = cube.to.slice();
|
||||
if (cube.inflate) {
|
||||
from[0] -= cube.inflate; from[1] -= cube.inflate; from[2] -= cube.inflate;
|
||||
to[0] += cube.inflate; to[1] += cube.inflate; to[2] += cube.inflate;
|
||||
}
|
||||
|
||||
var vertices = [];
|
||||
var epsilon = 0.0001
|
||||
function getVector2(arr, axis) {
|
||||
switch (axis) {
|
||||
case 0: return [arr[1], arr[2]]; break;
|
||||
case 1: return [arr[0], arr[2]]; break;
|
||||
case 2: return [arr[0], arr[1]]; break;
|
||||
}
|
||||
}
|
||||
function addVector(u, v, axis, w) {
|
||||
switch (axis) {
|
||||
case 0: vertices.push(w, u, v); break;
|
||||
case 1: vertices.push(u, w, v); break;
|
||||
case 2: vertices.push(u, v, w); break;
|
||||
}
|
||||
}
|
||||
|
||||
function addFace(name, uv_offset, axis, side) {
|
||||
|
||||
var start = getVector2(from, axis)
|
||||
var end = getVector2(to, axis)
|
||||
var face = cube.faces[name];
|
||||
var texture = face.getTexture();
|
||||
if (texture === null) return;
|
||||
|
||||
var px_x = texture ? Project.texture_width / texture.width : 1;
|
||||
var px_y = texture ? Project.texture_height / texture.height : 1;
|
||||
var uv_size = [
|
||||
Math.abs(face.uv_size[0]),
|
||||
Math.abs(face.uv_size[1])
|
||||
]
|
||||
uv_offset = [
|
||||
uv_offset[0] == true
|
||||
? (face.uv_size[0] > 0 ? (px_x-face.uv[2]) : ( face.uv[2]))
|
||||
: (face.uv_size[0] > 0 ? ( face.uv[0]) : (px_x-face.uv[0])),
|
||||
uv_offset[1] == true
|
||||
? (face.uv_size[1] > 0 ? (px_y-face.uv[3]) : ( face.uv[3]))
|
||||
: (face.uv_size[1] > 0 ? ( face.uv[1]) : (px_y-face.uv[1]))
|
||||
]
|
||||
uv_offset[0] = uv_offset[0] % px_x;
|
||||
uv_offset[1] = uv_offset[1] % px_y;
|
||||
|
||||
if ((face.rotation % 180 == 90) != (axis == 0)) {
|
||||
uv_size.reverse();
|
||||
uv_offset.reverse();
|
||||
}
|
||||
|
||||
var w = side == 0 ? from[axis] : to[axis]
|
||||
|
||||
//Columns
|
||||
var width = end[0]-start[0];
|
||||
var step = Math.abs( width / uv_size[0] );
|
||||
if (texture) step *= Project.texture_width / texture.width;
|
||||
if (step < epsilon) step = epsilon;
|
||||
|
||||
for (var col = start[0] - uv_offset[0]; col <= end[0]; col += step) {
|
||||
if (col >= start[0]) {
|
||||
addVector(col, start[1], axis, w);
|
||||
addVector(col, end[1], axis, w);
|
||||
}
|
||||
}
|
||||
|
||||
//lines
|
||||
var height = end[1]-start[1];
|
||||
var step = Math.abs( height / uv_size[1] );
|
||||
let tex_height = texture.frameCount ? (texture.height / texture.frameCount) : texture.height;
|
||||
if (texture) step *= Project.texture_height / tex_height;
|
||||
if (step < epsilon) step = epsilon;
|
||||
|
||||
for (var line = start[1] - uv_offset[1]; line <= end[1]; line += step) {
|
||||
if (line >= start[1]) {
|
||||
addVector(start[0], line, axis, w);
|
||||
addVector(end[0], line, axis, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addFace('north', [true, true], 2, 0);
|
||||
addFace('south', [false, true], 2, 1);
|
||||
addFace('west', [false, true], 0, 0);
|
||||
addFace('east', [true, true], 0, 1);
|
||||
addFace('down', [false, true], 1, 0);
|
||||
addFace('up', [false, false], 1, 1);
|
||||
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
|
||||
|
||||
let box = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
|
||||
box.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2]);
|
||||
box.no_export = true;
|
||||
|
||||
box.name = cube.uuid+'_grid_box';
|
||||
box.renderOrder = 2;
|
||||
box.frustumCulled = false;
|
||||
mesh.grid_box = box;
|
||||
mesh.add(box);
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -116,6 +116,48 @@ class MeshFace extends Face {
|
||||
if (this.mesh.faces[fkey] == this) return fkey;
|
||||
}
|
||||
}
|
||||
UVToLocal(uv) {
|
||||
let p0 = this.uv[this.vertices[0]];
|
||||
let p1 = this.uv[this.vertices[1]];
|
||||
let p2 = this.uv[this.vertices[2]];
|
||||
|
||||
let vertexa = this.mesh.vertices[this.vertices[0]];
|
||||
let vertexb = this.mesh.vertices[this.vertices[1]];
|
||||
let vertexc = this.mesh.vertices[this.vertices[2]];
|
||||
|
||||
let b0 = (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1])
|
||||
let b1 = ((p1[0] - uv[0]) * (p2[1] - uv[1]) - (p2[0] - uv[0]) * (p1[1] - uv[1])) / b0
|
||||
let b2 = ((p2[0] - uv[0]) * (p0[1] - uv[1]) - (p0[0] - uv[0]) * (p2[1] - uv[1])) / b0
|
||||
let b3 = ((p0[0] - uv[0]) * (p1[1] - uv[1]) - (p1[0] - uv[0]) * (p0[1] - uv[1])) / b0
|
||||
|
||||
let local_space = new THREE.Vector3(
|
||||
vertexa[0] * b1 + vertexb[0] * b2 + vertexc[0] * b3,
|
||||
vertexa[1] * b1 + vertexb[1] * b2 + vertexc[1] * b3,
|
||||
vertexa[2] * b1 + vertexb[2] * b2 + vertexc[2] * b3,
|
||||
)
|
||||
return local_space;
|
||||
}
|
||||
localToUV(vector) {
|
||||
let va = new THREE.Vector3().fromArray(this.mesh.vertices[this.vertices[0]]);
|
||||
let vb = new THREE.Vector3().fromArray(this.mesh.vertices[this.vertices[1]]);
|
||||
let vc = new THREE.Vector3().fromArray(this.mesh.vertices[this.vertices[2]]);
|
||||
|
||||
let uva = new THREE.Vector2().fromArray(this.uv[this.vertices[0]]);
|
||||
let uvb = new THREE.Vector2().fromArray(this.uv[this.vertices[1]]);
|
||||
let uvc = new THREE.Vector2().fromArray(this.uv[this.vertices[2]]);
|
||||
|
||||
let uv = THREE.Triangle.getUV(vector, va, vb, vc, uva, uvb, uvc, new THREE.Vector2());
|
||||
return uv.toArray();
|
||||
}
|
||||
getCenter() {
|
||||
let center = [0, 0, 0];
|
||||
this.vertices.forEach(vkey => {
|
||||
let vertex = this.mesh.vertices[vkey];
|
||||
center.V3_add(vertex);
|
||||
})
|
||||
center.V3_divide(this.vertices.length);
|
||||
return center;
|
||||
}
|
||||
}
|
||||
new Property(MeshFace, 'array', 'vertices', {default: 0});
|
||||
|
||||
@ -650,6 +692,10 @@ new NodePreviewController(Mesh, {
|
||||
mesh.vertex_points.geometry.computeBoundingSphere();
|
||||
mesh.outline.geometry.computeBoundingSphere();
|
||||
updateCubeHighlights()
|
||||
|
||||
if (Modes.paint) {
|
||||
Mesh.preview_controller.updatePaintingGrid(element);
|
||||
}
|
||||
},
|
||||
updateFaces(element) {
|
||||
let {mesh} = element;
|
||||
@ -718,6 +764,7 @@ new NodePreviewController(Mesh, {
|
||||
}
|
||||
|
||||
mesh.material = materials;
|
||||
if (!mesh.material) mesh.material = Canvas.transparentMaterial;
|
||||
}
|
||||
},
|
||||
updateUV(element, animation = true) {
|
||||
@ -765,11 +812,11 @@ new NodePreviewController(Mesh, {
|
||||
}
|
||||
|
||||
let line_colors = [];
|
||||
mesh.outline.vertex_order.forEach(key => {
|
||||
mesh.outline.vertex_order.forEach((key, i) => {
|
||||
let color;
|
||||
if (!Modes.edit || BarItems.selection_mode.value == 'object') {
|
||||
color = gizmo_colors.outline;
|
||||
} else if (selected_vertices.includes(key)) {
|
||||
} else if (selected_vertices.includes(key) && selected_vertices.includes(mesh.outline.vertex_order[i + ((i%2) ? -1 : 1) ])) {
|
||||
color = white;
|
||||
} else {
|
||||
color = gizmo_colors.grid;
|
||||
@ -810,6 +857,71 @@ new NodePreviewController(Mesh, {
|
||||
|
||||
mesh.geometry.attributes.highlight.array.set(array);
|
||||
mesh.geometry.attributes.highlight.needsUpdate = true;
|
||||
},
|
||||
updatePaintingGrid(element) {
|
||||
var mesh = element.mesh;
|
||||
if (mesh === undefined) return;
|
||||
mesh.remove(mesh.grid_box);
|
||||
if (element.visibility == false) return;
|
||||
|
||||
if (!Modes.paint || !settings.painting_grid.value) return;
|
||||
|
||||
var positions = [];
|
||||
|
||||
for (let fkey in element.faces) {
|
||||
let face = element.faces[fkey];
|
||||
if (face.vertices <= 2) continue;
|
||||
let offset = face.getNormal(true).V3_multiply(0.01);
|
||||
let x_memory = {};
|
||||
let y_memory = {};
|
||||
let texture = face.getTexture();
|
||||
var psize_x = texture ? Project.texture_width / texture.width : 1;
|
||||
var psize_y = texture ? Project.texture_height / texture.height : 1;
|
||||
let vertices = face.getSortedVertices();
|
||||
vertices.forEach((vkey1, i) => {
|
||||
let vkey2 = vertices[i+1] || vertices[0];
|
||||
let uv1 = face.uv[vkey1].slice();
|
||||
let uv2 = face.uv[vkey2].slice();
|
||||
if (uv1[0] > uv2[0]) [uv1[0], uv2[0]] = [uv2[0], uv1[0]];
|
||||
if (uv1[1] > uv2[1]) [uv1[1], uv2[1]] = [uv2[1], uv1[1]];
|
||||
|
||||
for (let x = Math.ceil(uv1[0] / psize_x) * psize_x; x < uv2[0]; x += psize_x) {
|
||||
if (!x_memory[x]) x_memory[x] = [];
|
||||
let y = uv1[1] + (uv2[1] - uv1[1]) * Math.lerp(uv1[0], uv2[0], x);
|
||||
x_memory[x].push(face.UVToLocal([x, y]).toArray().V3_add(offset));
|
||||
}
|
||||
for (let y = Math.ceil(uv1[1] / psize_y) * psize_y; y < uv2[1]; y += psize_y) {
|
||||
if (!y_memory[y]) y_memory[y] = [];
|
||||
let x = uv1[0] + (uv2[0] - uv1[0]) * Math.lerp(uv1[1], uv2[1], y);
|
||||
y_memory[y].push(face.UVToLocal([x, y]).toArray().V3_add(offset));
|
||||
}
|
||||
})
|
||||
|
||||
for (let key in x_memory) {
|
||||
let points = x_memory[key];
|
||||
if (points.length == 2) {
|
||||
positions.push(...points[0], ...points[1]);
|
||||
}
|
||||
}
|
||||
for (let key in y_memory) {
|
||||
let points = y_memory[key];
|
||||
if (points.length == 2) {
|
||||
positions.push(...points[0], ...points[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
|
||||
|
||||
let box = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
|
||||
box.no_export = true;
|
||||
|
||||
box.name = element.uuid+'_grid_box';
|
||||
box.renderOrder = 2;
|
||||
box.frustumCulled = false;
|
||||
mesh.grid_box = box;
|
||||
mesh.add(box);
|
||||
}
|
||||
})
|
||||
|
||||
@ -830,9 +942,9 @@ BARS.defineActions(function() {
|
||||
}},
|
||||
diameter: {label: 'dialog.add_primitive.diameter', type: 'number', value: 16},
|
||||
height: {label: 'dialog.add_primitive.height', type: 'number', value: 8, condition: ({shape}) => ['cylinder', 'cone', 'cube', 'pyramid', 'tube'].includes(shape)},
|
||||
sides: {label: 'dialog.add_primitive.sides', type: 'number', value: 12, condition: ({shape}) => ['cylinder', 'cone', 'circle', 'torus', 'sphere', 'tube'].includes(shape)},
|
||||
sides: {label: 'dialog.add_primitive.sides', type: 'number', value: 12, min: 3, max: 48, condition: ({shape}) => ['cylinder', 'cone', 'circle', 'torus', 'sphere', 'tube'].includes(shape)},
|
||||
minor_diameter: {label: 'dialog.add_primitive.minor_diameter', type: 'number', value: 4, condition: ({shape}) => ['torus', 'tube'].includes(shape)},
|
||||
minor_sides: {label: 'dialog.add_primitive.minor_sides', type: 'number', value: 8, condition: ({shape}) => ['torus'].includes(shape)},
|
||||
minor_sides: {label: 'dialog.add_primitive.minor_sides', type: 'number', value: 8, min: 2, max: 32, condition: ({shape}) => ['torus'].includes(shape)},
|
||||
},
|
||||
onConfirm(result) {
|
||||
let elements = [];
|
||||
|
@ -1064,127 +1064,11 @@ const Canvas = {
|
||||
vertex_uvs.array.set(arr[2], index*8 + 4); //0,0
|
||||
vertex_uvs.array.set(arr[3], index*8 + 6); //1,0
|
||||
},
|
||||
buildGridBox(cube) {
|
||||
var mesh = cube.mesh;
|
||||
if (mesh === undefined) return;
|
||||
mesh.remove(mesh.grid_box);
|
||||
if (cube.visibility == false) return;
|
||||
|
||||
if (!Modes.paint || !settings.painting_grid.value) return;
|
||||
|
||||
var box = Canvas.getPaintingGrid(cube);
|
||||
|
||||
box.name = cube.uuid+'_grid_box';
|
||||
box.renderOrder = 2;
|
||||
box.frustumCulled = false;
|
||||
mesh.grid_box = box;
|
||||
mesh.add(box);
|
||||
},
|
||||
getPaintingGrid(cube) {
|
||||
var from = cube.from.slice();
|
||||
var to = cube.to.slice();
|
||||
if (cube.inflate) {
|
||||
from[0] -= cube.inflate; from[1] -= cube.inflate; from[2] -= cube.inflate;
|
||||
to[0] += cube.inflate; to[1] += cube.inflate; to[2] += cube.inflate;
|
||||
}
|
||||
|
||||
var vertices = [];
|
||||
var epsilon = 0.0001
|
||||
function getVector2(arr, axis) {
|
||||
switch (axis) {
|
||||
case 0: return [arr[1], arr[2]]; break;
|
||||
case 1: return [arr[0], arr[2]]; break;
|
||||
case 2: return [arr[0], arr[1]]; break;
|
||||
}
|
||||
}
|
||||
function addVector(u, v, axis, w) {
|
||||
switch (axis) {
|
||||
case 0: vertices.push(w, u, v); break;
|
||||
case 1: vertices.push(u, w, v); break;
|
||||
case 2: vertices.push(u, v, w); break;
|
||||
}
|
||||
}
|
||||
|
||||
function addFace(name, uv_offset, axis, side) {
|
||||
|
||||
var start = getVector2(from, axis)
|
||||
var end = getVector2(to, axis)
|
||||
var face = cube.faces[name];
|
||||
var texture = face.getTexture();
|
||||
if (texture === null) return;
|
||||
|
||||
var px_x = texture ? Project.texture_width / texture.width : 1;
|
||||
var px_y = texture ? Project.texture_height / texture.height : 1;
|
||||
var uv_size = [
|
||||
Math.abs(face.uv_size[0]),
|
||||
Math.abs(face.uv_size[1])
|
||||
]
|
||||
uv_offset = [
|
||||
uv_offset[0] == true
|
||||
? (face.uv_size[0] > 0 ? (px_x-face.uv[2]) : ( face.uv[2]))
|
||||
: (face.uv_size[0] > 0 ? ( face.uv[0]) : (px_x-face.uv[0])),
|
||||
uv_offset[1] == true
|
||||
? (face.uv_size[1] > 0 ? (px_y-face.uv[3]) : ( face.uv[3]))
|
||||
: (face.uv_size[1] > 0 ? ( face.uv[1]) : (px_y-face.uv[1]))
|
||||
]
|
||||
uv_offset[0] = uv_offset[0] % px_x;
|
||||
uv_offset[1] = uv_offset[1] % px_y;
|
||||
|
||||
if ((face.rotation % 180 == 90) != (axis == 0)) {
|
||||
uv_size.reverse();
|
||||
uv_offset.reverse();
|
||||
}
|
||||
|
||||
var w = side == 0 ? from[axis] : to[axis]
|
||||
|
||||
//Columns
|
||||
var width = end[0]-start[0];
|
||||
var step = Math.abs( width / uv_size[0] );
|
||||
if (texture) step *= Project.texture_width / texture.width;
|
||||
if (step < epsilon) step = epsilon;
|
||||
|
||||
for (var col = start[0] - uv_offset[0]; col <= end[0]; col += step) {
|
||||
if (col >= start[0]) {
|
||||
addVector(col, start[1], axis, w);
|
||||
addVector(col, end[1], axis, w);
|
||||
}
|
||||
}
|
||||
|
||||
//lines
|
||||
var height = end[1]-start[1];
|
||||
var step = Math.abs( height / uv_size[1] );
|
||||
let tex_height = texture.frameCount ? (texture.height / texture.frameCount) : texture.height;
|
||||
if (texture) step *= Project.texture_height / tex_height;
|
||||
if (step < epsilon) step = epsilon;
|
||||
|
||||
for (var line = start[1] - uv_offset[1]; line <= end[1]; line += step) {
|
||||
if (line >= start[1]) {
|
||||
addVector(start[0], line, axis, w);
|
||||
addVector(end[0], line, axis, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addFace('north', [true, true], 2, 0);
|
||||
addFace('south', [false, true], 2, 1);
|
||||
addFace('west', [false, true], 0, 0);
|
||||
addFace('east', [true, true], 0, 1);
|
||||
addFace('down', [false, true], 1, 0);
|
||||
addFace('up', [false, false], 1, 1);
|
||||
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
|
||||
|
||||
var lines = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color: gizmo_colors.grid}));
|
||||
lines.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2]);
|
||||
lines.no_export = true;
|
||||
|
||||
return lines;
|
||||
},
|
||||
updatePaintingGrid() {
|
||||
Cube.all.forEach(cube => {
|
||||
Canvas.buildGridBox(cube)
|
||||
Outliner.elements.forEach(element => {
|
||||
if (element.preview_controller.updatePaintingGrid) {
|
||||
element.preview_controller.updatePaintingGrid(element);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -456,8 +456,9 @@ const Painter = {
|
||||
},
|
||||
runMirrorBrush(texture, x, y, event, uvTag) {
|
||||
if (uvTag && Painter.current.element) {
|
||||
let mirror_cube = Painter.getMirrorCube(Painter.current.element);
|
||||
if (mirror_cube) {
|
||||
let mirror_element = Painter.getMirrorCube(Painter.current.element);
|
||||
let even_brush_size = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brushTool;
|
||||
if (mirror_element instanceof Cube) {
|
||||
|
||||
let uvFactorX = 1 / Project.texture_width * texture.img.naturalWidth;
|
||||
let uvFactorY = 1 / Project.texture_height * texture.img.naturalHeight;
|
||||
@ -465,7 +466,7 @@ const Painter = {
|
||||
let face = Painter.current.face;
|
||||
let side_face = (face === 'west' || face === 'east')
|
||||
if (side_face) face = CubeFace.opposite[face];
|
||||
face = mirror_cube.faces[face];
|
||||
face = mirror_element.faces[face];
|
||||
|
||||
if (side_face &&
|
||||
uvTag[1] === face.uv[1] && uvTag[3] === face.uv[3] &&
|
||||
@ -481,7 +482,7 @@ const Painter = {
|
||||
//calculate new point
|
||||
if (face.uv[0] > face.uv[0+2] == uvTag[0] > uvTag[0+2]) {
|
||||
point_on_uv[0] = Math.max(face.uv[0], face.uv[0+2]) * uvFactorX - point_on_uv[0] - 1;
|
||||
if (BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brushTool) point_on_uv[0] += 1
|
||||
if (even_brush_size) point_on_uv[0] += 1
|
||||
} else {
|
||||
point_on_uv[0] = Math.min(face.uv[0], face.uv[0+2]) * uvFactorX + point_on_uv[0];
|
||||
}
|
||||
@ -492,9 +493,50 @@ const Painter = {
|
||||
}
|
||||
|
||||
let cube = Painter.current.element;
|
||||
Painter.current.element = mirror_cube;
|
||||
Painter.current.element = mirror_element;
|
||||
Painter.useBrushlike(texture, ...point_on_uv, event, face.uv, true, true);
|
||||
Painter.current.element = cube;
|
||||
|
||||
} else if (mirror_element instanceof Mesh) {
|
||||
|
||||
let mesh = mirror_element;
|
||||
|
||||
let clicked_face = mesh.faces[Painter.current.face];
|
||||
let normal = clicked_face.getNormal(true);
|
||||
let center = clicked_face.getCenter();
|
||||
let e = 0.01;
|
||||
let face;
|
||||
for (let fkey in mesh.faces) {
|
||||
let normal2 = mesh.faces[fkey].getNormal(true);
|
||||
let center2 = mesh.faces[fkey].getCenter();
|
||||
if (
|
||||
Math.epsilon(normal[0], -normal2[0], e) && Math.epsilon(normal[1], normal2[1], e) && Math.epsilon(normal[2], normal2[2], e) &&
|
||||
Math.epsilon(center[0], -center2[0], e) && Math.epsilon(center[1], center2[1], e) && Math.epsilon(center[2], center2[2], e)
|
||||
) {
|
||||
face = mesh.faces[fkey];
|
||||
}
|
||||
}
|
||||
if (!face) return;
|
||||
|
||||
if (!even_brush_size) {
|
||||
x += 0.5; y += 0.5;
|
||||
}
|
||||
let world_coord = mesh.mesh.localToWorld(clicked_face.UVToLocal([x, y]));
|
||||
world_coord.x *= -1;
|
||||
mesh.mesh.worldToLocal(world_coord);
|
||||
let point_on_uv = face.localToUV(world_coord);
|
||||
|
||||
if (even_brush_size) {
|
||||
point_on_uv = point_on_uv.map(v => Math.round(v))
|
||||
} else {
|
||||
point_on_uv = point_on_uv.map(v => Math.floor(v))
|
||||
}
|
||||
console.log([x, y], point_on_uv)
|
||||
|
||||
let old_mesh = Painter.current.element;
|
||||
Painter.current.element = mesh;
|
||||
Painter.useBrushlike(texture, ...point_on_uv, event, face.uv, true, true);
|
||||
Painter.current.element = old_mesh;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -755,24 +797,36 @@ const Painter = {
|
||||
added.a = original_a
|
||||
return mix;
|
||||
},
|
||||
getMirrorCube(cube) {
|
||||
getMirrorCube(element) {
|
||||
let center = Format.centered_grid ? 0 : 8;
|
||||
let e = 0.002
|
||||
if (cube.from[0]-center === center-cube.to[0] && !cube.rotation[1] && !cube.rotation[2]) {
|
||||
return cube;
|
||||
} else {
|
||||
for (var cube2 of Cube.all) {
|
||||
if (
|
||||
cube.inflate === cube2.inflate &&
|
||||
Math.epsilon(cube.from[2], cube2.from[2], e) && Math.epsilon(cube.to[2], cube2.to[2], e) &&
|
||||
Math.epsilon(cube.from[1], cube2.from[1], e) && Math.epsilon(cube.to[1], cube2.to[1], e) &&
|
||||
Math.epsilon(cube.size(0), cube2.size(0), e) && Math.epsilon(cube.to[0]-center, center-cube2.from[0], e)
|
||||
) {
|
||||
return cube2;
|
||||
if (element instanceof Cube) {
|
||||
if (element.from[0]-center === center-element.to[0] && !element.rotation[1] && !element.rotation[2]) {
|
||||
return element;
|
||||
} else {
|
||||
for (var element2 of Cube.all) {
|
||||
if (
|
||||
element.inflate === element2.inflate &&
|
||||
Math.epsilon(element.from[2], element2.from[2], e) && Math.epsilon(element.to[2], element2.to[2], e) &&
|
||||
Math.epsilon(element.from[1], element2.from[1], e) && Math.epsilon(element.to[1], element2.to[1], e) &&
|
||||
Math.epsilon(element.size(0), element2.size(0), e) && Math.epsilon(element.to[0]-center, center-element2.from[0], e)
|
||||
) {
|
||||
return element2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (element instanceof Mesh) {
|
||||
if (element instanceof Mesh && element.origin[0] === center && !element.rotation[1] && !element.rotation[2]) {
|
||||
return element;
|
||||
} else {
|
||||
for (var element2 of Mesh.all) {
|
||||
if (Object.keys(element.vertices).length !== Object.keys(element2.vertices).length) continue;
|
||||
return element2;
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
updateNslideValues() {
|
||||
BarItems.slider_brush_size.update()
|
||||
|
@ -486,6 +486,9 @@ const UVEditor = {
|
||||
i++;
|
||||
}
|
||||
},
|
||||
forElements(cb) {
|
||||
this.getMappableElements().forEach(cb);
|
||||
},
|
||||
//Load
|
||||
loadSelectedFace() {
|
||||
this.face = $('#uv_panel_sides input:checked').attr('id').replace('_radio', '')
|
||||
@ -884,28 +887,54 @@ const UVEditor = {
|
||||
},
|
||||
mirrorX(event) {
|
||||
var scope = this;
|
||||
this.forCubes(obj => {
|
||||
this.forElements(obj => {
|
||||
scope.getFaces(obj, event).forEach(function(side) {
|
||||
var proxy = obj.faces[side].uv[0]
|
||||
obj.faces[side].uv[0] = obj.faces[side].uv[2]
|
||||
obj.faces[side].uv[2] = proxy
|
||||
if (obj instanceof Cube) {
|
||||
var proxy = obj.faces[side].uv[0]
|
||||
obj.faces[side].uv[0] = obj.faces[side].uv[2]
|
||||
obj.faces[side].uv[2] = proxy
|
||||
} else if (obj instanceof Mesh) {
|
||||
let center = 0;
|
||||
let count = 0;
|
||||
obj.faces[side].vertices.forEach(vkey => {
|
||||
center += obj.faces[side].uv[vkey][0];
|
||||
count++;
|
||||
})
|
||||
center /= count;
|
||||
obj.faces[side].vertices.forEach(vkey => {
|
||||
obj.faces[side].uv[vkey][0] = center*2 - obj.faces[side].uv[vkey][0];
|
||||
})
|
||||
}
|
||||
})
|
||||
obj.autouv = 0
|
||||
Canvas.updateUV(obj)
|
||||
if (obj.autouv) obj.autouv = 0
|
||||
obj.preview_controller.updateUV(obj);
|
||||
})
|
||||
this.message('uv_editor.mirrored')
|
||||
this.loadData()
|
||||
},
|
||||
mirrorY(event) {
|
||||
var scope = this;
|
||||
this.forCubes(obj => {
|
||||
this.forElements(obj => {
|
||||
scope.getFaces(obj, event).forEach(function(side) {
|
||||
var proxy = obj.faces[side].uv[1]
|
||||
obj.faces[side].uv[1] = obj.faces[side].uv[3]
|
||||
obj.faces[side].uv[3] = proxy
|
||||
if (obj instanceof Cube) {
|
||||
var proxy = obj.faces[side].uv[1]
|
||||
obj.faces[side].uv[1] = obj.faces[side].uv[3]
|
||||
obj.faces[side].uv[3] = proxy
|
||||
} else if (obj instanceof Mesh) {
|
||||
let center = 0;
|
||||
let count = 0;
|
||||
obj.faces[side].vertices.forEach(vkey => {
|
||||
center += obj.faces[side].uv[vkey][1];
|
||||
count++;
|
||||
})
|
||||
center /= count;
|
||||
obj.faces[side].vertices.forEach(vkey => {
|
||||
obj.faces[side].uv[vkey][1] = center*2 - obj.faces[side].uv[vkey][1];
|
||||
})
|
||||
}
|
||||
})
|
||||
obj.autouv = 0
|
||||
Canvas.updateUV(obj)
|
||||
if (obj.autouv) obj.autouv = 0
|
||||
obj.preview_controller.updateUV(obj);
|
||||
})
|
||||
this.message('uv_editor.mirrored')
|
||||
this.loadData()
|
||||
@ -1161,62 +1190,77 @@ const UVEditor = {
|
||||
'_',
|
||||
'copy',
|
||||
'paste',
|
||||
{icon: 'photo_size_select_large', name: 'menu.uv.mapping', condition: () => !Project.box_uv, children: function(editor) { return [
|
||||
{icon: UVEditor.getReferenceFace().enabled!==false ? 'check_box' : 'check_box_outline_blank', name: 'generic.export', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.toggleUV(event)
|
||||
Undo.finishEdit('Toggle UV export')
|
||||
}},
|
||||
'uv_maximize',
|
||||
'uv_auto',
|
||||
'uv_rel_auto',
|
||||
{icon: 'rotate_90_degrees_ccw', condition: () => Format.uv_rotation, name: 'menu.uv.mapping.rotation', children: function() {
|
||||
var off = 'radio_button_unchecked'
|
||||
var on = 'radio_button_checked'
|
||||
let reference_face = UVEditor.getReferenceFace()
|
||||
return [
|
||||
{icon: (!reference_face.rotation ? on : off), name: '0°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(0)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 90 ? on : off), name: '90°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(90)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 180 ? on : off), name: '180°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(180)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 270 ? on : off), name: '270°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(270)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}}
|
||||
]
|
||||
}},
|
||||
'uv_turn_mapping',
|
||||
{
|
||||
icon: (UVEditor.getReferenceFace().uv[0] > UVEditor.getReferenceFace().uv[2] ? 'check_box' : 'check_box_outline_blank'),
|
||||
name: 'menu.uv.mapping.mirror_x',
|
||||
click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.mirrorX(event)
|
||||
Undo.finishEdit('Mirror UV')
|
||||
{icon: 'photo_size_select_large', name: 'menu.uv.mapping', condition: () => !Project.box_uv, children: function(editor) {
|
||||
let reference_face = UVEditor.getReferenceFace();
|
||||
function isMirrored(axis) {
|
||||
if (reference_face instanceof CubeFace) {
|
||||
reference_face.uv[axis+0] > reference_face.uv[axis+2]
|
||||
} else {
|
||||
let vertices = reference_face.getSortedVertices();
|
||||
if (vertices.length <= 2) return false;
|
||||
if (!Math.epsilon(reference_face.uv[vertices[0]][axis], reference_face.uv[vertices[1]][axis], 0.01)) {
|
||||
return reference_face.uv[vertices[0]][axis] > reference_face.uv[vertices[1]][axis];
|
||||
} else {
|
||||
return reference_face.uv[vertices[0]][axis] > reference_face.uv[vertices[2]][axis];
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: (UVEditor.getReferenceFace().uv[1] > UVEditor.getReferenceFace().uv[3] ? 'check_box' : 'check_box_outline_blank'),
|
||||
name: 'menu.uv.mapping.mirror_y',
|
||||
click: function() {
|
||||
}
|
||||
return [
|
||||
{icon: reference_face.enabled!==false ? 'check_box' : 'check_box_outline_blank', name: 'generic.export', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.mirrorY(event)
|
||||
Undo.finishEdit('Mirror UV')
|
||||
}
|
||||
},
|
||||
]}},
|
||||
UVEditor.toggleUV(event)
|
||||
Undo.finishEdit('Toggle UV export')
|
||||
}},
|
||||
'uv_maximize',
|
||||
'uv_auto',
|
||||
'uv_rel_auto',
|
||||
{icon: 'rotate_90_degrees_ccw', condition: () => Format.uv_rotation, name: 'menu.uv.mapping.rotation', children: function() {
|
||||
var off = 'radio_button_unchecked'
|
||||
var on = 'radio_button_checked'
|
||||
return [
|
||||
{icon: (!reference_face.rotation ? on : off), name: '0°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(0)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 90 ? on : off), name: '90°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(90)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 180 ? on : off), name: '180°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(180)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}},
|
||||
{icon: (reference_face.rotation === 270 ? on : off), name: '270°', click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.setRotation(270)
|
||||
Undo.finishEdit('Rotate UV')
|
||||
}}
|
||||
]
|
||||
}},
|
||||
'uv_turn_mapping',
|
||||
{
|
||||
icon: (isMirrored(0) ? 'check_box' : 'check_box_outline_blank'),
|
||||
name: 'menu.uv.mapping.mirror_x',
|
||||
click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.mirrorX(event)
|
||||
Undo.finishEdit('Mirror UV')
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: (isMirrored(1) ? 'check_box' : 'check_box_outline_blank'),
|
||||
name: 'menu.uv.mapping.mirror_y',
|
||||
click: function() {
|
||||
Undo.initEdit({elements: Cube.selected, uv_only: true})
|
||||
UVEditor.mirrorY(event)
|
||||
Undo.finishEdit('Mirror UV')
|
||||
}
|
||||
},
|
||||
]
|
||||
}},
|
||||
'face_tint',
|
||||
{icon: 'flip_to_back', condition: () => (Format.id == 'java_block'&& Cube.selected.length), name: 'action.cullface' , children: function() {
|
||||
var off = 'radio_button_unchecked';
|
||||
|
Loading…
Reference in New Issue
Block a user