UV Editor fixes

master
JannisX11 2021-09-01 22:13:04 +02:00
parent 1eedbe3e65
commit eaafbf7ad0
11 changed files with 164 additions and 419 deletions

BIN
assets/missing_blend.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1192,7 +1192,7 @@
background-color: rgba(50, 70, 240, 0.3);
z-index: 3;
}
.cube_uv_face.selected {
.cube_uv_face.selected:not(.unselected) {
border-color: var(--color-light);
z-index: 4;
box-shadow: 0 0 4px #000000cc;

View File

@ -186,10 +186,10 @@ class Keybind {
isTriggered(event) {
return (
(this.key === event.which || (this.key == 1001 && event instanceof MouseEvent)) &&
(this.ctrl === (event.ctrlKey || Pressing.overrides.ctrl) || this.ctrl == null ) &&
(this.shift === (event.shiftKey || Pressing.overrides.shift)|| this.shift == null ) &&
(this.alt === (event.altKey || Pressing.overrides.alt) || this.alt == null ) &&
(this.meta === event.metaKey || this.meta == null )
(this.ctrl === (event.ctrlKey || Pressing.overrides.ctrl) || this.ctrl === null ) &&
(this.shift === (event.shiftKey || Pressing.overrides.shift)|| this.shift === null ) &&
(this.alt === (event.altKey || Pressing.overrides.alt) || this.alt === null ) &&
(this.meta === event.metaKey || this.meta === null )
)
}
record() {
@ -266,7 +266,7 @@ Keybinds.loadKeymap = function(id, from_start_screen = false) {
if (preset && preset.keys[item.id] !== undefined) {
let keys = preset.keys[item.id]
if (keys == null) {
if (keys === null) {
item.keybind.clear();
} else if (keys) {
if (isApp && Blockbench.platform == 'darwin' && keys.ctrl && !keys.meta) {
@ -377,7 +377,7 @@ BARS.defineActions(() => {
let {keys} = JSON.parse(files[0].content);
Keybinds.actions.forEach(keybind_item => {
if (keys[keybind_item.id] == null) {
if (keys[keybind_item.id] === null) {
keybind_item.keybind.clear();
} else {
keybind_item.keybind.set(keys[keybind_item.id]).save(false);

View File

@ -1012,7 +1012,7 @@ new NodePreviewController(Cube, {
Canvas.face_order.forEach((face, fIndex) => {
if (cube.faces[face].texture == null) return;
if (cube.faces[face].texture === null) return;
stretch = 1;
frame = 0;

View File

@ -385,7 +385,7 @@ class Group extends OutlinerNode {
cb(this)
}
while (i < this.children.length) {
if (!type || this.children[i] instanceof type) {
if (!type || (type instanceof Array ? type.find(t2 => this.children[i] instanceof t2) : this.children[i] instanceof type)) {
cb(this.children[i])
}
if (this.children[i].type === 'group') {

View File

@ -1,5 +1,3 @@
const { fdatasync } = require("original-fs");
class MeshFace extends Face {
constructor(mesh, data) {
super(data);
@ -324,7 +322,7 @@ class Mesh extends OutlinerElement {
Mesh.prototype.needsUniqueName = false;
Mesh.prototype.menu = new Menu([
'extrude_mesh_selection',
'split_faces',
'loop_cut',
'create_face',
'invert_face',
'_',
@ -901,7 +899,7 @@ BARS.defineActions(function() {
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
}
})
new Action('split_faces', {
new Action('loop_cut', {
icon: 'carpenter',
category: 'edit',
keybind: new Keybind({key: 'r', shift: true}),
@ -1237,9 +1235,13 @@ BARS.defineActions(function() {
}
for (let i = 0; i < result.sides; i++) {
let [a, b] = vertex_keys.slice(i+2, i+2 + 2);
if (!b) {
if (!a) {
b = vertex_keys[2];
a = vertex_keys[1];
} else if (!b) {
b = vertex_keys[1];
}
console.log(a, b, m)
mesh.addFaces(new MeshFace( mesh, {vertices: [a, b, m]} ));
}
}

View File

@ -687,7 +687,7 @@ const Canvas = {
}
face_list.forEach(function(f, fIndex) {
if (cube.faces[f.face].texture == null) return;
if (cube.faces[f.face].texture === null) return;
var uv= [
f.from[0] + cube.uv_offset[0],
@ -734,7 +734,7 @@ const Canvas = {
Canvas.face_order.forEach((face, fIndex) => {
if (cube.faces[face].texture == null) return;
if (cube.faces[face].texture === null) return;
stretch = 1;
frame = 0;
@ -831,7 +831,7 @@ const Canvas = {
var end = getVector2(to, axis)
var face = cube.faces[name];
var texture = face.getTexture();
if (texture == null) return;
if (texture === null) return;
var px_x = texture ? Project.texture_width / texture.width : 1;
var px_y = texture ? Project.texture_height / texture.height : 1;

View File

@ -1423,7 +1423,7 @@ Blockbench.on('update_camera_position', e => {
Preview.all.forEach(preview => {
if (preview.canvas.isConnected && Mesh.all.length) {
preview.raycaster.params.Points.threshold = scale * 0.6;
preview.raycaster.params.Line.threshold = scale * 0.6;
preview.raycaster.params.Line.threshold = scale * 0.3;
}
})
})

View File

@ -1218,14 +1218,12 @@ function loadTextureDraggable() {
if (!tex) return;
if ($('.preview:hover').length > 0) {
var data = Canvas.raycast(event)
if (data.cube && data.face) {
var cubes_list = data.cube.selected ? Cube.selected : [data.cube];
if (tex && cubes_list) {
Undo.initEdit({elements: cubes_list})
cubes_list.forEach(cube => {
if (cube instanceof Cube) {
cube.applyTexture(tex, data.shiftKey || Pressing.overrides.shift || [data.face])
}
if (data.element && data.face) {
var elements = data.element.selected ? UVEditor.getMappableElements() : [data.element];
if (tex && elements) {
Undo.initEdit({elements})
elements.forEach(element => {
element.applyTexture(tex, data.shiftKey || Pressing.overrides.shift || [data.face])
})
Undo.finishEdit('Apply texture')
}
@ -1246,7 +1244,7 @@ function loadTextureDraggable() {
Texture.all.splice(index, 0, tex)
Canvas.updateLayeredTextures()
Undo.finishEdit('Reorder textures')
} else if ($('#cubes_list:hover')) {
} else if ($('#cubes_list:hover').length) {
var target_node = $('#cubes_list li.outliner_node.drag_hover').last().get(0);
$('.drag_hover').removeClass('drag_hover');
@ -1259,7 +1257,7 @@ function loadTextureDraggable() {
if (target.type === 'group') {
target.forEachChild(function(cube) {
array.push(cube)
}, Cube)
}, [Cube, Mesh])
} else {
array = selected.includes(target) ? selected : [target];
}
@ -1273,6 +1271,8 @@ function loadTextureDraggable() {
UVEditor.loadData()
Canvas.updateAllFaces()
} else if ($('#uv_viewport:hover').length) {
UVEditor.applyTexture(tex);
}
}, 10)
}

View File

@ -12,10 +12,9 @@ function switchBoxUV(state) {
cube.uv_offset[1] = cube.faces.up.uv[3];
})
}
$('#uv_panel_sides').toggle(!state)
UVEditor.vue.box_uv = state;
//UVEditor.setGrid(1).setSize(UVEditor.size)//.displayAllMappingOverlays();
Canvas.updateAllUVs()
UVEditor.setGrid(1);
Canvas.updateAllUVs();
}
const UVEditor = {
@ -32,142 +31,6 @@ const UVEditor = {
get vue() {
return this.panel.inside_vue;
},
/*
buildDom(toolbar) {
var scope = this
if (this.jquery.main) {
this.jquery.main.detach()
}
this.jquery.main = $('<div class="UVEditor" id="UVEditor_' + scope.id + '"></div>')
if (this.headline) {
this.jquery.main.append('<div class="uv_headline"><div class="uv_title">'+capitalizeFirstLetter(scope.id)+'</div><div class="tool"><i class="material-icons">fullscreen</i><div class="tooltip">Fullscreen</div></div></div>')
this.jquery.main.find('div.uv_headline > .tool').click(function() {
UVEditor.openTab(scope.id)
})
this.jquery.main.find('div.uv_headline').click(function(event) {
event.stopPropagation()
UVEditor.select(scope.id, event)
})
}
this.jquery.viewport = $('<div id="uv_viewport" class="checkerboard_target"></div>')
this.jquery.transform_info = $('<div class="uv_transform_info"></div>')
this.jquery.main.append(this.jquery.transform_info)
this.jquery.main.append(this.jquery.viewport)
this.jquery.frame = $(`<div id="uv_frame">
<div id="uv_size">
<div class="uv_size_handle"></div>
</div>
</div>`);
this.img = new Image();
this.img.style.objectFit = Format.animated_textures ? 'cover' : 'fill';
this.jquery.frame.append(this.img)
this.jquery.size = this.jquery.frame.find('div#uv_size')
this.jquery.viewport.append(this.jquery.frame)
if (Blockbench.browser === 'firefox') {
this.jquery.frame.css('image-rendering', '-moz-crisp-edges')
}
if (Toolbox.selected.paintTool) {
this.jquery.size.hide()
}
this.jquery.main.toggleClass('checkerboard_trigger', settings.uv_checkerboard.value);
this.jquery.sliders = $('<div class="bar uv_editor_sliders" style="margin-left: 2px;"></div>')
this.jquery.main.append(this.jquery.sliders)
this.jquery.frame.droppable({
accept: 'li.texture',
tolerance: 'pointer',
drop: function(event, ui) {
if (Cube.selected.length == 0) {
return
}
var id = $(ui.helper).attr('texid')
scope.applyTexture(id)
}
})
this.jquery.size.mouseenter(function() {
scope.displayMappingOverlay()
})
this.jquery.size.mouseleave(function() {
$(this).find('.uv_mapping_overlay').remove()
})
this.jquery.viewport.contextmenu(function(event) {
scope.contextMenu()
})
this.jquery.viewport.on('mousedown touchstart', function(event) {
if (Toolbox.selected.paintTool && (event.which === 1 || (event.touches && event.touches.length == 1))) {
scope.startPaintTool(event)
}
})
this.jquery.viewport.on('mousewheel', function(e) {
let event = e.originalEvent;
if (event.ctrlOrCmd) {
event.stopPropagation()
var n = (event.deltaY < 0) ? 0.1 : -0.1;
n *= scope.zoom
var number = limitNumber(scope.zoom + n, 1, scope.max_zoom)
let old_zoom = scope.zoom;
scope.setZoom(number)
event.preventDefault()
let offset = scope.jquery.viewport.offset()
let offsetX = event.clientX - offset.left;
let offsetY = event.clientY - offset.top;
let zoom_diff = scope.zoom - old_zoom;
this.scrollLeft += ((this.scrollLeft + offsetX) * zoom_diff) / old_zoom
this.scrollTop += ((this.scrollTop + offsetY) * zoom_diff) / old_zoom
scope.updateBrushOutline(e)
return false;
}
})
.on('scroll', e => {
scope.updateDragHandle()
})
var dMWCoords = {x: 0, y: 0}
function dragMouseWheel(e) {
scope.jquery.viewport[0].scrollLeft -= (e.pageX - dMWCoords.x)
scope.jquery.viewport[0].scrollTop -= (e.pageY - dMWCoords.y)
dMWCoords = {x: e.pageX, y: e.pageY}
}
function dragMouseWheelStop(e) {
removeEventListeners(document, 'mousemove touchmove', dragMouseWheel);
removeEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
}
scope.jquery.viewport.on('mousedown touchstart', function(e) {
if (e.which === 2) {
addEventListeners(document, 'mousemove touchmove', dragMouseWheel);
addEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
dMWCoords = {x: e.pageX, y: e.pageY}
e.preventDefault();
return false;
}
})
//Paint brush outline
this.brush_outline = $('<div id="uv_brush_outline"></div>');
scope.jquery.frame.on('mouseenter mousemove', e => {
this.updateBrushOutline(e)
})
scope.jquery.frame.on('mouseleave', e => {
this.brush_outline.detach();
})
this.setSize(this.size)
return this;
},
*/
message(msg, vars) {
msg = tl(msg, vars)
let box = document.createElement('div');
@ -596,9 +459,6 @@ const UVEditor = {
this.displayTexture(face)
this.displayFrame()//and transform info
if (this !== UVEditor && this.face === UVEditor.face) {
UVEditor.loadData()
}
},
save(pos_only) {
if (!Modes.edit) return;
@ -654,11 +514,15 @@ const UVEditor = {
UVEditor.loadData()
}
},
applyTexture(uuid) {
var scope = this;
Undo.initEdit({elements: Cube.selected, uv_only: true})
this.forCubes(obj => {
obj.faces[scope.face].texture = uuid
applyTexture(texture) {
let elements = this.getMappableElements();
Undo.initEdit({elements, uv_only: true})
elements.forEach(el => {
this.vue.selected_faces.forEach(face => {
if (el.faces[face]) {
el.faces[face].texture = texture.uuid;
}
})
})
this.loadData()
Canvas.updateSelectedFaces()
@ -724,12 +588,6 @@ const UVEditor = {
BarItems.face_tint.setIcon(face.tint !== -1 ? 'check_box' : 'check_box_outline_blank')
BarItems.slider_face_tint.update()
},
contextMenu(event) {
var scope = this;
UVEditor.getReferenceFace() = Cube.selected[0] && Cube.selected[0].faces[scope.face];
UVEditor.menu.open(event, UVEditor)
return UVEditor;
},
slidePos(modify, axis) {
var scope = this
var limit = scope.getResolution(axis);
@ -1227,57 +1085,6 @@ const UVEditor = {
}
}*/
},
/*
copy(event) {
let element = UVEditor.getMappableElements()[0]
if (!element) return;
UVEditor.clipboard = []
function addToClipboard(face) {
var tag = element.faces[face]
if (tag && element instanceof Cube) {
UVEditor.clipboard.push(new CubeFace(null, tag))
} else if (tag && element instanceof Mesh) {
UVEditor.clipboard.push(new MeshFace(null, tag))
}
}
UVEditor.vue.selected_faces.forEach(function(s) {
addToClipboard(s)
UVEditor.message('uv_editor.copied')
})
},
paste(event) {
if (UVEditor.clipboard === null || Cube.selected.length === 0) return;
function applyFace(tag, face) {
if (!face) face = tag.face
UVEditor.getMappableElements().forEach(function(obj) {
if (obj.faces[face]) {
obj.faces[face].extend(tag);
Canvas.updateUV(obj);
}
})
}
if (UVEditor.vue.selected_faces.length === 1) {
applyFace(UVEditor.clipboard[0], UVEditor.vue.selected_faces[0])
} else {
if (UVEditor.clipboard.length === 1) {
UVEditor.vue.selected_faces.forEach(function(s) {
applyFace(UVEditor.clipboard[0], s)
})
} else {
UVEditor.clipboard.forEach(function(s) {
if (UVEditor.vue.selected_faces.includes(s.face)) {
applyFace(s)
}
})
}
}
UVEditor.message('uv_editor.pasted')
UVEditor.loadData()
},*/
menu: new Menu([
@ -1302,23 +1109,24 @@ const UVEditor = {
{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: (!UVEditor.getReferenceFace().rotation ? on : off), name: '0&deg;', click: function() {
{icon: (!reference_face.rotation ? on : off), name: '0&deg;', click: function() {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.setRotation(0)
Undo.finishEdit('Rotate UV')
}},
{icon: (UVEditor.getReferenceFace().rotation === 90 ? on : off), name: '90&deg;', click: function() {
{icon: (reference_face.rotation === 90 ? on : off), name: '90&deg;', click: function() {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.setRotation(90)
Undo.finishEdit('Rotate UV')
}},
{icon: (UVEditor.getReferenceFace().rotation === 180 ? on : off), name: '180&deg;', click: function() {
{icon: (reference_face.rotation === 180 ? on : off), name: '180&deg;', click: function() {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.setRotation(180)
Undo.finishEdit('Rotate UV')
}},
{icon: (UVEditor.getReferenceFace().rotation === 270 ? on : off), name: '270&deg;', click: function() {
{icon: (reference_face.rotation === 270 ? on : off), name: '270&deg;', click: function() {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.setRotation(270)
Undo.finishEdit('Rotate UV')
@ -1388,7 +1196,7 @@ const UVEditor = {
arr.push({
name: t.name,
icon: (t.mode === 'link' ? t.img : t.source),
click: function() {UVEditor.applyTexture(t.uuid)}
click: function() {UVEditor.applyTexture(t)}
})
})
return arr;
@ -1711,7 +1519,7 @@ Interface.definePanels(function() {
}
}
}
if (texture == null) {
if (texture === null) {
this.texture = UVEditor.texture = null;
} else if (texture instanceof Texture) {
this.texture = texture;
@ -1788,6 +1596,7 @@ Interface.definePanels(function() {
}
},
contextMenu(event) {
if (!UVEditor.getReferenceFace()) return;
UVEditor.menu.open(event);
},
selectFace(key, event, keep_selection) {
@ -1823,138 +1632,121 @@ Interface.definePanels(function() {
}
}
},
dragFace(face_key, event) {
drag({event, onDrag, onEnd}) {
if (event.which == 2 || event.which == 3) return;
this.selectFace(face_key, event, true);
let scope = this;
let elements = this.mappable_elements;
Undo.initEdit({elements, uv_only: true})
elements.forEach(el => {
if (el instanceof Mesh) {
delete Project.selected_vertices[el.uuid];
}
})
let pos = [0, 0];
let last_pos = [0, 0];
let offset;
if (Project.box_uv) {
offset = function(element, x, y) {
element.uv_offset[0] += x;
element.uv_offset[1] += y;
}
} else {
offset = function(element, x, y) {
scope.selected_faces.forEach(key => {
if (element.faces[key] && element instanceof Cube) {
element.faces[key].uv[0] += x;
element.faces[key].uv[1] += y;
element.faces[key].uv[2] += x;
element.faces[key].uv[3] += y;
}
})
}
}
function drag(e1) {
let step_x = (scope.inner_width / UVEditor.getResolution(0) / UVEditor.grid);
let step_y = (scope.inner_height / UVEditor.getResolution(1) / UVEditor.grid);
let snap = UVEditor.grid / canvasGridSize(e1.shiftKey || Pressing.overrides.shift, e1.ctrlOrCmd || Pressing.overrides.ctrl);
let step_x = (scope.inner_width / UVEditor.getResolution(0) / snap);
let step_y = (scope.inner_height / UVEditor.getResolution(1) / snap);
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / UVEditor.grid;
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / UVEditor.grid;
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap;
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap;
if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) {
elements.forEach(element => {
if (element instanceof Mesh) {
scope.selected_faces.forEach(key => {
let face = element.faces[key];
if (!face) return;
face.vertices.forEach(vertex_key => {
face.uv[vertex_key][0] += pos[0] - last_pos[0];
face.uv[vertex_key][1] += pos[1] - last_pos[1];
})
})
} else {
offset(element, pos[0] - last_pos[0], pos[1] - last_pos[1]);
}
})
onDrag(pos[0] - last_pos[0], pos[1] - last_pos[1])
last_pos.replace(pos);
UVEditor.displaySliders();
UVEditor.loadData();
UVEditor.vue.$forceUpdate();
Canvas.updateView({elements, element_aspects: {uv: true}});
scope.dragging_uv = true;
}
UVEditor.displaySliders();
UVEditor.loadData();
UVEditor.vue.$forceUpdate();
Canvas.updateView({elements: scope.mappable_elements, element_aspects: {uv: true}});
scope.dragging_uv = true;
}
function stop(e1) {
removeEventListeners(document, 'mousemove touchmove', drag);
removeEventListeners(document, 'mouseup touchend', stop);
UVEditor.disableAutoUV()
Undo.finishEdit('Move UV')
setTimeout(() => scope.dragging_uv = false, 10);
}
addEventListeners(document, 'mousemove touchmove', drag);
addEventListeners(document, 'mouseup touchend', stop);
},
resizeFace(face_key, event, x_side, y_side) {
if (event.which == 2 || event.which == 3) return;
event.stopPropagation();
let scope = this;
let elements = this.mappable_elements;
Undo.initEdit({elements, uv_only: true})
let pos = [0, 0];
let last_pos = [0, 0];
let offset = function(element, x, y) {
scope.selected_faces.forEach(key => {
if (element.faces[key] && element instanceof Cube) {
if (x_side == -1) element.faces[key].uv[0] += x;
if (y_side == -1) element.faces[key].uv[1] += y;
if (x_side == 1) element.faces[key].uv[2] += x;
if (y_side == 1) element.faces[key].uv[3] += y;
}
})
element.uv_offset[0] += x;
element.uv_offset[1] += y;
}
function drag(e1) {
let step_x = (scope.inner_width / UVEditor.getResolution(0) / UVEditor.grid);
let step_y = (scope.inner_height / UVEditor.getResolution(1) / UVEditor.grid);
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / UVEditor.grid;
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / UVEditor.grid;
if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) {
elements.forEach(element => {
offset(element, pos[0] - last_pos[0], pos[1] - last_pos[1])
})
last_pos.replace(pos);
}
UVEditor.displaySliders();
UVEditor.loadData();
UVEditor.vue.$forceUpdate();
Canvas.updateView({elements: scope.mappable_elements, element_aspects: {uv: true}});
scope.dragging_uv = true;
}
function stop() {
removeEventListeners(document, 'mousemove touchmove', drag);
removeEventListeners(document, 'mouseup touchend', stop);
UVEditor.disableAutoUV()
Undo.finishEdit('Resize UV')
setTimeout(() => scope.dragging_uv = false, 10);
if (scope.dragging_uv) {
onEnd();
setTimeout(() => scope.dragging_uv = false, 10);
} else {
Undo.cancelEdit();
}
}
addEventListeners(document, 'mousemove touchmove', drag);
addEventListeners(document, 'mouseup touchend', stop);
},
dragFace(face_key, event) {
if (event.which == 2 || event.which == 3) return;
if (face_key) this.selectFace(face_key, event, true);
let elements = this.mappable_elements;
Undo.initEdit({elements, uv_only: true})
this.mappable_elements.forEach(el => {
if (el instanceof Mesh) {
delete Project.selected_vertices[el.uuid];
}
})
this.drag({
event,
onDrag: (diff_x, diff_y) => {
elements.forEach(element => {
if (element instanceof Mesh) {
this.selected_faces.forEach(key => {
let face = element.faces[key];
if (!face) return;
face.vertices.forEach(vertex_key => {
face.uv[vertex_key][0] += diff_x;
face.uv[vertex_key][1] += diff_y;
})
})
} else if (Project.box_uv) {
element.uv_offset[0] += diff_x;
element.uv_offset[1] += diff_y;
} else {
this.selected_faces.forEach(key => {
if (element.faces[key] && element instanceof Cube) {
element.faces[key].uv[0] += diff_x;
element.faces[key].uv[1] += diff_y;
element.faces[key].uv[2] += diff_x;
element.faces[key].uv[3] += diff_y;
}
})
}
})
},
onEnd: () => {
UVEditor.disableAutoUV()
Undo.finishEdit('Move UV')
}
})
},
resizeFace(face_key, event, x_side, y_side) {
if (event.which == 2 || event.which == 3) return;
event.stopPropagation();
let elements = this.mappable_elements;
Undo.initEdit({elements, uv_only: true})
this.drag({
event,
onDrag: (x, y) => {
elements.forEach(element => {
this.selected_faces.forEach(key => {
if (element.faces[key] && element instanceof Cube) {
if (x_side == -1) element.faces[key].uv[0] += x;
if (y_side == -1) element.faces[key].uv[1] += y;
if (x_side == 1) element.faces[key].uv[2] += x;
if (y_side == 1) element.faces[key].uv[3] += y;
}
})
element.uv_offset[0] += x;
element.uv_offset[1] += y;
})
},
onEnd: () => {
UVEditor.disableAutoUV()
Undo.finishEdit('Resize UV')
}
})
},
dragVertices(element, vertex_key, event) {
if (event.which == 2 || event.which == 3) return;
@ -1973,80 +1765,29 @@ Interface.definePanels(function() {
sel_vertices.replace([vertex_key]);
}
let scope = this;
let elements = this.mappable_elements;
Undo.initEdit({elements, uv_only: true})
let pos = [0, 0];
let last_pos = [0, 0];
let dragged = false;
function drag(e1) {
let step_x = (scope.inner_width / UVEditor.getResolution(0) / UVEditor.grid);
let step_y = (scope.inner_height / UVEditor.getResolution(1) / UVEditor.grid);
pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / UVEditor.grid;
pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / UVEditor.grid;
if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) {
this.drag({
event,
onDrag: (x, y) => {
elements.forEach(element => {
scope.selected_faces.forEach(key => {
this.selected_faces.forEach(key => {
let face = element.faces[key];
face.vertices.forEach(vertex_key => {
if (scope.selected_vertices[element.uuid] && scope.selected_vertices[element.uuid].includes(vertex_key)) {
face.uv[vertex_key][0] += pos[0] - last_pos[0];
face.uv[vertex_key][1] += pos[1] - last_pos[1];
if (this.selected_vertices[element.uuid] && this.selected_vertices[element.uuid].includes(vertex_key)) {
face.uv[vertex_key][0] += x;
face.uv[vertex_key][1] += y;
}
})
})
})
last_pos.replace(pos);
dragged = true;
}
UVEditor.displaySliders();
UVEditor.loadData();
UVEditor.vue.$forceUpdate();
Canvas.updateView({elements: scope.mappable_elements, element_aspects: {uv: true}});
scope.dragging_uv = true;
}
function stop(e1) {
removeEventListeners(document, 'mousemove touchmove', drag);
removeEventListeners(document, 'mouseup touchend', stop);
if (dragged) {
},
onEnd: () => {
Undo.finishEdit('Move UV');
} else {
Undo.cancelEdit();
}
setTimeout(() => scope.dragging_uv = false, 10);
}
addEventListeners(document, 'mousemove touchmove', drag);
addEventListeners(document, 'mouseup touchend', stop);
},
/*
openFaceMenu(event) {
let faces = [];
this.mappable_elements.forEach(element => {
for (let key in element.faces) {
if (faces.find(item => item.id == key)) continue;
faces.push({
id: key,
name: this.face_names[key],
icon: this.selected_faces.includes(key) ? 'check_box' : 'check_box_outline_blank',
click: () => {
this.selected_faces.splice(0, Infinity, key);
//UVEditor.loadData()
}
})
}
})
new Menu(faces).open(this.$refs.seleced_faces)
},*/
},
toPixels(uv_coord, offset = 0) {
return (uv_coord / this.project_resolution[0] * this.inner_width + offset) + 'px'
@ -2113,7 +1854,7 @@ Interface.definePanels(function() {
</div>
<div class="bar uv_cube_face_bar" v-if="mappable_elements[0] && mappable_elements[0].type == 'cube' && !box_uv">
<li v-for="(face, key) in mappable_elements[0].faces" :class="{selected: selected_faces.includes(key), disabled: mappable_elements[0].faces[key].texture == null}" @mousedown="selectFace(key, $event)">
<li v-for="(face, key) in mappable_elements[0].faces" :class="{selected: selected_faces.includes(key), disabled: mappable_elements[0].faces[key].texture === null}" @mousedown="selectFace(key, $event)">
{{ face_names[key] }}
</li>
</div>
@ -2130,14 +1871,14 @@ Interface.definePanels(function() {
:style="{width: (width+8) + 'px', height: (height+8) + 'px', overflowX: (zoom > 1) ? 'scroll' : 'hidden', overflowY: (inner_height > height) ? 'scroll' : 'hidden'}"
>
<div id="uv_frame" @click.stop="reverseSelect($event)" ref="frame" :style="{width: inner_width + 'px', height: inner_height + 'px'}" v-if="texture != null">
<div id="uv_frame" @click.stop="reverseSelect($event)" ref="frame" :style="{width: inner_width + 'px', height: inner_height + 'px'}" v-if="texture !== null">
<template v-if="mode == 'uv'" v-for="element in (showing_overlays ? all_mappable_elements : mappable_elements)" :key="element.uuid">
<template v-if="element.type == 'cube' && !box_uv">
<div class="cube_uv_face"
v-for="(face, key) in element.faces" :key="key"
v-if="face.getTexture() == texture"
v-if="face.getTexture() == texture || texture == 0"
:title="face_names[key]"
:class="{selected: selected_faces.includes(key), unselected: showing_overlays && !mappable_elements.includes(element)}"
@mousedown.prevent="dragFace(key, $event)"
@ -2148,7 +1889,7 @@ Interface.definePanels(function() {
'--height': toPixels(Math.abs(face.uv_size[1]), 2),
}"
>
<template v-if="selected_faces.includes(key)">
<template v-if="selected_faces.includes(key) && !(showing_overlays && !mappable_elements.includes(element))">
{{ face_names[key] || '' }}
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, -1)" style="width: var(--width)"></div>
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, 1)" style="top: var(--height); width: var(--width)"></div>
@ -2205,6 +1946,7 @@ Interface.definePanels(function() {
<div id="uv_brush_outline" v-if="mode == 'paint' && mouse_coords.x >= 0" :style="getBrushOutlineStyle()"></div>
<img style="object-fit: cover; object-position: 0px 0px;" v-if="texture && texture.error != 1" :src="texture.source">
<img style="object-fit: cover; object-position: 0px 0px; opacity: 0.02; mix-blend-mode: screen;" v-if="texture == 0 && !box_uv" src="./assets/missing_blend.png">
</div>
<div class="uv_transparent_face" v-else-if="selected_faces.length">${tl('uv_editor.transparent_face')}</div>
@ -2217,7 +1959,6 @@ Interface.definePanels(function() {
<div v-show="mode == 'uv'" class="bar uv_editor_sliders" ref="slider_bar" style="margin-left: 2px;"></div>
<div v-show="mode == 'uv'" class="toolbar_wrapper uv_editor"></div>
</div>
</div>
`
}
})
@ -2265,7 +2006,7 @@ Interface.definePanels(function() {
UVEditor.sliders.pos_x = new NumSlider({
id: 'uv_slider_pos_x',
private: true,
condition: () => UVEditor.vue.selected_faces.length,
condition: () => UVEditor.vue.selected_faces.length || Project.box_uv,
get: function() {
return getPos(0);
},
@ -2280,7 +2021,7 @@ Interface.definePanels(function() {
UVEditor.sliders.pos_y = new NumSlider({
id: 'uv_slider_pos_y',
private: true,
condition: () => UVEditor.vue.selected_faces.length,
condition: () => UVEditor.vue.selected_faces.length || Project.box_uv,
get: function() {
return getPos(1);
},
@ -2295,7 +2036,7 @@ Interface.definePanels(function() {
UVEditor.sliders.size_x = new NumSlider({
id: 'uv_slider_size_x',
private: true,
condition: () => (!Project.box_uv && Cube.selected[0]),
condition: () => (!Project.box_uv && Cube.selected[0] && UVEditor.vue.selected_faces.length),
get: function() {
if (!Project.box_uv) {
let ref_face = UVEditor.getReferenceFace();
@ -2316,7 +2057,7 @@ Interface.definePanels(function() {
UVEditor.sliders.size_y = new NumSlider({
id: 'uv_slider_size_y',
private: true,
condition: () => (!Project.box_uv && Cube.selected[0]),
condition: () => (!Project.box_uv && Cube.selected[0] && UVEditor.vue.selected_faces.length),
get: function() {
if (!Project.box_uv) {
let ref_face = UVEditor.getReferenceFace();

View File

@ -395,6 +395,8 @@
"dialog.create_texture.double_use.desc": "If two elements already have the same UV space assigned, keep it that way in the new map",
"dialog.create_texture.padding": "Padding",
"dialog.create_texture.padding.desc": "Add a small padding between the individual parts of the template",
"dialog.create_texture.combine_polys": "Combine Faces",
"dialog.create_texture.combine_polys.desc": "Combine connected coplanar faces into one UV section",
"dialog.create_texture.resolution": "Resolution",
"dialog.create_texture.resolution.desc": "The base resolution of the texture, in pixels per meter. The real size of the texture map may differ depending on the amount and size of elements.",