From 23da75b77d9a43d8764a542b2af7aee1e363ef85 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 31 Dec 2023 15:16:29 +0000 Subject: [PATCH 01/19] first add, animation import not working correctly --- plugins/vintagestory/about.md | 5 + plugins/vintagestory/vintagestory.js | 596 +++++++++++++++++++++++++++ plugins/vintagestory_models.js | 276 ------------- 3 files changed, 601 insertions(+), 276 deletions(-) create mode 100644 plugins/vintagestory/about.md create mode 100644 plugins/vintagestory/vintagestory.js delete mode 100644 plugins/vintagestory_models.js diff --git a/plugins/vintagestory/about.md b/plugins/vintagestory/about.md new file mode 100644 index 00000000..6f59cdc2 --- /dev/null +++ b/plugins/vintagestory/about.md @@ -0,0 +1,5 @@ +Written by Malik12tree + +Updated by zburke and Crabb + +Discord thread on official Vintage Story Discord https://discord.com/channels/302152934249070593/1190619114004299836 \ No newline at end of file diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js new file mode 100644 index 00000000..fc8bb37e --- /dev/null +++ b/plugins/vintagestory/vintagestory.js @@ -0,0 +1,596 @@ +(function () { + var codec; + + var import_action; + var export_action; + + var setting_gamepath; + + var autosettings = []; + var namespace = {} + + BBPlugin.register('vintagestory', { + title: "Vintage Story", + author: "Malik12tree", + icon: "park", + description: "Export and import of Vintage Story shapes", + tags: ["Vintage Story"], + version: "1.0.1", + min_version: "4.9.2", + variant: "both", + await_loading: true, + creation_date: "2023-12-22", + onload() { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game folder', + description: 'Set this to the install path', + category: 'defaults', + value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory', + type: 'text', + }); + + var format = new ModelFormat('vintagestory', { + id: "vintagestorymodel", + name: 'Vintage Story Model', + description: 'Model Format for VS specific features', + animated_textures: false, + animation_files: false, + animation_mode: true, + bone_binding_expression: false, + bone_rig: true, + box_uv: false, + category: 'general', + centered_grid: false, + display_mode: false, + edit_mode: true, + icon: 'park', + image_editor: false, + integer_size: false, + java_face_properties: true, + locators: true, + meshes: false, + model_identifier: false, + optional_box_uv: false, + paint_mode: true, + parent_model_id: false, + pose_mode: false, + rotate_cubes: true, + rotation_limit: false, + select_texture_for_particles: false, + show_on_start_screen: true, + single_texture: false, + target: ['Vintage Story'], + texture_folder: true, + texture_meshes: false, + uv_rotation: true, + vertex_color_ambient_occlusion: false, + }); + + codec = new Codec('vintagestory', { + name: 'Vintage Story Block/Item Model', + remember: true, + extension: 'json', + format: format, + load_filter: { + type: 'json', + extensions: ['json'], + condition(model) { + return model.elements; + } + }, + compile(exportOptions) { + const axis = ["x", "y", "z"]; + const faces = ["north", "east", "south", "west", "up", "down",]; + const channels = ["rotation", "position", "scale"]; + + let vs_model_json = { + textureWidth: Project.texture_width, + textureHeight: Project.texture_height, + textureSizes: {}, + textures: {}, + elements: [], + animations: [] + }; + + // Textures + if (Project.textures.length > 0) { + let TFS = "" // Texture Folder Suffix + let NSS = "" // Namespace Suffix + + for (let i = 0; i < Project.textures.length; i++) { + if (Project.textures[i].folder) { + TFS = "/"; + } else { + TFS = ""; + } + if (Project.textures[i].namespace) { + NSS = ":"; + } else { + NSS = ""; + } + + eval(`vs_model_json.textureSizes["${Project.textures[i].id}"] = [${Project.textures[i].width}, ${Project.textures[i].height}]`); + vs_model_json.textures[Project.textures[i].id] = Project.textures[i].namespace.replace("survival", "game") + NSS + Project.textures[i].folder + TFS + Project.textures[i].name.replace(".png", ""); + } + } + + // Elements + for (let obj of Outliner.root) { + createElement(vs_model_json.elements, obj); + } + + function createElement(elements, obj) { + if (!obj.export) return; + + if (obj.type === "cube") { + let element = { + name: obj.name, + from: [obj.from[0], obj.from[1], obj.from[2]], + to: [obj.to[0], obj.to[1], obj.to[2]], + rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], + faces: { + north: { texture: "#null", uv: [0, 0, 0, 0] }, + east: { texture: "#null", uv: [0, 0, 0, 0] }, + south: { texture: "#null", uv: [0, 0, 0, 0] }, + west: { texture: "#null", uv: [0, 0, 0, 0] }, + up: { texture: "#null", uv: [0, 0, 0, 0] }, + down: { texture: "#null", uv: [0, 0, 0, 0] } + }, + }; + + if (obj.rotation[0] != 0) + element.rotationX = obj.rotation[0] + if (obj.rotation[1] != 0) + element.rotationY = obj.rotation[1] + if (obj.rotation[2] != 0) + element.rotationZ = obj.rotation[2] + + // Handle faces + for (let face of faces) { + // Face texture + if (obj.faces[face].texture) { + var texture = Project.textures.find(e => e.uuid == obj.faces[face].texture) + if (texture) { + element.faces[face].texture = "#" + texture.id; + } + else { + console.log("Texture not found") + } + } + + // Face UV + element.faces[face].uv = [obj.faces[face].uv[0], obj.faces[face].uv[1], obj.faces[face].uv[2], obj.faces[face].uv[3]]; + + // Face rotation + if (obj.faces[face].rotation !== 0) { + element.faces[face].rotation = obj.faces[face].rotation; + } + }; + + elements.push(element); + } + else if (obj.type == "group") { + // Don't export empty groups + var hasChildren = + obj.children != undefined && + obj.children != null && + obj.children.length > 0; + + if (!hasChildren) + return; + + // TODO: check for child cube with the same name so it can be collapsed + // or just take the first cube, it doesnt matter? + + let element = { + name: obj.name, + from: [0, 0, 0], + to: [0, 0, 0], + rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], + faces: { + north: { texture: "#null", uv: [0, 0, 0, 0] }, + east: { texture: "#null", uv: [0, 0, 0, 0] }, + south: { texture: "#null", uv: [0, 0, 0, 0] }, + west: { texture: "#null", uv: [0, 0, 0, 0] }, + up: { texture: "#null", uv: [0, 0, 0, 0] }, + down: { texture: "#null", uv: [0, 0, 0, 0] } + }, + children: [] + }; + + if (obj.rotation[0] != 0) + element.rotationX = obj.rotation[0] + if (obj.rotation[1] != 0) + element.rotationY = obj.rotation[1] + if (obj.rotation[2] != 0) + element.rotationZ = obj.rotation[2] + + elements.push(element); + + for (let child of obj.children) { + createElement(element.children, child); + } + } + } + + //Animation + for (let i = 0; i < Animation.all.length; i++) { + let animation = Animation.all[i]; + + const snap_time = (1 / Math.clamp(animation.snapping, 1, 120)).toFixed(4) * 1; + + let animators = [] + Object.keys(animation.animators).forEach(key => { + animators.push(animation.animators[key]); + }); + + let anim = { + name: animation.name, + code: (animation.name).toLowerCase().replace(" ", "_"), + onActivityStopped: "EaseOut", + onAnimationEnd: "Repeat", + quantityframes: (animation.length * 30).toFixed() * 1, + keyframes: [ + ], + } + + // loop mode + if (animation.loop === "hold") { + anim.onAnimationEnd = "Hold" + } else if (animation.loop === "once") { + anim.onAnimationEnd = "Stop" + } + + animators.forEach(animator => { + let newKfs = []; + + channels.forEach(channel => { + if (animator.group !== undefined && animator[channel].length > 0) { + var keyframes_sorted = animator[channel].slice().sort((a, b) => a.time - b.time); + for (let k = 0; k <= keyframes_sorted.last().time + 0.5; k += snap_time) { + const timeIndex = Math.trunc(k * 10000) / 10000; + + //target kf + const findingKF = animator[channel].find(kf => getRangeBool(kf.time, timeIndex - .02, timeIndex + .02)); + if (findingKF !== undefined) { + const tIndex = newKfs.findIndex(e => e.find(f => f.time == findingKF.time)); + + if (tIndex !== -1) { + newKfs[tIndex].push(findingKF); + } else { + newKfs.push([findingKF]); + } + } + } + } + }); + + newKfs.forEach((frame, indexf) => { + let keyframe = { + frame: ((frame[0].time * 29).toFixed() * 1), + elements: { + } + } + const groupC = [animator.group] + + for (let g = 0; g < groupC.length; g++) { + let elemA = {}; + + if (animator.keyframes.length > 0) { + frame.forEach(kf => { + axis.forEach(a => { + var mult = 1; + + if (kf.channel == "position" && a == "x") + mult = -1; + + if (kf.channel == "rotation" && a != "z") + mult = -1 + + elemA[kf.channel.replace("position", "offset").replace("scale", "stretch") + a.toUpperCase()] = kf.data_points[0][a] * mult; + }); + }); + // 30 is fps VS uses for anims + if (anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1) !== undefined) { + anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1).elements[groupC[g].name] = elemA; + } else { + keyframe.elements[groupC[g].name] = elemA; + } + } + } + if (anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1) === undefined) { + anim.keyframes.push(keyframe); + } + }); + }); + + anim.keyframes.sort((a, b) => a.frame - b.frame); + vs_model_json.animations.push(anim); + } + + return autoStringify(vs_model_json); + }, + parse(model, path, add) { + + // Setup undo + var new_elements = []; + var new_textures = []; + //Undo.initEdit()//{ elements: new_elements, textures: new_textures }) //outliner: true, + + // New group + Project.added_models++; + var root_group = new Group(pathToName(path, false)).init().addTo(); + + // Texture sizes + if (model.texture_size instanceof Array && !add) { + Project.texture_width = Math.clamp(parseInt(model.texture_size[0]), 1, Infinity) + Project.texture_height = Math.clamp(parseInt(model.texture_size[1]), 1, Infinity) + } + + // Get existing textures + var texture_ids = {} + Project.textures.forEach(tex => { + texture_ids[tex.id] = tex + }) + + // Resolve new textures + if (model.textures) { + for (var key in model.textures) { + // Check if texture has already been loaded + var existingTexture = Project.textures.find(e => e.id == key) + if (existingTexture) + continue; + + // Create a new texture + var texture = new Texture().add() + texture.id = key + + // Update game namespace from settings + namespace["game"] = settings.vs_gamepath.value.replaceAll('\\', '/') + "/assets/survival/textures" + + // Update blank/relative namespace if we're in an assets folder + if (path.includes("assets")) { + var blankNSArr = [] + for (var fragment of path.replaceAll('\\', '/').split('/')) { + if (fragment == "shapes") { + break; + } + blankNSArr.push(fragment) + } + namespace[""] = blankNSArr.join('/') + "/textures" + } + + // Find namespace + var link = model.textures[key] + var spaces = link.split(':') + if (spaces.length > 1) { + texture.namespace = spaces[0] + link = spaces[1] + } + else { + texture.namespace = "" + } + + // Load texture + var fullPath = "file:///" + namespace[texture.namespace] + "/" + link + ".png" + texture = texture.fromPath(fullPath) + + // Find folder + var pathArr = link.split('/') + pathArr.pop() + texture.folder = pathArr.join('/') + + // Record + new_textures.push(texture); + texture_ids[key] = texture + } + + //Select Last Texture + if (Texture.all.length > 0) { + Texture.all.last().select(); + } + } + + // Resolve elements + for (let element of model.elements) { + parseElement(element, root_group, [0, 0, 0], new_elements, new_textures); + } + + // Read animations + for (let modelAni of model.animations) { + var newAnimation = new Animation() + newAnimation.name = modelAni.name + newAnimation.length = modelAni.quantityframes / 30 + if (modelAni.onAnimationEnd === "Stop") + newAnimation.loop = "once" + else if (modelAni.onAnimationEnd === "Hold") + newAnimation.loop = "hold" + newAnimation.add() + Animation.selected = newAnimation + + let boneAnimators = {} + + for (let modelKf of modelAni.keyframes) { + Object.keys(modelKf.elements).forEach((bonename) => { + + var boneAnimator = boneAnimators[bonename] + if (boneAnimator == undefined) { + console.log("adding new boneanimator " + bonename + " to " + newAnimation.name) + let uuid = guid(); + boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) + boneAnimators[bonename] = boneAnimator + newAnimation.animators[uuid] = boneAnimator + } + else { + console.log("already exists") + } + + var frame = modelKf.frame / 29 // is this an off-by-one error in the export? + var modelBone = modelKf.elements[bonename] + + if (modelBone.offsetX != undefined) { + var val = [modelBone.offsetX, modelBone.offsetY, modelBone.offsetZ] + console.log("position " + val) + var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) + kf.data_points = val + } + if (modelBone.rotationX != undefined) { + var val = [modelBone.rotationX, modelBone.rotationY, modelBone.rotationZ] + console.log("rotation " + val) + var kf = boneAnimator.createKeyframe(val, frame, "rotation", false, false) + kf.data_points = val + } + }) + } + } + + function parseElement(element, group, parentPositionOrigin, new_elements, new_textures) { + // From/to + let from = [element.from[0] + parentPositionOrigin[0], element.from[1] + parentPositionOrigin[1], element.from[2] + parentPositionOrigin[2]]; + let to = [element.to[0] + parentPositionOrigin[0], element.to[1] + parentPositionOrigin[1], element.to[2] + parentPositionOrigin[2]]; + + // Rotation origin + let rotationOrigin = [0, 0, 0] + if (element.rotationOrigin) { + rotationOrigin = [ + element.rotationOrigin[0], + element.rotationOrigin[1], + element.rotationOrigin[2] + ]; + } + + // Rotation + let rotation = [ + element.rotationX == undefined ? 0 : element.rotationX, + element.rotationY == undefined ? 0 : element.rotationY, + element.rotationZ == undefined ? 0 : element.rotationZ + ]; + + // Check for children + var hasChildren = + element.children != undefined && + element.children != null && + element.children.length > 0; + + var isZeroSize = + from[0] == to[0] && + from[1] == to[1] && + from[2] == to[2]; + + // Create group and descend children if required + var parent_group = group; + if (hasChildren) { + parent_group = new Group().extend({ + name: element.name, + origin: rotationOrigin, + rotation: rotation + }).init().addTo(group); + + new_elements.push(parent_group) + + for (let child_element of element.children) { + parseElement(child_element, parent_group, from, new_elements, new_textures); + } + } + + // If the cube is a dummy for animations, ignore it. + if (!isZeroSize) { + // Create cube + let new_cube = new Cube({ + name: element.name, + from: from, + to: to, + origin: rotationOrigin, + rotation: rotation + }) + + // Faces + if (element.faces) { + for (var key in element.faces) { + var read_face = element.faces[key]; + var new_face = new_cube.faces[key]; + if (read_face === undefined) { + new_face.texture = null + new_face.uv = [0, 0, 0, 0] + } else { + if (typeof read_face.uv === 'object') { + new_face.uv.forEach((n, i) => { + new_face.uv[i] = read_face.uv[i] * UVEditor.getResolution(i % 2) / 16; + }) + } + if (read_face.texture === '#null') { + new_face.texture = false; + } else if (read_face.texture) { + var id = read_face.texture.replace(/^#/, '') + if (texture_ids[id]) + new_face.texture = texture_ids[id].uuid; + else + console.log("Cannot resolve texture id " + id) + } + } + } + } + + // Done + new_cube.init().addTo(parent_group) + new_elements.push(new_cube) + } + } + + //Undo.finishEdit("vsimporter")//, { "elements": new_elements, "textures": new_textures }); + Validator.validate() + } + }); + + format.codec = codec + + import_action = new Action('import_vsmodel', { + id: "import_vintagestory", + name: 'Import Vintage Story Shape', + icon: 'park', + category: 'file', + click() { + Blockbench.import({ + type: 'Vintage Story Shape', + extensions: ['json'], + type: codec.name, + readtype: 'text', + }, files => { + files.forEach(file => { + codec.parse(autoParseJSON(file.content), file.path, true) + }) + }) + } + }) + + export_action = new Action('export_vsmodel', { + id: "export_vintagestory", + name: 'Export Vintage Story Shape', + type: codec.name, + icon: 'park', + category: 'file', + condition: () => { return Format.id == format.id }, + click() { + codec.export() + } + }); + + MenuBar.addAction(import_action, 'file.import'); + MenuBar.addAction(export_action, 'file.export'); + }, + onunload() { + codec.format.delete(); + codec.delete(); + + import_action.delete() + export_action.delete() + + setting_gamepath.delete() + autosettings.forEach(setting => { setting.delete() }) + } + }) + + function getRangeBool(x, min, max) { + return x >= min && x <= max; + } +})() diff --git a/plugins/vintagestory_models.js b/plugins/vintagestory_models.js deleted file mode 100644 index 55a86ec0..00000000 --- a/plugins/vintagestory_models.js +++ /dev/null @@ -1,276 +0,0 @@ -(function(){ - let exportVsAction; - Plugin.register('vintagestory_models', { - title: 'Vintage Story Models', - icon: 'park', - author: 'Malik12tree', - description: 'Allows to export VintageStory models.', - version: '1.0.0', - variant: "both", - onload() { - // Simulate the rotation on a mesh; rotation is stored as local - let m = new THREE.Mesh(); - function rotationToLocal(rotation) { - m.rotation.set(0,0,0, "ZYX"); - m.rotateOnWorldAxis(new THREE.Vector3(1,0,0), Math.degToRad(rotation[0])); - m.rotateOnWorldAxis(new THREE.Vector3(0,1,0), Math.degToRad(rotation[1])); - m.rotateOnWorldAxis(new THREE.Vector3(0,0,1), Math.degToRad(rotation[2])); - m.rotation.reorder("XYZ"); - let r = [ - Math.radToDeg(m.rotation.x), - Math.radToDeg(m.rotation.y), - Math.radToDeg(m.rotation.z), - ] - return r; - } - exportVsAction = new Action("exportVsModel", { - name: "Export Vintage Story Model", - icon: "park", - click: function(){ - const worldcenter = Format.name === "Java Block/Item" ? 0: 8; - let VSjson = { - textureWidth: Project.texture_width, - textureHeight: Project.texture_height, - textureSizes: { - }, - textures: { - }, - elements: [ - ], - animations:[ - ] - }; - //textures - if (Project.textures.length > 0) { - let TFS = "" // Texture Folder Suffix - for (let i = 0; i < Project.textures.length; i++) { - eval(`VSjson.textureSizes["${Project.textures[i].id}"] = [${Project.textures[i].width}, ${Project.textures[i].height}]`) - if (Project.textures[i].folder) { - TFS = "/"; - } else { - TFS = ""; - } - if (Blockbench.operating_system == "Windows") { - // remove Drive name - VSjson.textures[Project.textures[i].id] = Project.textures[i].path.substring(2).replace(".png", "").replaceAll("\\", "/") - } else { - VSjson.textures[Project.textures[i].id] = Project.textures[i].path.replace(".png", "").replaceAll("\\", "/").replaceAll("\/", "/") - } - } - } - //elements - let elemALL = []; - let axis = ["x", "y", "z"]; - let faces = ["north","east","south","west","up","down",] - let channels = ["rotation", "position", "scale"]; - for (let i = 0; i < Group.all.length; i++) { - let elem = { - name: Group.all[i].name, - rotationOrigin: [Group.all[i].origin[0]+worldcenter,Group.all[i].origin[1],Group.all[i].origin[2]+worldcenter], - from: [0,0,0], - to: [0,0,0], - parent: Group.all[i].parent === "root" ? null : Group.all[i].parent.name, - faces: {north: { texture: "#", uv: [ 0,0,0,0 ] },east: { texture: "#", uv: [ 0,0,0,0 ] },south: { texture: "#", uv: [ 0,0,0,0 ] },west: { texture: "#", uv: [ 0,0,0,0 ] },up: { texture: "#", uv: [ 0,0,0,0 ] },down: { texture: "#", uv: [ 0,0,0,0 ] }}, - } - // rotations - let r = Cube.all[i].mesh.rotation.clone().reorder("XYZ"); - r = [Math.radToDeg(r.x),Math.radToDeg(r.y),Math.radToDeg(r.z)] - axis.forEach((axis, index) => { - if (Group.all[i].rotation[index] !== 0) { - elem["rotation" + axis.toUpperCase()] = r[index]; - } - }); - elemALL.push(elem); - } - for (let i = 0; i < Cube.all.length; i++) { - let elem = { - name : Cube.all[i].name + i, // index incase - from: [Cube.all[i].from[0]+worldcenter,Cube.all[i].from[1],Cube.all[i].from[2]+worldcenter], - to: [Cube.all[i].to[0]+worldcenter,Cube.all[i].to[1],Cube.all[i].to[2]+worldcenter], - rotationOrigin: [Cube.all[i].origin[0]+worldcenter,Cube.all[i].origin[1],Cube.all[i].origin[2]+worldcenter], - faces: { - north: { texture: "#0", uv: [ 0, 0, 0, 0 ] }, - east: { texture: "#0", uv: [ 0, 0, 0, 0 ] }, - south: { texture: "#0", uv: [ 0, 0, 0, 0 ] }, - west: { texture: "#0", uv: [ 0, 0, 0, 0 ] }, - up: { texture: "#0", uv: [ 0, 0, 0, 0 ] }, - down: { texture: "#0", uv: [ 0, 0, 0, 0 ] } - }, - parent: Cube.all[i].parent === "root" ? null : Cube.all[i].parent.name - }; - - //rotations - let r = Cube.all[i].mesh.rotation.clone().reorder("XYZ"); - r = [Math.radToDeg(r.x),Math.radToDeg(r.y),Math.radToDeg(r.z)] - axis.forEach((axis, index) => { - if (Cube.all[i].rotation[index] !== 0) { - elem["rotation" + axis.toUpperCase()] = r[index]; - } - }); - - //faces - faces.forEach(face => { - if (Project.textures.length > 0) { - if (!Format.single_texture) { - if (Project.textures.find(e => e.uuid == Cube.all[i].faces[face].texture) !== false) { - elem.faces[face].texture = "#" + Project.textures.find(e => e.uuid == Cube.all[i].faces[face].texture).id; - } else { - elem.faces[face].texture = "#missing"; - } - - } else { - elem.faces[face].texture = "#" + Cube.all[i].faces[face].getTexture().id; - } - } - elem.faces[face].autoUv = Project.box_uv; - elem.faces[face].uv = [Cube.all[i].faces[face].uv[0],Cube.all[i].faces[face].uv[1],Cube.all[i].faces[face].uv[2],Cube.all[i].faces[face].uv[3]]; - if (Cube.all[i].faces[face].rotation !== 0) { - elem.faces[face].rotation = Cube.all[i].faces[face].rotation; - } - }); - - // VSjson.elements.push(elem); - elemALL.push(elem); - } - VSjson.elements = list_to_tree(elemALL); - - //Animation - if (Format.animation_mode) { - for (let i = 0; i < Animation.all.length; i++) { - const snap_time = (1/Math.clamp(Animation.all[i].snapping, 1, 120)).toFixed(4)*1; - // console.log(snap_time); - - let animators = [] - Object.keys(Animation.all[i].animators).forEach(key => { - animators.push(Animation.all[i].animators[key]); - }); - let anim = { - name: Animation.all[i].name, - code: (Animation.all[i].name).toLowerCase().replace(" ", "_"), - onActivityStopped: "EaseOut", - onAnimationEnd: "Repeat", - quantityframes: (Animation.all[i].length*30).toFixed()*1, - keyframes: [ - ], - } - // loop mode - if (Animation.all[i].loop === "hold") { - anim.onAnimationEnd = "Hold" - } else if (Animation.all[i].loop === "once"){ - anim.onAnimationEnd = "Stop" - } - animators.forEach(animator => { - - // keyframes - - //gather - let newKfs = []; - channels.forEach(channel => { - if (animator.group !== undefined && animator[channel].length > 0) { - var keyframes_sorted = animator[channel].slice().sort((a, b) => a.time - b.time); - for (let k = 0; k <= keyframes_sorted.last().time + 0.5; k+=snap_time) { - const timeIndex = Math.trunc(k*10000)/10000; - - //target kf - const findingKF = animator[channel].find(kf => getRangeBool(kf.time, timeIndex-.02, timeIndex+.02)); - if(findingKF !== undefined){ - let KFclone = new Keyframe(findingKF); - if (findingKF.channel == "rotation") { - let rl = rotationToLocal([ - findingKF.data_points[0].x*1, - findingKF.data_points[0].y*1, - findingKF.data_points[0].z*1, - ]) - KFclone.data_points[0].x = rl[0]; - KFclone.data_points[0].y = rl[1]; - KFclone.data_points[0].z = rl[2]; - } - const tIndex = newKfs.findIndex(e => e.find(f => f.time == findingKF.time)); - if (tIndex !== -1) { - newKfs[tIndex].push(KFclone); - } else { - newKfs.push([KFclone]); - } - } - } - } - }); - newKfs.forEach((frame, indexf) => { - let keyframe = { - frame: ((frame[0].time*30).toFixed()*1), - elements: { - } - } - const groupC = [animator.group] - - for (let g = 0; g < groupC.length; g++) { - let elemA = {}; - - if (animator.keyframes.length > 0) { - frame.forEach(kf => { - axis.forEach(a => { - elemA[kf.channel.replace("position", "offset").replace("scale", "stretch") + a.toUpperCase()] = kf.data_points[0][a]*1; - }); - }); - // 30 is fps VS uses for anims - if (anim.keyframes.find(e => e.frame === (frame[0].time*30).toFixed()*1) !== undefined) { - anim.keyframes.find(e => e.frame === (frame[0].time*30).toFixed()*1).elements[groupC[g].name] = elemA; - } else { - keyframe.elements[groupC[g].name] = elemA; - } - } - } - if (anim.keyframes.find(e => e.frame === (frame[0].time*30).toFixed()*1) === undefined) { - anim.keyframes.push(keyframe); - } - }); - }); - anim.keyframes.sort((a, b) => a.frame - b.frame); - VSjson.animations.push(anim) - } - } - if (isApp) { - Blockbench.export({ - type: 'VintageStory Model', - extensions: ['json'], - name: (Project.name !== '' ? Project.name: "model"), - content: autoStringify(VSjson), - savetype: 'json' - }); - } else{ - var blob = new Blob([autoStringify(VSjson)], {type: "text/plain;charset=utf-8"}); - saveAs(blob, (Project.name !== '' ? Project.name: "model") + ".json"); - } - console.log(VSjson); - } - }) - MenuBar.addAction(exportVsAction, "file.export"); - }, - onunload() { - exportVsAction.delete(); - } - }); - -function getRangeBool(x, min, max) { - return x >= min && x <= max; -} - -function list_to_tree(list) { - var map = {}, node, roots = [], i; - - for (i = 0; i < list.length; i ++) { - map[list[i].name] = i; - list[i].children = []; - } - - for (i = 0; i < list.length; i ++) { - node = list[i]; - if (node.parent !== null) { - list[map[node.parent]].children.push(node); - } else { - roots.push(node); - } - } - return roots; - } -})() \ No newline at end of file From 137a290ce20c4d4228b3e95452216d27f900ac62 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 31 Dec 2023 15:35:34 +0000 Subject: [PATCH 02/19] fix some errors --- plugins/vintagestory/vintagestory.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index fc8bb37e..122b6338 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -422,23 +422,19 @@ newAnimation.animators[uuid] = boneAnimator } else { - console.log("already exists") + console.log(bonename + " already exists") } var frame = modelKf.frame / 29 // is this an off-by-one error in the export? var modelBone = modelKf.elements[bonename] if (modelBone.offsetX != undefined) { - var val = [modelBone.offsetX, modelBone.offsetY, modelBone.offsetZ] - console.log("position " + val) + var val = { x: modelBone.offsetX, y: modelBone.offsetY, z: modelBone.offsetZ } var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) - kf.data_points = val } if (modelBone.rotationX != undefined) { - var val = [modelBone.rotationX, modelBone.rotationY, modelBone.rotationZ] - console.log("rotation " + val) + var val = { x: modelBone.rotationX, y: modelBone.rotationY, z: modelBone.rotationZ } var kf = boneAnimator.createKeyframe(val, frame, "rotation", false, false) - kf.data_points = val } }) } From 3282bc37b92946eda7a69ed51afb7edf9ced2993 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 31 Dec 2023 15:41:44 +0000 Subject: [PATCH 03/19] fix directions --- plugins/vintagestory/vintagestory.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 122b6338..9f7b077d 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -427,13 +427,13 @@ var frame = modelKf.frame / 29 // is this an off-by-one error in the export? var modelBone = modelKf.elements[bonename] - + if (modelBone.offsetX != undefined) { - var val = { x: modelBone.offsetX, y: modelBone.offsetY, z: modelBone.offsetZ } + var val = { x: modelBone.offsetX * -1, y: modelBone.offsetY, z: modelBone.offsetZ } var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) } if (modelBone.rotationX != undefined) { - var val = { x: modelBone.rotationX, y: modelBone.rotationY, z: modelBone.rotationZ } + var val = { x: modelBone.rotationX * -1, y: modelBone.rotationY * -1, z: modelBone.rotationZ } var kf = boneAnimator.createKeyframe(val, frame, "rotation", false, false) } }) From e4071024a5a1987c56590b65645ecf3c5371e9b0 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 31 Dec 2023 15:54:14 +0000 Subject: [PATCH 04/19] add animators to timeline --- plugins/vintagestory/vintagestory.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 9f7b077d..78834233 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -438,6 +438,9 @@ } }) } + Object.keys(boneAnimators).forEach(function (key) { + boneAnimators[key].addToTimeline() + }); } function parseElement(element, group, parentPositionOrigin, new_elements, new_textures) { From 672a4bef7fbbbd862e22f561bbfa70403b244151 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Mon, 1 Jan 2024 13:10:19 +0000 Subject: [PATCH 05/19] fix times --- .gitignore | 1 + plugins/vintagestory/vintagestory.js | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index ae1dd1fc..7ec690e5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ .DS_Store desktop.ini +plugins/vintagestory/vintagestory - Copy.js diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 78834233..30ea7c38 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -229,7 +229,7 @@ code: (animation.name).toLowerCase().replace(" ", "_"), onActivityStopped: "EaseOut", onAnimationEnd: "Repeat", - quantityframes: (animation.length * 30).toFixed() * 1, + quantityframes: (animation.length * 30).toFixed() * 1 + 1, keyframes: [ ], } @@ -267,7 +267,7 @@ newKfs.forEach((frame, indexf) => { let keyframe = { - frame: ((frame[0].time * 29).toFixed() * 1), + frame: ((frame[0].time * 30).toFixed() * 1), elements: { } } @@ -291,14 +291,14 @@ }); }); // 30 is fps VS uses for anims - if (anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1) !== undefined) { - anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1).elements[groupC[g].name] = elemA; + if (anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1) !== undefined) { + anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1).elements[groupC[g].name] = elemA; } else { keyframe.elements[groupC[g].name] = elemA; } } } - if (anim.keyframes.find(e => e.frame === (frame[0].time * 29).toFixed() * 1) === undefined) { + if (anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1) === undefined) { anim.keyframes.push(keyframe); } }); @@ -425,7 +425,7 @@ console.log(bonename + " already exists") } - var frame = modelKf.frame / 29 // is this an off-by-one error in the export? + var frame = modelKf.frame / 30 var modelBone = modelKf.elements[bonename] if (modelBone.offsetX != undefined) { From 388e835439a5f5d2dd6df48e3dee9fbaea991fc9 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Mon, 1 Jan 2024 14:03:36 +0000 Subject: [PATCH 06/19] improve animation export --- .gitignore | 1 + plugins/vintagestory/vintagestory.js | 60 +++++++++++++++++++++------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 7ec690e5..36005b59 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ .DS_Store desktop.ini plugins/vintagestory/vintagestory - Copy.js +plugins/vintagestory/vintagestory - Copy (2).js diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 30ea7c38..5b077063 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -182,8 +182,26 @@ // TODO: check for child cube with the same name so it can be collapsed // or just take the first cube, it doesnt matter? - let element = { - name: obj.name, + // POSITION + let position_element = { + name: obj.name + "_position", + from: [0, 0, 0], + to: [0, 0, 0], + rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], + faces: { + north: { texture: "#null", uv: [0, 0, 0, 0] }, + east: { texture: "#null", uv: [0, 0, 0, 0] }, + south: { texture: "#null", uv: [0, 0, 0, 0] }, + west: { texture: "#null", uv: [0, 0, 0, 0] }, + up: { texture: "#null", uv: [0, 0, 0, 0] }, + down: { texture: "#null", uv: [0, 0, 0, 0] } + }, + children: [] + }; + + // ROTATION + let rotation_element = { + name: obj.name + "_rotation", from: [0, 0, 0], to: [0, 0, 0], rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], @@ -199,17 +217,21 @@ }; if (obj.rotation[0] != 0) - element.rotationX = obj.rotation[0] + rotation_element.rotationX = obj.rotation[0] if (obj.rotation[1] != 0) - element.rotationY = obj.rotation[1] + rotation_element.rotationY = obj.rotation[1] if (obj.rotation[2] != 0) - element.rotationZ = obj.rotation[2] + rotation_element.rotationZ = obj.rotation[2] - elements.push(element); + position_element.children.push(rotation_element); + + elements.push(position_element); for (let child of obj.children) { - createElement(element.children, child); + createElement(rotation_element.children, child); } + + } } @@ -229,7 +251,7 @@ code: (animation.name).toLowerCase().replace(" ", "_"), onActivityStopped: "EaseOut", onAnimationEnd: "Repeat", - quantityframes: (animation.length * 30).toFixed() * 1 + 1, + quantityframes: (animation.length * 30).toFixed() * 1, keyframes: [ ], } @@ -275,6 +297,7 @@ for (let g = 0; g < groupC.length; g++) { let elemA = {}; + let elemB = {}; if (animator.keyframes.length > 0) { frame.forEach(kf => { @@ -287,14 +310,21 @@ if (kf.channel == "rotation" && a != "z") mult = -1 - elemA[kf.channel.replace("position", "offset").replace("scale", "stretch") + a.toUpperCase()] = kf.data_points[0][a] * mult; + if (kf.channel == "position") + elemA[kf.channel.replace("position", "offset").replace("scale", "stretch") + a.toUpperCase()] = kf.data_points[0][a] * mult; + else if (kf.channel == "rotation") + elemB[kf.channel.replace("position", "offset").replace("scale", "stretch") + a.toUpperCase()] = kf.data_points[0][a] * mult; + else + console.log("Can't handle scaling just yet") }); }); // 30 is fps VS uses for anims if (anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1) !== undefined) { - anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1).elements[groupC[g].name] = elemA; + anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1).elements[groupC[g].name + "_position"] = elemA; + anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1).elements[groupC[g].name + "_rotation"] = elemB; } else { - keyframe.elements[groupC[g].name] = elemA; + keyframe.elements[groupC[g].name + "_position"] = elemA; + keyframe.elements[groupC[g].name + "_rotation"] = elemB; } } } @@ -400,6 +430,7 @@ for (let modelAni of model.animations) { var newAnimation = new Animation() newAnimation.name = modelAni.name + newAnimation.snapping = 30 newAnimation.length = modelAni.quantityframes / 30 if (modelAni.onAnimationEnd === "Stop") newAnimation.loop = "once" @@ -415,7 +446,6 @@ var boneAnimator = boneAnimators[bonename] if (boneAnimator == undefined) { - console.log("adding new boneanimator " + bonename + " to " + newAnimation.name) let uuid = guid(); boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) boneAnimators[bonename] = boneAnimator @@ -425,9 +455,9 @@ console.log(bonename + " already exists") } - var frame = modelKf.frame / 30 + var frame = modelKf.frame / 30 // is this an off-by-one error in the export? var modelBone = modelKf.elements[bonename] - + if (modelBone.offsetX != undefined) { var val = { x: modelBone.offsetX * -1, y: modelBone.offsetY, z: modelBone.offsetZ } var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) @@ -592,4 +622,4 @@ function getRangeBool(x, min, max) { return x >= min && x <= max; } -})() +})() \ No newline at end of file From 7e2ffcc4bf36a7f38f9d4486fc3398ad62a22927 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Mon, 1 Jan 2024 14:13:07 +0000 Subject: [PATCH 07/19] set type to bone corrrectly --- plugins/vintagestory/vintagestory.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 5b077063..37ec4d2c 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -448,6 +448,7 @@ if (boneAnimator == undefined) { let uuid = guid(); boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) + boneAnimator.type = "bone" boneAnimators[bonename] = boneAnimator newAnimation.animators[uuid] = boneAnimator } From a5909580e0a3bee3f3c64023c6f36a6c6332d70b Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Mon, 1 Jan 2024 15:11:48 +0000 Subject: [PATCH 08/19] better handling of animators on import, still not quite right tho --- plugins/vintagestory/vintagestory.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 37ec4d2c..058b4cb7 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -443,17 +443,14 @@ for (let modelKf of modelAni.keyframes) { Object.keys(modelKf.elements).forEach((bonename) => { - var boneAnimator = boneAnimators[bonename] if (boneAnimator == undefined) { - let uuid = guid(); + var group = Project.groups.find(e => e.name == bonename) + let uuid = group.uuid boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) boneAnimator.type = "bone" boneAnimators[bonename] = boneAnimator - newAnimation.animators[uuid] = boneAnimator - } - else { - console.log(bonename + " already exists") + newAnimation.animators[group.uuid] = boneAnimator } var frame = modelKf.frame / 30 // is this an off-by-one error in the export? From ac84c8cf7de4d12600107ccaa4b3b0050bec4379 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Mon, 1 Jan 2024 16:48:24 +0000 Subject: [PATCH 09/19] tidy --- .gitignore | 6 ------ plugins/vintagestory/vintagestory.js | 5 ++++- 2 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 36005b59..00000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ - -.DS_Store -desktop.ini -plugins/vintagestory/vintagestory - Copy.js -plugins/vintagestory/vintagestory - Copy (2).js diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 058b4cb7..b7565d79 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -453,7 +453,7 @@ newAnimation.animators[group.uuid] = boneAnimator } - var frame = modelKf.frame / 30 // is this an off-by-one error in the export? + var frame = modelKf.frame / 30 var modelBone = modelKf.elements[bonename] if (modelBone.offsetX != undefined) { @@ -469,6 +469,8 @@ Object.keys(boneAnimators).forEach(function (key) { boneAnimators[key].addToTimeline() }); + + Animator.preview() } function parseElement(element, group, parentPositionOrigin, new_elements, new_textures) { @@ -560,6 +562,7 @@ // Done new_cube.init().addTo(parent_group) + new_elements.push(new_cube) } } From fca452db2cfb5d8ba31077ed1ca9d195c16e1301 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 14 Jan 2024 14:18:18 +0000 Subject: [PATCH 10/19] Update vintagestory.js dont export unanimated groups --- plugins/vintagestory/vintagestory.js | 123 +++++++++++++++------------ 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index b7565d79..c6c5fb60 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -114,6 +114,18 @@ } } + // Fetch all target bones + let animatedBoneNames = new Set(); + Animation.all.forEach(animation => { + Object.keys(animation.animators).forEach(key => { + console.log(key) + console.log(animation.animators[key].name) + animatedBoneNames.add(animation.animators[key].name) + }) + }) + console.log(animatedBoneNames.values()) + + // Elements for (let obj of Outliner.root) { createElement(vs_model_json.elements, obj); @@ -182,63 +194,67 @@ // TODO: check for child cube with the same name so it can be collapsed // or just take the first cube, it doesnt matter? - // POSITION - let position_element = { - name: obj.name + "_position", - from: [0, 0, 0], - to: [0, 0, 0], - rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], - faces: { - north: { texture: "#null", uv: [0, 0, 0, 0] }, - east: { texture: "#null", uv: [0, 0, 0, 0] }, - south: { texture: "#null", uv: [0, 0, 0, 0] }, - west: { texture: "#null", uv: [0, 0, 0, 0] }, - up: { texture: "#null", uv: [0, 0, 0, 0] }, - down: { texture: "#null", uv: [0, 0, 0, 0] } - }, - children: [] - }; - - // ROTATION - let rotation_element = { - name: obj.name + "_rotation", - from: [0, 0, 0], - to: [0, 0, 0], - rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], - faces: { - north: { texture: "#null", uv: [0, 0, 0, 0] }, - east: { texture: "#null", uv: [0, 0, 0, 0] }, - south: { texture: "#null", uv: [0, 0, 0, 0] }, - west: { texture: "#null", uv: [0, 0, 0, 0] }, - up: { texture: "#null", uv: [0, 0, 0, 0] }, - down: { texture: "#null", uv: [0, 0, 0, 0] } - }, - children: [] - }; - - if (obj.rotation[0] != 0) - rotation_element.rotationX = obj.rotation[0] - if (obj.rotation[1] != 0) - rotation_element.rotationY = obj.rotation[1] - if (obj.rotation[2] != 0) - rotation_element.rotationZ = obj.rotation[2] - - position_element.children.push(rotation_element); - - elements.push(position_element); - - for (let child of obj.children) { - createElement(rotation_element.children, child); + if (animatedBoneNames.has(obj.name)) { + // POSITION + let position_element = { + name: obj.name + "_position", + from: [0, 0, 0], + to: [0, 0, 0], + rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], + faces: { + north: { texture: "#null", uv: [0, 0, 0, 0] }, + east: { texture: "#null", uv: [0, 0, 0, 0] }, + south: { texture: "#null", uv: [0, 0, 0, 0] }, + west: { texture: "#null", uv: [0, 0, 0, 0] }, + up: { texture: "#null", uv: [0, 0, 0, 0] }, + down: { texture: "#null", uv: [0, 0, 0, 0] } + }, + children: [] + }; + + // ROTATION + let rotation_element = { + name: obj.name + "_rotation", + from: [0, 0, 0], + to: [0, 0, 0], + rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], + faces: { + north: { texture: "#null", uv: [0, 0, 0, 0] }, + east: { texture: "#null", uv: [0, 0, 0, 0] }, + south: { texture: "#null", uv: [0, 0, 0, 0] }, + west: { texture: "#null", uv: [0, 0, 0, 0] }, + up: { texture: "#null", uv: [0, 0, 0, 0] }, + down: { texture: "#null", uv: [0, 0, 0, 0] } + }, + children: [] + }; + + if (obj.rotation[0] != 0) + rotation_element.rotationX = obj.rotation[0] + if (obj.rotation[1] != 0) + rotation_element.rotationY = obj.rotation[1] + if (obj.rotation[2] != 0) + rotation_element.rotationZ = obj.rotation[2] + + position_element.children.push(rotation_element); + + elements.push(position_element); + + for (let child of obj.children) { + createElement(rotation_element.children, child); + } + } + else { + // Unanimated group but still has children. + for (let child of obj.children) { + createElement(elements, child); + } } - - } } //Animation - for (let i = 0; i < Animation.all.length; i++) { - let animation = Animation.all[i]; - + Animation.all.forEach(animation => { const snap_time = (1 / Math.clamp(animation.snapping, 1, 120)).toFixed(4) * 1; let animators = [] @@ -318,6 +334,7 @@ console.log("Can't handle scaling just yet") }); }); + // 30 is fps VS uses for anims if (anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1) !== undefined) { anim.keyframes.find(e => e.frame === (frame[0].time * 30).toFixed() * 1).elements[groupC[g].name + "_position"] = elemA; @@ -336,7 +353,7 @@ anim.keyframes.sort((a, b) => a.frame - b.frame); vs_model_json.animations.push(anim); - } + }) return autoStringify(vs_model_json); }, From 5830aa2843c7750be1292698e3ef56b67c4e4880 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 14 Jan 2024 14:52:18 +0000 Subject: [PATCH 11/19] collapse cubes and folders with the same name --- plugins/vintagestory/vintagestory.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index c6c5fb60..d63b7778 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -131,10 +131,14 @@ createElement(vs_model_json.elements, obj); } - function createElement(elements, obj) { + function createElement(elements, obj, excludeCubeName = null) { if (!obj.export) return; if (obj.type === "cube") { + // Already exported in place of the folder + if (obj.name === excludeCubeName) + return; + let element = { name: obj.name, from: [obj.from[0], obj.from[1], obj.from[2]], @@ -148,6 +152,7 @@ up: { texture: "#null", uv: [0, 0, 0, 0] }, down: { texture: "#null", uv: [0, 0, 0, 0] } }, + children: [] }; if (obj.rotation[0] != 0) @@ -180,6 +185,8 @@ }; elements.push(element); + + return element } else if (obj.type == "group") { // Don't export empty groups @@ -191,9 +198,6 @@ if (!hasChildren) return; - // TODO: check for child cube with the same name so it can be collapsed - // or just take the first cube, it doesnt matter? - if (animatedBoneNames.has(obj.name)) { // POSITION let position_element = { @@ -246,8 +250,15 @@ } else { // Unanimated group but still has children. + let nextChildren = elements + for (let child of obj.children) { + if (child.name === obj.name) { + nextChildren = createElement(elements, child, null).children + } + } + for (let child of obj.children) { - createElement(elements, child); + createElement(nextChildren, child, obj.name) } } } From 3b4190a87386289ca2da20379736f9eb783c23f8 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 14 Jan 2024 15:12:42 +0000 Subject: [PATCH 12/19] tidy console logging --- plugins/vintagestory/vintagestory.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index d63b7778..742eb055 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -118,13 +118,9 @@ let animatedBoneNames = new Set(); Animation.all.forEach(animation => { Object.keys(animation.animators).forEach(key => { - console.log(key) - console.log(animation.animators[key].name) animatedBoneNames.add(animation.animators[key].name) }) }) - console.log(animatedBoneNames.values()) - // Elements for (let obj of Outliner.root) { From c8440089d8a6ebac23f186b1ab8b03dfaf536a41 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Tue, 16 Jan 2024 11:09:58 +0000 Subject: [PATCH 13/19] take group translation into account when promoting --- plugins/vintagestory/vintagestory.js | 39 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 742eb055..110e9bc1 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -11,11 +11,11 @@ BBPlugin.register('vintagestory', { title: "Vintage Story", - author: "Malik12tree", + author: "Malik12tree,Crabb", icon: "park", description: "Export and import of Vintage Story shapes", tags: ["Vintage Story"], - version: "1.0.1", + version: "1.0.2", min_version: "4.9.2", variant: "both", await_loading: true, @@ -127,7 +127,7 @@ createElement(vs_model_json.elements, obj); } - function createElement(elements, obj, excludeCubeName = null) { + function createElement(elements, obj, excludeCubeName = null, parentPos = [0, 0, 0], parentRot = [0, 0, 0]) { if (!obj.export) return; if (obj.type === "cube") { @@ -137,8 +137,8 @@ let element = { name: obj.name, - from: [obj.from[0], obj.from[1], obj.from[2]], - to: [obj.to[0], obj.to[1], obj.to[2]], + from: [obj.from[0] - parentPos[0], obj.from[1] - parentPos[1], obj.from[2] - parentPos[2]], + to: [obj.to[0] - parentPos[0], obj.to[1] - parentPos[1], obj.to[2] - parentPos[2]], rotationOrigin: [obj.origin[0], obj.origin[1], obj.origin[2]], faces: { north: { texture: "#null", uv: [0, 0, 0, 0] }, @@ -151,12 +151,9 @@ children: [] }; - if (obj.rotation[0] != 0) - element.rotationX = obj.rotation[0] - if (obj.rotation[1] != 0) - element.rotationY = obj.rotation[1] - if (obj.rotation[2] != 0) - element.rotationZ = obj.rotation[2] + element.rotationX = obj.rotation[0] + parentRot[0] + element.rotationY = obj.rotation[1] + parentRot[1] + element.rotationZ = obj.rotation[2] + parentRot[2] // Handle faces for (let face of faces) { @@ -229,12 +226,9 @@ children: [] }; - if (obj.rotation[0] != 0) - rotation_element.rotationX = obj.rotation[0] - if (obj.rotation[1] != 0) - rotation_element.rotationY = obj.rotation[1] - if (obj.rotation[2] != 0) - rotation_element.rotationZ = obj.rotation[2] + rotation_element.rotationX = obj.rotation[0] + rotation_element.rotationY = obj.rotation[1] + rotation_element.rotationZ = obj.rotation[2] position_element.children.push(rotation_element); @@ -247,14 +241,21 @@ else { // Unanimated group but still has children. let nextChildren = elements + let parentFrom = obj.from + let parentRot = obj.rotation + + for (let child of obj.children) { if (child.name === obj.name) { - nextChildren = createElement(elements, child, null).children + let promotedChild = createElement(elements, child, null, parentFrom, parentRot) + nextChildren = promotedChild.children + parentFrom = child.from + parentRot = child.rotation } } for (let child of obj.children) { - createElement(nextChildren, child, obj.name) + createElement(nextChildren, child, obj.name, parentFrom) } } } From e189f540c9c5507fcc9fe9f5aa1c7dd20040db0d Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 12:45:41 +0000 Subject: [PATCH 14/19] PR update update plugins.json remove locators tag remove java face tag restore .gitignore tag for desktop only until I find out how to switch features per variant --- .gitignore | 3 + plugins.json | 6 +- plugins/vintagestory/vintagestory.js | 148 +++++++++++++++------------ 3 files changed, 89 insertions(+), 68 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..791f039b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.DS_Store +desktop.ini \ No newline at end of file diff --git a/plugins.json b/plugins.json index f31d81de..692316df 100644 --- a/plugins.json +++ b/plugins.json @@ -539,11 +539,11 @@ }, "vintagestory_models": { "title": "Vintage Story Models", - "author": "Malik12tree", + "author": "Malik12tree, Crabb", "icon": "park", "description": "Allows to export VintageStory models.", - "version": "1.0.0", - "variant": "both" + "version": "1.0.2", + "variant": "desktop" }, "endimations_exporter": { "title": "Endimator Animations Exporter", diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 110e9bc1..b9ad4abe 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -11,13 +11,13 @@ BBPlugin.register('vintagestory', { title: "Vintage Story", - author: "Malik12tree,Crabb", + author: "Malik12tree, Crabb", icon: "park", description: "Export and import of Vintage Story shapes", tags: ["Vintage Story"], version: "1.0.2", min_version: "4.9.2", - variant: "both", + variant: "desktop", await_loading: true, creation_date: "2023-12-22", onload() { @@ -46,8 +46,8 @@ icon: 'park', image_editor: false, integer_size: false, - java_face_properties: true, - locators: true, + java_face_properties: false, + locators: false, meshes: false, model_identifier: false, optional_box_uv: false, @@ -249,13 +249,20 @@ if (child.name === obj.name) { let promotedChild = createElement(elements, child, null, parentFrom, parentRot) nextChildren = promotedChild.children + console.log("name = " + child.name) + console.log("parent rot = " + parentRot) parentFrom = child.from parentRot = child.rotation + console.log("child rot = " + parentRot) + + console.log("child origin = " + [child.origin[0], child.origin[1], child.origin[2]]) + console.log("group origin = " + [obj.origin[0], obj.origin[1], obj.origin[2]]) + } } for (let child of obj.children) { - createElement(nextChildren, child, obj.name, parentFrom) + createElement(nextChildren, child, obj.name, parentFrom)//, parentRot) } } } @@ -448,76 +455,86 @@ // Resolve elements for (let element of model.elements) { - parseElement(element, root_group, [0, 0, 0], new_elements, new_textures); + parseElement(element, root_group, new_elements, new_textures); } // Read animations - for (let modelAni of model.animations) { - var newAnimation = new Animation() - newAnimation.name = modelAni.name - newAnimation.snapping = 30 - newAnimation.length = modelAni.quantityframes / 30 - if (modelAni.onAnimationEnd === "Stop") - newAnimation.loop = "once" - else if (modelAni.onAnimationEnd === "Hold") - newAnimation.loop = "hold" - newAnimation.add() - Animation.selected = newAnimation - - let boneAnimators = {} - - for (let modelKf of modelAni.keyframes) { - Object.keys(modelKf.elements).forEach((bonename) => { - var boneAnimator = boneAnimators[bonename] - if (boneAnimator == undefined) { - var group = Project.groups.find(e => e.name == bonename) - let uuid = group.uuid - boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) - boneAnimator.type = "bone" - boneAnimators[bonename] = boneAnimator - newAnimation.animators[group.uuid] = boneAnimator - } + if (model.animations) { + for (let modelAni of model.animations) { + var newAnimation = new Animation() + newAnimation.name = modelAni.name + newAnimation.snapping = 30 + newAnimation.length = modelAni.quantityframes / 30 + if (modelAni.onAnimationEnd === "Stop") + newAnimation.loop = "once" + else if (modelAni.onAnimationEnd === "Hold") + newAnimation.loop = "hold" + newAnimation.add() + Animation.selected = newAnimation + + let boneAnimators = {} + + for (let modelKf of modelAni.keyframes) { + Object.keys(modelKf.elements).forEach((bonename) => { + var boneAnimator = boneAnimators[bonename] + if (boneAnimator == undefined) { + var group = Project.groups.find(e => e.name == bonename) + let uuid = group.uuid + boneAnimator = new BoneAnimator(uuid, newAnimation, bonename) + boneAnimator.type = "bone" + boneAnimators[bonename] = boneAnimator + newAnimation.animators[group.uuid] = boneAnimator + } - var frame = modelKf.frame / 30 - var modelBone = modelKf.elements[bonename] + var frame = modelKf.frame / 30 + var modelBone = modelKf.elements[bonename] - if (modelBone.offsetX != undefined) { - var val = { x: modelBone.offsetX * -1, y: modelBone.offsetY, z: modelBone.offsetZ } - var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) - } - if (modelBone.rotationX != undefined) { - var val = { x: modelBone.rotationX * -1, y: modelBone.rotationY * -1, z: modelBone.rotationZ } - var kf = boneAnimator.createKeyframe(val, frame, "rotation", false, false) - } - }) - } - Object.keys(boneAnimators).forEach(function (key) { - boneAnimators[key].addToTimeline() - }); + if (modelBone.offsetX != undefined) { + var val = { x: modelBone.offsetX * -1, y: modelBone.offsetY, z: modelBone.offsetZ } + var kf = boneAnimator.createKeyframe(val, frame, "position", false, false) + } + if (modelBone.rotationX != undefined) { + var val = { x: modelBone.rotationX * -1, y: modelBone.rotationY * -1, z: modelBone.rotationZ } + var kf = boneAnimator.createKeyframe(val, frame, "rotation", false, false) + } + }) + } + Object.keys(boneAnimators).forEach(function (key) { + boneAnimators[key].addToTimeline() + }); - Animator.preview() + Animator.preview() + } } - function parseElement(element, group, parentPositionOrigin, new_elements, new_textures) { + function parseElement(element, group, new_elements, new_textures, parentPositionOrigin = [0, 0, 0], parentRotation = [0, 0, 0]) { // From/to - let from = [element.from[0] + parentPositionOrigin[0], element.from[1] + parentPositionOrigin[1], element.from[2] + parentPositionOrigin[2]]; - let to = [element.to[0] + parentPositionOrigin[0], element.to[1] + parentPositionOrigin[1], element.to[2] + parentPositionOrigin[2]]; + let from = [ + element.from[0] + parentPositionOrigin[0], + element.from[1] + parentPositionOrigin[1], + element.from[2] + parentPositionOrigin[2] + ]; + let to = [ + element.to[0] + parentPositionOrigin[0], + element.to[1] + parentPositionOrigin[1], + element.to[2] + parentPositionOrigin[2] + ] // Rotation origin - let rotationOrigin = [0, 0, 0] + let rotationOrigin = [parentPositionOrigin[0], parentPositionOrigin[1], parentPositionOrigin[2]] if (element.rotationOrigin) { rotationOrigin = [ - element.rotationOrigin[0], - element.rotationOrigin[1], - element.rotationOrigin[2] + element.rotationOrigin[0] + parentPositionOrigin[0], + element.rotationOrigin[1] + parentPositionOrigin[1], + element.rotationOrigin[2] + parentPositionOrigin[2] ]; } // Rotation let rotation = [ - element.rotationX == undefined ? 0 : element.rotationX, - element.rotationY == undefined ? 0 : element.rotationY, - element.rotationZ == undefined ? 0 : element.rotationZ + (element.rotationX == undefined ? 0 : element.rotationX) + parentRotation[0], + (element.rotationY == undefined ? 0 : element.rotationY) + parentRotation[1], + (element.rotationZ == undefined ? 0 : element.rotationZ) + parentRotation[2] ]; // Check for children @@ -526,27 +543,28 @@ element.children != null && element.children.length > 0; - var isZeroSize = - from[0] == to[0] && - from[1] == to[1] && - from[2] == to[2]; - // Create group and descend children if required var parent_group = group; if (hasChildren) { parent_group = new Group().extend({ name: element.name, - origin: rotationOrigin, - rotation: rotation + origin: [0,0,0], + rotation: [0,0,0] }).init().addTo(group); new_elements.push(parent_group) for (let child_element of element.children) { - parseElement(child_element, parent_group, from, new_elements, new_textures); + parseElement(child_element, parent_group, new_elements, new_textures, from, rotation); } } + // Check for zero size + var isZeroSize = + from[0] == to[0] && + from[1] == to[1] && + from[2] == to[2]; + // If the cube is a dummy for animations, ignore it. if (!isZeroSize) { // Create cube From 0657b659b4a2ab19f4aa60d550be9e9f3594e0d7 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 12:48:50 +0000 Subject: [PATCH 15/19] update for PR --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 791f039b..ae1dd1fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ + .DS_Store -desktop.ini \ No newline at end of file +desktop.ini From 0c4b824b52acb26beb0e1d337008551abc152d00 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 12:50:42 +0000 Subject: [PATCH 16/19] metadata fix --- plugins.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins.json b/plugins.json index 692316df..a9f06006 100644 --- a/plugins.json +++ b/plugins.json @@ -537,11 +537,11 @@ "min_version": "4.0.0", "variant": "desktop" }, - "vintagestory_models": { - "title": "Vintage Story Models", + "vintagestory": { + "title": "Vintage Story", "author": "Malik12tree, Crabb", "icon": "park", - "description": "Allows to export VintageStory models.", + "description": "Allows to export and import VintageStory models.", "version": "1.0.2", "variant": "desktop" }, From 835dadf22bc951220b6eb595adeec4bd1a1881be Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 13:09:25 +0000 Subject: [PATCH 17/19] dont try and set the default path for mac or linux --- plugins/vintagestory/vintagestory.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index b9ad4abe..b38a007e 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -21,13 +21,24 @@ await_loading: true, creation_date: "2023-12-22", onload() { - setting_gamepath = new Setting('vs_gamepath', { - name: 'Vintage Story game folder', - description: 'Set this to the install path', - category: 'defaults', - value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory', - type: 'text', - }); + if (Blockbench.operating_system === "Windows") { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory/assets/survival/textures', + type: 'text', + }); + } + else { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: '', + type: 'text', + }); + } var format = new ModelFormat('vintagestory', { id: "vintagestorymodel", @@ -408,7 +419,7 @@ texture.id = key // Update game namespace from settings - namespace["game"] = settings.vs_gamepath.value.replaceAll('\\', '/') + "/assets/survival/textures" + namespace["game"] = settings.vs_gamepath.value // Update blank/relative namespace if we're in an assets folder if (path.includes("assets")) { From c5b3999bc27c6a7b6637d04d1afddff88a75f262 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 13:12:54 +0000 Subject: [PATCH 18/19] check isApp before using game textures --- plugins.json | 2 +- plugins/vintagestory/vintagestory.js | 41 +++++++++++++++------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/plugins.json b/plugins.json index a9f06006..30081ff3 100644 --- a/plugins.json +++ b/plugins.json @@ -543,7 +543,7 @@ "icon": "park", "description": "Allows to export and import VintageStory models.", "version": "1.0.2", - "variant": "desktop" + "variant": "both" }, "endimations_exporter": { "title": "Endimator Animations Exporter", diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index b38a007e..5a58ad58 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -17,27 +17,29 @@ tags: ["Vintage Story"], version: "1.0.2", min_version: "4.9.2", - variant: "desktop", + variant: "both", await_loading: true, creation_date: "2023-12-22", onload() { - if (Blockbench.operating_system === "Windows") { - setting_gamepath = new Setting('vs_gamepath', { - name: 'Vintage Story game textures folder', - description: 'Set this to the base game texture folder', - category: 'defaults', - value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory/assets/survival/textures', - type: 'text', - }); - } - else { - setting_gamepath = new Setting('vs_gamepath', { - name: 'Vintage Story game textures folder', - description: 'Set this to the base game texture folder', - category: 'defaults', - value: '', - type: 'text', - }); + if (isApp) { + if (Blockbench.operating_system === "Windows") { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory/assets/survival/textures', + type: 'text', + }); + } + else { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: '', + type: 'text', + }); + } } var format = new ModelFormat('vintagestory', { @@ -419,7 +421,8 @@ texture.id = key // Update game namespace from settings - namespace["game"] = settings.vs_gamepath.value + if (isApp) + namespace["game"] = settings.vs_gamepath.value // Update blank/relative namespace if we're in an assets folder if (path.includes("assets")) { From 9520415de66ca00aee237248d34dab08e2d88d15 Mon Sep 17 00:00:00 2001 From: EdibleCrab Date: Sun, 21 Jan 2024 13:21:33 +0000 Subject: [PATCH 19/19] only default the path on isApp && Windows --- plugins/vintagestory/vintagestory.js | 58 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/plugins/vintagestory/vintagestory.js b/plugins/vintagestory/vintagestory.js index 5a58ad58..49544d67 100644 --- a/plugins/vintagestory/vintagestory.js +++ b/plugins/vintagestory/vintagestory.js @@ -21,25 +21,23 @@ await_loading: true, creation_date: "2023-12-22", onload() { - if (isApp) { - if (Blockbench.operating_system === "Windows") { - setting_gamepath = new Setting('vs_gamepath', { - name: 'Vintage Story game textures folder', - description: 'Set this to the base game texture folder', - category: 'defaults', - value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory/assets/survival/textures', - type: 'text', - }); - } - else { - setting_gamepath = new Setting('vs_gamepath', { - name: 'Vintage Story game textures folder', - description: 'Set this to the base game texture folder', - category: 'defaults', - value: '', - type: 'text', - }); - } + if (isApp && Blockbench.operating_system === "Windows") { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: process.env.APPDATA.replaceAll('\\', '/') + '/Vintagestory/assets/survival/textures', + type: 'text', + }); + } + else { + setting_gamepath = new Setting('vs_gamepath', { + name: 'Vintage Story game textures folder', + description: 'Set this to the base game texture folder', + category: 'defaults', + value: '', + type: 'text', + }); } var format = new ModelFormat('vintagestory', { @@ -270,7 +268,7 @@ console.log("child origin = " + [child.origin[0], child.origin[1], child.origin[2]]) console.log("group origin = " + [obj.origin[0], obj.origin[1], obj.origin[2]]) - + } } @@ -524,15 +522,15 @@ function parseElement(element, group, new_elements, new_textures, parentPositionOrigin = [0, 0, 0], parentRotation = [0, 0, 0]) { // From/to let from = [ - element.from[0] + parentPositionOrigin[0], - element.from[1] + parentPositionOrigin[1], - element.from[2] + parentPositionOrigin[2] - ]; + element.from[0] + parentPositionOrigin[0], + element.from[1] + parentPositionOrigin[1], + element.from[2] + parentPositionOrigin[2] + ]; let to = [ - element.to[0] + parentPositionOrigin[0], - element.to[1] + parentPositionOrigin[1], - element.to[2] + parentPositionOrigin[2] - ] + element.to[0] + parentPositionOrigin[0], + element.to[1] + parentPositionOrigin[1], + element.to[2] + parentPositionOrigin[2] + ] // Rotation origin let rotationOrigin = [parentPositionOrigin[0], parentPositionOrigin[1], parentPositionOrigin[2]] @@ -562,8 +560,8 @@ if (hasChildren) { parent_group = new Group().extend({ name: element.name, - origin: [0,0,0], - rotation: [0,0,0] + origin: [0, 0, 0], + rotation: [0, 0, 0] }).init().addTo(group); new_elements.push(parent_group)