This commit is contained in:
JannisX11 2021-01-09 14:57:04 +01:00
commit aaf97b6554
40 changed files with 580 additions and 1720 deletions

View File

@ -220,6 +220,9 @@
margin-top: 5px;
float: left;
}
#settings h3 > i.settings_expand_icon {
opacity: 0.7;
}
#settings h3:hover {
color: var(--color-light);
}
@ -249,7 +252,7 @@
#settingslist li li {
padding: 5px 0;
}
#settingslist li li:hover {
#settingslist li li:hover input[type=checkbox] {
color: var(--color-light);
}
#settingslist li > .setting_element {
@ -275,11 +278,10 @@
#settingslist .setting_name {
font-size: 1.1em;
height: 24px;
color: var(--color-light);
}
#settingslist .setting_description {
font-size: 0.9em;
color: var(--color-text);
font-size: 0.94em;
color: var(--color-subtle_text);
}
#settingslist input[type=number] {
height: 28px;
@ -331,18 +333,17 @@
}
#keybindlist li > div.keybindslot {
width: calc(48% - 32px);
padding: 2px;
padding: 4px;
padding-left: 8px;
height: 30px;
background-color: var(--color-button);
background-color: var(--color-back);
border: 1px solid var(--color-border);
font-size: 0.9em;
font-size: 0.94em;
overflow: hidden;
white-space: nowrap;
display: inline-block;
}
#keybindlist li > div.keybindslot:hover {
background-color: var(--color-selected);
color: var(--color-light);
}
#keybindlist li > div.keybindslot.conflict {
@ -352,13 +353,14 @@
background: transparent;
width: calc(52% - 28px);
text-align: right;
padding: 2px;
padding: 4px;
padding-left: 8px;
vertical-align: top;
display: inline-block;
}
#keybindlist > li > ul > li {
position: relative;
display: flex;
}
#keybindlist > li {
width: 100%;
@ -382,7 +384,8 @@
}
.color_field p {
margin: 0;
font-size: 0.9em;
font-size: 0.94em;
color: var(--color-subtle_text);
}
.color_field h4 {
margin: 0;
@ -633,9 +636,11 @@
min-height: 80px;
}
#plugin_list li {
border: 1px solid var(--color-border);
height: 128px;
overflow-y: hidden;
margin: 8px;
background-color: var(--color-ui);
margin-right: 2px;
}
#plugin_list li.expanded {
min-height: 128px;
@ -660,7 +665,7 @@
text-align: right;
}
#plugin_list li .button_bar.tiny {
opacity: 0.6;
color: var(--color-subtle_text);
font-size: 0.86em;
padding-right: 2px;
}
@ -705,12 +710,12 @@
}
#plugin_list .author {
opacity: 0.6;
color: var(--color-subtle_text);
font-size: 0.86em;
clear: both;
}
#plugin_list .description {
font-size: 0.9em;
font-size: 0.94em;
max-height: 48px;
margin-right: 12px;
}
@ -723,7 +728,7 @@
.no_plugin_message {
text-align: center;
margin-top: 30px;
opacity: 0.5;
color: var(--color-subtle_text);
}
.search_bar {
float: right;

5
css/fontawesome.css vendored
View File

@ -4302,6 +4302,10 @@ readers do not read off random characters that represent icons */
src: url("../font/fa-brands-400.eot");
src: url("../font/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../font/fa-brands-400.woff2") format("woff2"), url("../font/fa-brands-400.woff") format("woff"), url("../font/fa-brands-400.ttf") format("truetype"), url("../font/fa-brands-400.svg#fontawesome") format("svg"); }
.fa {
font-family: 'Font Awesome 5 Free';
font-weight: 900; }
.fab {
font-family: 'Font Awesome 5 Brands'; }
@font-face {
@ -4323,7 +4327,6 @@ readers do not read off random characters that represent icons */
src: url("../font/fa-solid-900.eot");
src: url("../font/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../font/fa-solid-900.woff2") format("woff2"), url("../font/fa-solid-900.woff") format("woff"), url("../font/fa-solid-900.ttf") format("truetype"), url("../font/fa-solid-900.svg#fontawesome") format("svg"); }
.fa,
.fas {
font-family: 'Font Awesome 5 Free';
font-weight: 900; }

View File

@ -133,7 +133,6 @@
}
.bar {
height: 30px;
margin-top: 2px;
}
.bar > * {
float: left;

View File

@ -1,7 +1,4 @@
/*Panel*/
.panel {
padding-bottom: 4px;
}
.panel.grow > .panel_inside {
flex-grow: 1;
display: flex;
@ -42,7 +39,6 @@
height: 30px;
cursor: default;
text-align: center;
font-size: 0.9em;
flex-grow: 1;
overflow: hidden;
}
@ -74,6 +70,7 @@
position: relative;
display: flex;
margin-right: 2px;
margin-top: 2px;
}
.bar.slider_input_combo input.tool[type="range"] {
float: none;
@ -130,11 +127,12 @@
margin-bottom: 180px;
}
.outliner_node .outliner_object i, .outliner_object i[class^="icon-"] {
flex: 0 0 20px;
text-align: center;
width: 21px;
padding-top: 4px;
}
.outliner_node .outliner_object i.icon_off {
opacity: 0.56;
color: var(--color-subtle_text);
}
.outliner_opener_placeholder {
width: 18px;
@ -142,6 +140,7 @@
float: left;
}
.outliner_object {
display: flex;
width: 100%;
padding: 2px;
box-sizing: border-box;
@ -213,20 +212,18 @@
#cubes_list > div > ul > li.outliner_node.parent_li {
border: none !important;
}
.outliner_object input.cube_name {
width: calc(100% - 84px);
.outliner_object > input {
width: 0;
flex: 1 0 0;
padding-right: 5px;
padding-left: 5px;
pointer-events: none;
}
.outliner_object input.cube_name.renaming {
.outliner_object > input.renaming {
pointer-events: auto;
}
.outliner_object a {
width: 21px;
display: inline-block;
i.outliner_toggle {
font-size: 15px;
float: right;
}
i.icon-open-state {
opacity: 0.7;
@ -265,7 +262,7 @@
background-color: var(--color-selected);
box-shadow: 0 0.4px 3.5px rgba(0, 0, 0, 0.6);
}
body > .outliner_object a {
body > .outliner_object .outliner_toggle {
display: none;
}
div#outliner_stats {
@ -274,12 +271,6 @@
margin-top: 4px;
font-weight: normal;
}
#outliner.more_options input.cube_name {
width: calc(100% - 148px);
}
#outliner:not(.more_options) a.advanced_option {
display: none;
}
#particle_label {
float: right;
margin-right: 10px;
@ -325,10 +316,10 @@
color: var(--color-light);
}
.texture > i:not(.clickable) {
opacity: 0.5;
color: var(--color-subtle_text);
}
.texture i.icon_off {
opacity: 0.56;
color: var(--color-subtle_text);
}
div.texture_icon_wrapper {
height: 48px;
@ -371,7 +362,7 @@
height: 20px;
overflow: hidden;
font-size: 0.9em;
opacity: 0.6;
color: var(--color-subtle_text);
cursor: default;
}
.texture_error {
@ -414,7 +405,48 @@
margin-left: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
#texture_animation_playback {
display: flex;
}
#texture_animation_timeline {
display: flex;
position: relative;
width: 0;
flex-grow: 1;
padding-left: 6px;
padding-right: 2px;
background-color: var(--color-back);
border: 1px solid var(--color-border);
}
#texture_animation_timeline .texture_animation_frame {
flex-grow: 1;
width: 1px;
height: 8px;
margin-top: 20px;
border-left: 1px solid var(--color-text);
opacity: 0.3;
pointer-events: none;
}
#texture_animation_timeline #animated_texture_playhead {
position: absolute;
height: 28px;
border-style: solid;
border-width: 8px;
border-color: transparent;
border-top-color: var(--color-accent);
border-radius: 3px;
margin-left: -2px;
pointer-events: none;
}
#texture_animation_timeline #animated_texture_playhead::before {
position: absolute;
content: "";
height: 28px;
border-left: 2px solid var(--color-accent);
top: -8px;
left: -1px;
}
/*Animations*/
@ -430,7 +462,6 @@
position: relative;
vertical-align: middle;
padding: 8px;
padding-left: 16px;
box-sizing: border-box;
}
.animation:hover {
@ -439,6 +470,9 @@
.animation.selected {
background: var(--color-selected);
}
ul.indented .animation {
padding-left: 16px;
}
.animation > i {
margin-right: 4px;
}
@ -466,8 +500,7 @@
color: var(--color-light);
}
.in_list_button.unclickable {
opacity: 0.5;
color: var(--color-text) !important;
color: var(--color-subtle_text) !important;
pointer-events: none;
}
@ -492,6 +525,9 @@
}
/* Keyframe Panel */
.panel#keyframe .bar {
margin-top: 2px;
}
.panel#keyframe .tabs_small label {
font-size: 1em;
height: 30px;
@ -821,13 +857,16 @@
.animator_channel_bar .channel_head .text_button .channel_mute {
font-size: 11pt;
}
.animator_channel_bar .channel_head .text_button .fa-eye-slash {
color: var(--color-subtle_text);
}
.channel_head span {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
}
.animator_channel_bar .channel_head span {
opacity: 0.75
color: var(--color-subtle_text);
}
.keyframe_section {
flex-grow: 1;
@ -1111,7 +1150,7 @@
user-select: text;
}
#chat_history li span.timestamp {
opacity: 0.6;
color: var(--color-subtle_text);
font-size: 0.8em;
float: right;
clear: both;

View File

@ -112,8 +112,6 @@
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(../font/MaterialIcons-Regular.woff2) format('woff2'),
url(../font/MaterialIcons-Regular.woff) format('woff'),
url(../font/MaterialIcons-Regular.ttf) format('truetype');
}
@font-face {
@ -310,6 +308,7 @@
--color-text: #cacad4;
--color-light: #f4f3ff;
--color-accent_text: #000006;
--color-subtle_text: #848891;
--color-bright_ui: #f4f3ff;
--color-grid: #495061;
--color-checkerboard: #1c2026;
@ -546,7 +545,7 @@
color: var(--color-light);
}
input[type=checkbox][disabled=disabled] {
opacity: 0.6;
color: var(--color-subtle_text);
}
input[type=radio]::before {
content: "\f111";
@ -558,7 +557,7 @@
content: "\f192";
}
input[type=radio][disabled=disabled] {
opacity: 0.6;
color: var(--color-subtle_text);
}
div.nslide {

View File

@ -59,9 +59,9 @@
height: auto;
padding: 6px 12px;
background: var(--color-ui);
color: var(--color-subtle_text);
margin: 0;
text-transform: uppercase;
opacity: 0.6;
font-size: 1.1em;
}
.panel p {
@ -600,8 +600,12 @@
#start-files left span.icon_wrapper {
height: 22px;
}
#start-files left li p {
font-size: 0.94em;
color: var(--color-subtle_text);
}
#start-files left i {
font-size: 19pt;
font-size: 18pt;
height: 22px;
margin: 2px 8px 0px 0;
display: inline-block;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -89,6 +89,7 @@
<script src="js/outliner/group.js"></script>
<script src="js/outliner/cube.js"></script>
<script src="js/outliner/locator.js"></script>
<script src="js/outliner/null_object.js"></script>
<script src="js/preview/preview.js"></script>
<script src="js/preview/canvas.js"></script>
<script src="js/preview/transformer.js"></script>
@ -104,8 +105,8 @@
<script src="js/animations/timeline.js"></script>
<script src="js/plugin_loader.js"></script>
<script src="js/io/format.js"></script>
<script src="js/io/codec.js"></script>
<script src="js/io/format.js"></script>
<script src="js/io/project.js"></script>
<script src="js/io/io.js"></script>
<script src="js/io/formats/bbmodel.js"></script>
@ -235,7 +236,7 @@
</li>
</ul>
<div class="bar">
<div class="bar" style="margin: 4px 0;">
<div class="search_bar">
<input type="text" class="dark_bordered" id="action_search_bar" oninput="BARS.list.updateSearch()">
<i class="material-icons" id="plugin_search_bar_icon">search</i>
@ -427,7 +428,7 @@
<li v-for="category in structure" v-if="!category.hidden">
<h3 v-on:click="toggleCategory(category)">
<i class="material-icons">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
<i class="material-icons settings_expand_icon">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
{{ category.name }}
</h3>
<ul v-if="category.open">
@ -485,7 +486,7 @@
<ul id="keybindlist">
<li v-for="category in structure" v-if="!category.hidden">
<h3 v-on:click="toggleCategory(category)">
<i class="material-icons f_left">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
<i class="material-icons f_left settings_expand_icon">{{ category.open ? 'expand_more' : 'navigate_next' }}</i>
{{ category.name }}
<i class="material-icons f_right" v-if="category.conflict" style="color: var(--color-close);">fiber_manual_record</i>
</h3>

View File

@ -96,6 +96,9 @@ class Animation {
for (var key in Animation.properties) {
Animation.properties[key].copy(this, copy)
}
if (this.markers.length) {
copy.markers = this.markers.map(marker => marker.getUndoCopy());
}
if (Object.keys(this.animators).length) {
copy.animators = {}
for (var uuid in this.animators) {
@ -478,7 +481,7 @@ class Animation {
type: 'file',
extensions: ['json'],
filetype: 'JSON Animation',
condition: isApp
condition: Animation.properties.path.condition
},
loop: {
label: 'menu.animation.loop',
@ -561,6 +564,7 @@ class Animation {
name: 'menu.animation.save',
id: 'save',
icon: 'save',
condition: () => Format.animation_files,
click(animation) {
animation.save();
}
@ -569,7 +573,7 @@ class Animation {
name: 'menu.animation.open_location',
id: 'open_location',
icon: 'folder',
condition(animation) {return isApp && animation.path && fs.existsSync(animation.path)},
condition(animation) {return isApp && Format.animation_files && animation.path && fs.existsSync(animation.path)},
click(animation) {
shell.showItemInFolder(animation.path);
}
@ -598,10 +602,11 @@ class Animation {
Undo.finishEdit('remove animation', {animations: []})
}}
])
new Property(Animation, 'boolean', 'saved', {default: true})
new Property(Animation, 'string', 'path')
new Property(Animation, 'boolean', 'saved', {default: true, condition: () => Format.animation_files})
new Property(Animation, 'string', 'path', {condition: () => isApp && Format.animation_files})
Blockbench.on('finish_edit', event => {
if (!Format.animation_files) return;
if (event.aspects.animations && event.aspects.animations.length) {
event.aspects.animations.forEach(animation => {
animation.saved = false;
@ -1210,8 +1215,11 @@ const Animator = {
outlines.children.empty()
Canvas.updateAllPositions()
}
if (Animator.animations.length) {
Animator.animations[0].select()
if (Animation.all.length && !Animation.all.includes(Animation.selected)) {
Animation.all[0].select();
}
if (Group.selected) {
Group.selected.select();
}
Animator.preview()
},
@ -1369,7 +1377,10 @@ const Animator = {
if (!json_content || !json_content.particle_effect) return;
if (Animator.particle_effects[path]) {
Animator.particle_effects[path].config.reset().setFromJSON(json_content, {path});
Animator.particle_effects[path].config
.reset()
.setFromJSON(json_content, {path})
.set('file_path', path);
for (var uuid in Animator.particle_effects[path].emitters) {
let emitter = Animator.particle_effects[path].emitters[uuid];
emitter.updateConfig();
@ -1630,8 +1641,7 @@ Animator.MolangParser.global_variables = {
'true': 1,
'false': 0,
get 'query.delta_time'() {
let timecode = new Date().getMilliseconds();
let time = (timecode - Timeline.last_frame_timecode + 1) / 1000;
let time = (Date.now() - Timeline.last_frame_timecode + 1) / 1000;
if (time < 0) time += 1;
return time;
},
@ -1702,7 +1712,7 @@ BARS.defineActions(function() {
new Action('load_animation_file', {
icon: 'fa-file-video',
category: 'animation',
condition: {modes: ['animate']},
condition: {modes: ['animate'], method: () => Format.animation_files},
click: function () {
var path = ModelMeta.export_path
if (isApp) {
@ -1760,6 +1770,7 @@ BARS.defineActions(function() {
new Action('save_all_animations', {
icon: 'save',
category: 'animation',
condition: () => Format.animation_files,
click: function () {
let paths = [];
Animation.all.forEach(animation => {
@ -1835,8 +1846,9 @@ Interface.definePanels(function() {
component: {
name: 'panel-animations',
data() { return {
animations: Animator.animations,
files_folded: {}
animations: Animation.all,
files_folded: {},
animation_files_enabled: true
}},
methods: {
toggle(key) {
@ -1859,6 +1871,15 @@ Interface.definePanels(function() {
},
computed: {
files() {
if (!this.animation_files_enabled) {
return {
'': {
animations: this.animations,
name: '',
hide_head: true
}
}
}
let files = {};
this.animations.forEach(animation => {
let key = animation.path || '';
@ -1878,17 +1899,17 @@ Interface.definePanels(function() {
<div class="toolbar_wrapper animations"></div>
<ul id="animations_list" class="list">
<li v-for="(file, key) in files" :key="key" class="animation_file" @contextmenu.prevent.stop="showFileContextMenu($event, key)">
<div class="animation_file_head" v-on:click.stop="toggle(key)">
<div class="animation_file_head" v-if="!file.hide_head" v-on:click.stop="toggle(key)">
<i v-on:click.stop="toggle(key)" class="icon-open-state fa" :class=\'{"fa-angle-right": files_folded[key], "fa-angle-down": !files_folded[key]}\'></i>
<label :title="key">{{ file.name }}</label>
<div class="in_list_button" v-if="!file.saved" v-on:click.stop="saveFile(key, file)">
<div class="in_list_button" v-if="animation_files_enabled && !file.saved" v-on:click.stop="saveFile(key, file)">
<i class="material-icons">save</i>
</div>
<div class="in_list_button" v-on:click.stop="addAnimation(key)">
<i class="material-icons">add</i>
</div>
</div>
<ul v-if="!files_folded[key]">
<ul v-if="!files_folded[key]" :class="{indented: !file.hide_head}">
<li
v-for="animation in file.animations"
v-bind:class="{ selected: animation.selected }"
@ -1901,7 +1922,7 @@ Interface.definePanels(function() {
>
<i class="material-icons">movie</i>
<label :title="animation.name">{{ animation.name }}</label>
<div class="in_list_button" v-bind:class="{unclickable: animation.saved}" v-on:click.stop="animation.save()">
<div v-if="animation_files_enabled" class="in_list_button" v-bind:class="{unclickable: animation.saved}" v-on:click.stop="animation.save()">
<i v-if="animation.saved" class="material-icons">check_circle</i>
<i v-else class="material-icons">save</i>
</div>
@ -1927,6 +1948,7 @@ Interface.definePanels(function() {
},
component: {
name: 'panel-placeholders',
components: {VuePrismEditor},
data() { return {
text: ''
}},

View File

@ -10,6 +10,12 @@ class TimelineMarker {
Merge.number(this, data, 'color');
Merge.number(this, data, 'time');
}
getUndoCopy() {
return {
color: this.color,
time: this.time,
}
}
callPlayhead() {
Timeline.setTime(this.time)
Animator.preview()
@ -512,7 +518,7 @@ const Timeline = {
Timeline.pause()
Timeline.playing = true
BarItems.play_animation.setIcon('pause')
Timeline.last_frame_timecode = new Date().getMilliseconds();
Timeline.last_frame_timecode = Date.now();
Timeline.interval = setInterval(Timeline.loop, 100/6)
if (Animation.selected.loop == 'hold' && Timeline.time >= (Animation.selected.length||1e3)) {
Timeline.setTime(0)
@ -538,7 +544,7 @@ const Timeline = {
var new_time = Animator.MolangParser.parse('query.anim_time + query.delta_time')
}
Timeline.setTime(Timeline.time + (new_time - Timeline.time) * (Timeline.playback_speed/100));
Timeline.last_frame_timecode = new Date().getMilliseconds();
Timeline.last_frame_timecode = Date.now();
} else {
if (Animation.selected.loop == 'once') {

View File

@ -56,7 +56,7 @@ function canvasGridSize(shift, ctrl) {
}
function updateNslideValues() {
if (selected.length) {
if (Outliner.selected.length) {
BarItems.slider_pos_x.update()
BarItems.slider_pos_y.update()
BarItems.slider_pos_z.update()
@ -71,7 +71,7 @@ function updateNslideValues() {
BarItems.slider_face_tint.update()
}
}
if (selected.length || (Format.bone_rig && Group.selected)) {
if (Outliner.selected.length || (Format.bone_rig && Group.selected)) {
BarItems.slider_origin_x.update()
BarItems.slider_origin_y.update()
BarItems.slider_origin_z.update()
@ -82,13 +82,16 @@ function updateNslideValues() {
if (Format.bone_rig) {
BarItems.bone_reset_toggle.setIcon(Group.selected && Group.selected.reset ? 'check_box' : 'check_box_outline_blank')
} else {
BarItems.rescale_toggle.setIcon(selected[0].rescale ? 'check_box' : 'check_box_outline_blank')
BarItems.rescale_toggle.setIcon(Outliner.selected[0].rescale ? 'check_box' : 'check_box_outline_blank')
}
}
if (Modes.animate && Group.selected) {
BarItems.slider_ik_chain_length.update();
BarItems.ik_enabled.setIcon(Group.selected.ik_enabled ? 'check_box' : 'check_box_outline_blank')
}
if (Texture.all.length) {
BarItems.animated_texture_frame.update();
}
}
function setProjectResolution(width, height, modify_uv) {
if (Project.texture_width / width != Project.texture_width / height) {
@ -139,7 +142,7 @@ function updateProjectResolution() {
}
//Selections
function updateSelection() {
function updateSelection(options = {}) {
elements.forEach(obj => {
if (selected.includes(obj) && !obj.selected && !obj.locked) {
obj.selectLow()
@ -189,7 +192,7 @@ function updateSelection() {
BarItems.cube_counter.update();
updateNslideValues();
updateCubeHighlights();
if (settings.highlight_cubes.value) updateCubeHighlights();
Canvas.updateOrigin();
Transformer.updateSelection();
Transformer.update();

View File

@ -289,14 +289,14 @@ function openDefaultTexturePath() {
if (answer === 0) {
return;
} else if (answer === 1) {
ElecDialogs.showOpenDialog(currentwindow, {
let path = Blockbench.pickDirectory({
title: tl('message.default_textures.select'),
properties: ['openDirectory'],
}, function(filePaths) {
if (filePaths) {
settings.default_path.value = filePaths[0]
}
})
resource_id: 'texture',
});
if (path) {
settings.default_path.value = path;
}
} else {
settings.default_path.value = false
}

View File

@ -2014,7 +2014,7 @@ Interface.definePanels(function() {
<label class="tool" for="gui" onclick="DisplayMode.loadGUI()"><div class="tooltip">${ tl('display.slot.gui') }</div><i class="material-icons">border_style</i></label>
</div>
<p class="reference_model_bar">${ tl('display.reference') }</p>
<div id="display_ref_bar" class="bar tabs_small reference_model_bar">
<div id="display_ref_bar" class="bar tabs_small icon_bar reference_model_bar">
</div>
<div id="display_sliders">

View File

@ -58,6 +58,44 @@ Object.assign(Blockbench, {
}).click()
}
},
pickDirectory(options) {
if (typeof options !== 'object') {options = {}}
/**
resource_id
startpath
title
*/
if (isApp) {
if (!options.startpath && options.resource_id) {
options.startpath = StateMemory.dialog_paths[options.resource_id]
}
let dirNames = electron.dialog.showOpenDialogSync(currentwindow, {
title: options.title ? options.title : '',
dontAddToRecent: true,
properties: ['openDirectory'],
defaultPath: settings.streamer_mode.value
? app.getPath('desktop')
: options.startpath
})
if (!dirNames) return null;
if (options.resource_id) {
StateMemory.dialog_paths[options.resource_id] = PathModule.dirname(dirNames[0]);
StateMemory.save('dialog_paths');
}
return dirNames[0];
} else {
console.warn('Picking directories is currently not supported in the web app');
}
},
read(files, options, cb) {
if (files == undefined) return false;
if (typeof files == 'string') files = [files];
@ -111,7 +149,6 @@ Object.assign(Blockbench, {
}
} else /*text*/ {
var data;
console.log(file)
try {
data = fs.readFileSync(file, readtype == 'text' ? 'utf8' : undefined);
} catch(err) {

View File

@ -66,7 +66,9 @@ class BarItem {
.on('mouseenter', function() {
var tooltip = $(this).find('div.tooltip');
if (!tooltip.length) return;
var description = tooltip.find('.tooltip_description');
if ($(this).parent().parent().hasClass('vertical')) {
tooltip.css('margin', '0')
if ($(this).offset().left > window.innerWidth/2) {
@ -75,7 +77,6 @@ class BarItem {
tooltip.css('margin-left', '34px')
}
} else {
if (!tooltip.length) return;
tooltip.css('margin-left', '0')
var offset = tooltip && tooltip.offset()
@ -83,7 +84,7 @@ class BarItem {
if (offset.right > 4) {
tooltip.css('margin-left', -offset.right+'px')
}
}
// description
if (!description.length) return;
@ -96,6 +97,11 @@ class BarItem {
description.css('margin-left', -offset.right+'px')
}
// height
if ((window.innerHeight - offset.top) < 28) {
tooltip.css('margin-top', -tooltip.height()+'px');
description.css('margin-top', '-51px');
}
}
})
}
@ -415,12 +421,13 @@ class NumSlider extends Widget {
<div class="nslide_overlay">
<div class="color_corner" style="border-color: ${css_color}"></div>
</div>
<div class="tooltip">${this.name}</div>
<div class="nslide tab_target" n-action="${this.id}"></div>
</div>`).get(0);
this.jq_outer = $(this.node)
this.jq_inner = this.jq_outer.find('.nslide');
this.addLabel(data.label);
this.jq_inner
.on('mousedown touchstart', async (event) => {
if (scope.jq_inner.hasClass('editing')) return;
@ -1508,8 +1515,7 @@ const BARS = {
children: [
'import_texture',
'create_texture',
'reload_textures',
'animated_textures'
'reload_textures'
]
})
Toolbars.tools = new Toolbar({

View File

@ -181,11 +181,10 @@ function buildForm(dialog) {
}, fileCB);
break;
case 'folder':
let filePaths = electron.dialog.showOpenDialog(currentwindow, {
properties: ['openDirectory'],
defaultPath: data.value
let path = Blockbench.pickDirectory({
startpath: data.value,
})
if (filePaths) fileCB([{ path: filePaths[0] }]);
if (path) fileCB([{path}]);
break;
case 'save':
Blockbench.export({

View File

@ -412,7 +412,8 @@ $(document).on('keydown mousedown', function(e) {
if (next.length) {
if (next.hasClass('cube_name')) {
var target = Outliner.root.findRecursive('uuid', next.parent().parent().attr('id'))
let uuid = next.parent().parent().attr('id');
var target = OutlinerElement.uuids[uuid];
if (target) {
stopRenameOutliner();
setTimeout(() => {

View File

@ -166,7 +166,7 @@ const Settings = {
new Setting('shift_size', {category: 'snapping', value: 64, type: 'number'});
new Setting('ctrl_size', {category: 'snapping', value: 160, type: 'number'});
new Setting('negative_size',{category: 'snapping', value: false});
new Setting('animation_snap',{category: 'snapping', value: 25, type: 'number'});
new Setting('animation_snap',{category: 'snapping', value: 24, type: 'number'});
//Paint
new Setting('sync_color', {category: 'paint', value: false});

View File

@ -19,6 +19,7 @@ const CustomTheme = {
text: '#cacad4',
light: '#f4f3ff',
accent_text: '#000006',
subtle_text: '#848891',
grid: '#495061',
wireframe: '#576f82',
checkerboard: '#1c2026',

View File

@ -114,6 +114,11 @@ class Codec {
}
this.events[event_name].safePush(cb)
}
removeListener(event_name, cb) {
if (this.events[event_name]) {
this.events[event_name].remove(cb);
}
}
//Delete
delete() {
delete Codecs[this.id];

View File

@ -87,6 +87,7 @@ class ModelFormat {
uv_dialog.all_editors.forEach(editor => {
editor.img.style.objectFit = Format.animated_textures ? 'cover' : 'fill';
})
Interface.Panels.animations.inside_vue._data.animation_files_enabled = this.animation_files;
for (var key in ModelProject.properties) {
if (Project[key] == undefined) {
ModelProject.properties[key].reset(Project);
@ -107,7 +108,7 @@ class ModelFormat {
}
convertTo() {
Undo.history.length = 0;
Undo.history.empty();
Undo.index = 0;
ModelMeta.export_path = '';
@ -216,16 +217,14 @@ class ModelFormat {
}
}
BARS.defineActions(function() {
new ModelFormat({
id: 'free',
icon: 'icon-format_free',
rotate_cubes: true,
bone_rig: true,
centered_grid: true,
optional_box_uv: true,
uv_rotation: true,
animation_mode: true,
codec: Codecs.project
})
new ModelFormat({
id: 'free',
icon: 'icon-format_free',
rotate_cubes: true,
bone_rig: true,
centered_grid: true,
optional_box_uv: true,
uv_rotation: true,
animation_mode: true,
codec: Codecs.project
})

View File

@ -549,7 +549,7 @@ BARS.defineActions(function() {
Format.codec.export()
}
}
if (Format.animation_mode && Animation.all.length) {
if (Format.animation_mode && Format.animation_files && Animation.all.length) {
BarItems.save_all_animations.trigger();
}
} else {

View File

@ -51,7 +51,12 @@ class ModelProject {
}
Outliner.elements.empty();
Outliner.root.purge();
Canvas.materials;
for (var key in Canvas.materials) {
delete Canvas.materials[key];
}
for (var key in Canvas.bones) {
delete Canvas.bones[key];
}
selected.empty();
Group.all.empty();
Group.selected = undefined;
@ -126,6 +131,7 @@ new Property(ModelProject, 'vector', 'visible_box', {
});
new Property(ModelProject, 'boolean', 'layered_textures', {
label: 'dialog.project.layered_textures',
description: 'dialog.project.layered_textures.desc',
condition() {return Format.single_texture}
});
@ -172,6 +178,7 @@ BARS.defineActions(function() {
let entry = form[property.name] = {
label: property.label,
description: property.description,
value: Project[property.name],
type: property.type
}

View File

@ -110,9 +110,6 @@ class Cube extends NonGroup {
this.inflate = 0;
this.rotation = [0, 0, 0];
this.origin = [0, 0, 0];
if (!Format.centered_grid) {
this.origin.V3_set(8, 8, 8);
}
this.visibility = true;
this.autouv = 0
this.parent = 'root';
@ -275,6 +272,7 @@ class Cube extends NonGroup {
}
delete Canvas.meshes[this.uuid]
mesh.geometry.dispose()
if (mesh.outline && mesh.outline.geometry) mesh.outline.geometry.dispose()
}
}
delete Canvas.meshes[this.uuid]
@ -805,11 +803,11 @@ class Cube extends NonGroup {
'delete'
]);
Cube.prototype.buttons = [
Outliner.buttons.visibility,
Outliner.buttons.locked,
Outliner.buttons.export,
Outliner.buttons.autouv,
Outliner.buttons.shading,
Outliner.buttons.autouv
Outliner.buttons.export,
Outliner.buttons.locked,
Outliner.buttons.visibility,
];
Cube.selected = [];
Cube.all = [];
@ -857,7 +855,6 @@ BARS.defineActions(function() {
Blockbench.dispatchEvent( 'add_cube', {object: base_cube} )
Vue.nextTick(function() {
updateSelection()
if (settings.create_rename.value) {
base_cube.rename()
}

View File

@ -211,6 +211,7 @@ class Group extends OutlinerElement {
})
TickUpdates.selection = true
this.constructor.all.remove(this);
delete Canvas.bones[this.uuid];
delete OutlinerElement.uuids[this.uuid];
if (undo) {
cubes.length = 0
@ -442,11 +443,11 @@ class Group extends OutlinerElement {
Group.prototype.isParent = true;
Group.prototype.name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false;
Group.prototype.buttons = [
Outliner.buttons.visibility,
Outliner.buttons.locked,
Outliner.buttons.export,
Outliner.buttons.autouv,
Outliner.buttons.shading,
Outliner.buttons.autouv
Outliner.buttons.export,
Outliner.buttons.locked,
Outliner.buttons.visibility,
];
Group.prototype.needsUniqueName = () => Format.bone_rig;
Group.prototype.menu = new Menu([
@ -500,38 +501,6 @@ function getAllGroups() {
iterate(Outliner.root)
return ta;
}
function addGroup() {
Undo.initEdit({outliner: true});
var add_group = Group.selected
if (!add_group && selected.length) {
add_group = Cube.selected.last()
}
var base_group = new Group({
origin: add_group ? add_group.origin : undefined
})
base_group.addTo(add_group)
base_group.isOpen = true
if (Format.bone_rig) {
base_group.createUniqueName()
}
if (add_group instanceof NonGroup && selected.length > 1) {
selected.forEach(function(s, i) {
s.addTo(base_group)
})
}
base_group.init().select()
Undo.finishEdit('add_group');
loadOutlinerDraggable()
Vue.nextTick(function() {
updateSelection()
if (settings.create_rename.value) {
base_group.rename()
}
base_group.showInOutliner()
Blockbench.dispatchEvent( 'add_group', {object: base_group} )
})
}
window.__defineGetter__('selected_group', () => {
console.warn('selected_group is deprecated. Please use Group.selected instead.')
return Group.selected
@ -545,7 +514,36 @@ BARS.defineActions(function() {
condition: () => Modes.edit,
keybind: new Keybind({key: 71, ctrl: true}),
click: function () {
addGroup();
Undo.initEdit({outliner: true});
var add_group = Group.selected
if (!add_group && selected.length) {
add_group = Cube.selected.last()
}
var base_group = new Group({
origin: add_group ? add_group.origin : undefined
})
base_group.addTo(add_group)
base_group.isOpen = true
if (Format.bone_rig) {
base_group.createUniqueName()
}
if (add_group instanceof NonGroup && selected.length > 1) {
selected.forEach(function(s, i) {
s.addTo(base_group)
})
}
base_group.init().select()
Undo.finishEdit('add_group');
loadOutlinerDraggable()
Vue.nextTick(function() {
updateSelection()
if (settings.create_rename.value) {
base_group.rename()
}
base_group.showInOutliner()
Blockbench.dispatchEvent( 'add_group', {object: base_group} )
})
}
})
new Action({

View File

@ -80,8 +80,8 @@ class Locator extends NonGroup {
Locator.prototype.movable = true;
Locator.prototype.visibility = true;
Locator.prototype.buttons = [
Outliner.buttons.export,
Outliner.buttons.locked,
Outliner.buttons.export
];
Locator.prototype.needsUniqueName = true;
Locator.prototype.menu = new Menu([
@ -107,6 +107,11 @@ BARS.defineActions(function() {
locator.select().createUniqueName();
objs.push(locator);
Undo.finishEdit('add locator');
Vue.nextTick(function() {
if (settings.create_rename.value) {
locator.rename();
}
})
}
})
})

117
js/outliner/null_object.js Normal file
View File

@ -0,0 +1,117 @@
class NullObject extends NonGroup {
constructor(data, uuid) {
super(data, uuid);
for (var key in NullObject.properties) {
NullObject.properties[key].reset(this);
}
if (data) {
this.extend(data);
}
}
extend(object) {
for (var key in NullObject.properties) {
NullObject.properties[key].merge(this, object)
}
this.sanitizeName();
Merge.boolean(this, object, 'locked')
//Merge.boolean(this, object, 'export');
return this;
}
getUndoCopy() {
var copy = new NullObject(this)
copy.uuid = this.uuid
copy.type = this.type;
delete copy.parent;
return copy;
}
getSaveCopy() {
let save = {};
for (var key in NullObject.properties) {
NullObject.properties[key].copy(this, save)
}
//save.export = this.export ? undefined : false;
save.locked = this.locked;
save.uuid = this.uuid;
save.type = 'null_object';
return save;
}
init() {
if (this.parent instanceof Group == false) {
this.addTo(Group.selected)
}
super.init();
TickUpdates.outliner = true;
return this;
}
flip(axis, center) {
var offset = this.from[axis] - center
this.from[axis] = center - offset;
// Name
if (axis == 0 && this.name.includes('right')) {
this.name = this.name.replace(/right/g, 'left').replace(/2$/, '');
} else if (axis == 0 && this.name.includes('left')) {
this.name = this.name.replace(/left/g, 'right').replace(/2$/, '');
}
this.createUniqueName();
return this;
}
getWorldCenter() {
var pos = new THREE.Vector3();
var q = new THREE.Quaternion();
if (this.parent instanceof Group) {
THREE.fastWorldPosition(this.parent.mesh, pos);
this.parent.mesh.getWorldQuaternion(q);
var offset2 = new THREE.Vector3().fromArray(this.parent.origin).applyQuaternion(q);
pos.sub(offset2);
}
var offset = new THREE.Vector3().fromArray(this.from).applyQuaternion(q);
pos.add(offset);
return pos;
}
}
NullObject.prototype.title = tl('data.null_object');
NullObject.prototype.type = 'null_object';
NullObject.prototype.icon = 'fa far fa-circle';
//NullObject.prototype.name_regex = 'a-z0-9_'
NullObject.prototype.movable = true;
NullObject.prototype.visibility = true;
NullObject.prototype.buttons = [
//Outliner.buttons.export,
Outliner.buttons.locked,
];
//NullObject.prototype.needsUniqueName = true;
NullObject.prototype.menu = new Menu([
'copy',
'rename',
'delete'
])
NullObject.selected = [];
NullObject.all = [];
new Property(NullObject, 'string', 'name', {default: 'null_object'})
new Property(NullObject, 'vector', 'from')
BARS.defineActions(function() {
new Action('add_null_object', {
icon: 'far.fa-circle',
category: 'edit',
condition: () => {return Format.animation_mode},
click: function () {
var objs = []
Undo.initEdit({elements: objs, outliner: true});
var null_object = new NullObject().addTo(Group.selected||selected[0]).init();
null_object.select();
objs.push(null_object);
Undo.finishEdit('add null_object');
Vue.nextTick(function() {
if (settings.create_rename.value) {
null_object.rename();
}
})
}
})
})

View File

@ -598,7 +598,7 @@ function parseGroups(array, importGroup, startIndex) {
if (typeof array[i] === 'number') {
var obj = elements[array[i] + (startIndex ? startIndex : 0) ]
} else {
var obj = elements.findRecursive('uuid', array[i])
var obj = OutlinerElement.uuids[array[i]];
}
if (obj) {
obj.removeFromParent()
@ -913,14 +913,11 @@ BARS.defineActions(function() {
keybind: new Keybind({key: 115}),
click: function () {
var state = !$('.panel#outliner').hasClass('more_options')
if (state) {
$('.panel#outliner').addClass('more_options')
BarItems.outliner_toggle.setIcon('dns')
} else {
$('.panel#outliner').removeClass('more_options')
BarItems.outliner_toggle.setIcon('view_stream')
}
Outliner.vue._data.show_advanced_toggles = !Outliner.vue._data.show_advanced_toggles;
BarItems.outliner_toggle.setIcon(Outliner.vue._data.show_advanced_toggles
? 'dns'
: 'view_stream'
)
}
})
new BarText('cube_counter', {
@ -1127,7 +1124,8 @@ Interface.definePanels(function() {
onOpened: function () {},
select: function() {},
children: Outliner.root
}
},
show_advanced_toggles: false
}},
methods: {
openMenu(event) {
@ -1138,7 +1136,7 @@ Interface.definePanels(function() {
<div>
<div class="toolbar_wrapper outliner"></div>
<ul id="cubes_list" class="list" @contextmenu.stop.prevent="openMenu($event)">
<vue-tree :root="root"></vue-tree>
<vue-tree :root="root" :show_advanced_toggles="show_advanced_toggles"></vue-tree>
</ul>
</div>
`

View File

@ -1,5 +1,4 @@
(function () {
'use strict';
var VueTreeItem = Vue.extend({
template:
'<li class="outliner_node" v-bind:class="{ parent_li: node.children && node.children.length > 0}" v-bind:id="node.uuid">' +
@ -14,21 +13,26 @@
'<i v-if="node.children && node.children.length > 0 && (!Animator.open || node.children.some(o => o instanceof Group || o instanceof Locator))" v-on:click.stop="toggle(node)" class="icon-open-state fa" :class=\'{"fa-angle-right": !node.isOpen, "fa-angle-down": node.isOpen}\'></i>' +
'<i v-else class="outliner_opener_placeholder"></i>' +
//Main
'<i :class="node.icon + (settings.outliner_colors.value ? \' ec_\'+node.color : \'\')" v-on:dblclick.stop="if (node.children && node.children.length) {node.isOpen = !node.isOpen;}"></i>' +
'<i :class="node.icon + ((settings.outliner_colors.value && node.color) ? \' ec_\'+node.color : \'\')" v-on:dblclick.stop="if (node.children && node.children.length) {node.isOpen = !node.isOpen;}"></i>' +
'<input type="text" class="cube_name tab_target" v-model="node.name" disabled>' +
'<a v-for="btn in node.buttons" class="ml5" href="javascript:" :title="btn.title" v-on:click.stop="btnClick(btn, node)" v-bind:class="{advanced_option: btn.advanced_option && (btn.id !== \'locked\' || !node.isIconEnabled(btn))}">' +
'<i v-if="node.isIconEnabled(btn) === true" :class="btn.icon"></i>' +
'<i v-else-if="node.isIconEnabled(btn) === \'alt\'" :class="btn.icon_alt"></i>' +
'<i v-else :class="[btn.icon_off, \'icon_off\']"></i>' +
'</a>' +
`<i v-for="btn in node.buttons"
v-if="(!btn.advanced_option || show_advanced_toggles || (btn.id === \'locked\' && node.isIconEnabled(btn)))"
class="outliner_toggle"
:class="getBtnClasses(btn, node)"
:title="btn.title"
v-on:click.stop="btnClick(btn, node)"
></i>` +
'</div>' +
//Other Entries
'<ul v-show="node.isOpen">' +
'<vue-tree-item v-for="item in node.children" :node="item" v-key="item.uuid"></vue-tree-item>' +
'<ul v-if="node.isOpen">' +
'<vue-tree-item v-for="item in node.children" :node="item" :show_advanced_toggles="show_advanced_toggles" v-key="item.uuid"></vue-tree-item>' +
`<div class="outliner_line_guide" v-if="node == Group.selected" v-bind:style="{left: getIndentation(node) + 'px'}"></div>` +
'</ul>' +
'</li>',
props: {
show_advanced_toggles: Boolean,
node: {
type: Object
}
@ -48,6 +52,16 @@
Vue.set(node, 'isOpen', true);
}
},
getBtnClasses: function (btn, node) {
let value = node.isIconEnabled(btn);
if (value === true) {
return [btn.icon];
} else if (value === false) {
return [btn.icon_off, 'icon_off'];
} else {
return [btn.icon_alt];
}
},
btnClick: function (btn, node) {
if (typeof btn.click === 'function') {
btn.click(node);
@ -80,10 +94,11 @@
template: `
<div class="vue-tree">
<ul>
<tree-item :node.sync="root"></tree-item>
<tree-item :node.sync="root" :show_advanced_toggles="show_advanced_toggles"></tree-item>
</ul>
</div>`,
props: {
show_advanced_toggles: Boolean,
root: {
type: Object
}

View File

@ -120,7 +120,7 @@ const Canvas = {
if (display_mode && ground_anim_before) {
ground_animation = ground_anim_before
}
updateCubeHighlights();
if (settings.highlight_cubes.value) updateCubeHighlights();
},
//Main updaters
clear() {
@ -402,11 +402,13 @@ const Canvas = {
var from = cube.from.slice()
from.forEach((v, i) => {
from[i] -= cube.inflate
from[i] -= cube.inflate;
from[i] -= cube.origin[i];
})
var to = cube.to.slice()
to.forEach((v, i) => {
to[i] += cube.inflate
to[i] -= cube.origin[i];
if (from[i] === to[i]) {
to[i] += 0.001
}
@ -416,7 +418,7 @@ const Canvas = {
mesh.scale.set(1, 1, 1)
mesh.position.set(cube.origin[0], cube.origin[1], cube.origin[2])
mesh.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2])
//mesh.geometry.translate(-cube.origin[0], -cube.origin[1], -cube.origin[2])
mesh.rotation.set(0, 0, 0)
mesh.geometry.computeBoundingSphere()

View File

@ -650,7 +650,7 @@ class Preview {
mousemove(event) {
if (Settings.get('highlight_cubes')) {
var data = this.raycast(event);
updateCubeHighlights(data && data.cube);
if (settings.highlight_cubes.value) updateCubeHighlights(data && data.cube);
}
}
mouseup(event) {

View File

@ -859,7 +859,7 @@
return;
}
this.rotation_object = rotation_object;
if (Format.bone_rig && !Modes.animate) {
if (_has_groups && Format.bone_rig && !Modes.animate) {
Canvas.updateAllBones()
}
//Center
@ -1207,7 +1207,7 @@
moveElementsInSpace(difference, axisNumber)
scope.updateSelection()
updateSelection()
}
previousValue = point[axis]
scope.hasChanged = true

View File

@ -51,6 +51,7 @@ class Property {
if (options.condition) this.condition = options.condition;
if (options.exposed == false) this.exposed = false;
if (options.label) this.label = options.label;
if (options.description) this.description = options.description;
if (options.options) this.options = options.options;
}
delete() {

View File

@ -538,6 +538,7 @@ class Texture {
Texture.selected = undefined;
}
textures.splice(textures.indexOf(this), 1)
delete Canvas.materials[this.uuid];
if (!no_update) {
Canvas.updateAllFaces()
TextureAnimator.updateButton()
@ -1186,30 +1187,39 @@ TextureAnimator = {
}
},
nextFrame() {
var animated_tex = []
textures.forEach(function(tex, i) {
var animated_textures = []
textures.forEach(tex => {
if (tex.frameCount > 1) {
if (tex.currentFrame >= tex.frameCount-1) {
tex.currentFrame = 0
} else {
tex.currentFrame++;
}
$($('.texture').get(i)).find('img').css('margin-top', (tex.currentFrame*-48)+'px')
animated_tex.push(tex)
animated_textures.push(tex)
}
})
if (animated_tex.includes(main_uv.texture)) {
TextureAnimator.update(animated_textures);
},
update(animated_textures) {
let maxFrame = 0;
animated_textures.forEach(tex => {
$(`.texture[texid="${tex.uuid}"]`).find('img').css('margin-top', (tex.currentFrame*-48)+'px');
maxFrame = Math.max(maxFrame, tex.currentFrame);
})
if (animated_textures.includes(main_uv.texture)) {
main_uv.img.style.objectPosition = `0 -${main_uv.texture.currentFrame * main_uv.inner_height}px`;
}
Cube.all.forEach(function(cube) {
Cube.all.forEach(cube => {
var update = false
for (var face in cube.faces) {
update = update || animated_tex.includes(cube.faces[face].getTexture());
update = update || animated_textures.includes(cube.faces[face].getTexture());
}
if (update) {
Canvas.updateUV(cube, true)
}
})
BarItems.animated_texture_frame.update();
Interface.Panels.textures.inside_vue._data.currentFrame = maxFrame;
},
reset() {
TextureAnimator.stop();
@ -1298,12 +1308,12 @@ BARS.defineActions(function() {
path.splice(-1)
path = path.join(osfs)
let filePaths = electron.dialog.showOpenDialogSync(currentwindow, {
properties: ['openDirectory'],
defaultPath: path
let dirPath = Blockbench.pickDirectory({
resource_id: 'texture',
startpath: path,
})
if (filePaths && filePaths.length) {
var new_path = filePaths[0]
if (dirPath && dirPath.length) {
var new_path = dirPath[0]
Undo.initEdit({textures})
textures.forEach(function(t) {
if (typeof t.path === 'string' && t.path.includes(path)) {
@ -1314,26 +1324,39 @@ BARS.defineActions(function() {
}
}
})
function textureAnimationCondition() {
return Format.animated_textures && Texture.all.find(tex => tex.frameCount > 1);
}
new Action('animated_textures', {
icon: 'play_arrow',
category: 'textures',
condition: function() {
if (!Format.animated_textures) return false;
var i = 0;
var show = false;
while (i < textures.length) {
if (textures[i].frameCount > 1) {
show = true;
break;
}
i++;
}
return show;
},
condition: textureAnimationCondition,
click: function () {
TextureAnimator.toggle()
}
})
function getSliderTexture() {
return [Texture.getDefault(), ...Texture.all].find(tex => tex && tex.frameCount > 1);
}
new NumSlider('animated_texture_frame', {
category: 'textures',
condition: textureAnimationCondition,
getInterval(event) {
return 1;
},
get: function() {
let tex = getSliderTexture()
return tex ? tex.currentFrame+1 : 0;
},
change: function(modify) {
let tex = getSliderTexture()
if (tex) {
tex.currentFrame = Math.clamp(modify(tex.currentFrame+1), 1, tex.frameCount) - 1;
TextureAnimator.update([tex]);
}
}
})
})
Interface.definePanels(function() {
@ -1346,10 +1369,15 @@ Interface.definePanels(function() {
toolbars: {
head: Toolbars.texturelist
},
onResize() {
this.inside_vue._data.currentFrame += 1;
this.inside_vue._data.currentFrame -= 1;
},
component: {
name: 'panel-textures',
data() { return {
textures: Texture.all
textures: Texture.all,
currentFrame: 0,
}},
methods: {
openMenu(event) {
@ -1365,6 +1393,48 @@ Interface.definePanels(function() {
}
return message;
}
},
slideTimelinePointer(e1) {
let scope = this;
if (!this.$refs.timeline) return;
let timeline_offset = $(this.$refs.timeline).offset().left + 8;
let timeline_width = this.$refs.timeline.clientWidth - 8;
let maxFrameCount = this.maxFrameCount;
function slide(e2) {
convertTouchEvent(e2);
let pos = e2.clientX - timeline_offset;
scope.currentFrame = Math.clamp(Math.round((pos / timeline_width) * maxFrameCount), 0, maxFrameCount-1);
let textures = Texture.all.filter(tex => tex.frameCount > 1);
textures.forEach(tex => {
tex.currentFrame = scope.currentFrame % tex.frameCount;
})
TextureAnimator.update(textures);
}
function off(e3) {
removeEventListeners(document, 'mousemove touchmove', slide);
removeEventListeners(document, 'mouseup touchend', off);
}
addEventListeners(document, 'mousemove touchmove', slide);
addEventListeners(document, 'mouseup touchend', off);
slide(e1);
},
getPlayheadPos() {
if (!this.$refs.timeline) return 0;
let width = this.$refs.timeline.clientWidth - 8;
return Math.clamp((this.currentFrame / this.maxFrameCount) * width, 0, width);
}
},
computed: {
maxFrameCount() {
let count = 0;
this.textures.forEach(tex => {
if (tex.frameCount > count) count = tex.frameCount;
});
return count;
}
},
template: `
@ -1405,8 +1475,19 @@ Interface.definePanels(function() {
</i>
</li>
</ul>
<div id="texture_animation_playback" class="bar" v-show="maxFrameCount">
<div class="tool_wrapper"></div>
<div id="texture_animation_timeline" ref="timeline" @mousedown="slideTimelinePointer">
<div class="texture_animation_frame" v-for="i in maxFrameCount"></div>
<div id="animated_texture_playhead" :style="{left: getPlayheadPos() + 'px'}"></div>
</div>
</div>
</div>
`
`,
mounted() {
BarItems.animated_textures.toElement('#texture_animation_playback .tool_wrapper')
BarItems.animated_texture_frame.setWidth(52).toElement('#texture_animation_playback .tool_wrapper')
}
},
menu: new Menu([
'import_texture',

View File

@ -121,7 +121,8 @@ function moveCubesRelative(difference, index, event) { //Multiple
difference *= canvasGridSize(event.shiftKey, event.ctrlOrCmd);
}
moveElementsInSpace(difference, axes[index])
moveElementsInSpace(difference, axes[index]);
updateSelection();
Undo.finishEdit('move')
}
@ -643,7 +644,6 @@ function moveElementsInSpace(difference, axis) {
Canvas.adaptObjectPosition(el);
}
})
TickUpdates.selection = true;
}
//Rotate

View File

@ -16,6 +16,7 @@
"data.cube": "Cube",
"data.locator": "Locator",
"data.null_object": "Null Object",
"data.group": "Group",
"data.texture": "Texture",
"data.origin": "Pivot",
@ -63,16 +64,16 @@
"format.free": "Generic Model",
"format.free.desc": "Model without restrictions for game engines, rendering etc.",
"format.skin": "Skin",
"format.skin": "Minecraft Skin",
"format.skin.desc": "Edit player and entity skins",
"format.java_block": "Java Block/Item",
"format.java_block.desc": "Block model for Java Edition. Size and rotations are limited.",
"format.java_block.desc": "Block or item model for Minecraft Java Edition.",
"format.bedrock": "Bedrock Model",
"format.bedrock.desc": "Model for Bedrock Edition",
"format.bedrock.desc": "Model for Minecraft Bedrock Edition",
"format.bedrock_old": "Bedrock Legacy Model",
"format.bedrock_old.desc": "Pre-1.12 Bedrock Edition entity model",
"format.bedrock_old.desc": "Pre-1.12 Minecraft Bedrock Edition entity model",
"format.modded_entity": "Modded Entity",
"format.modded_entity.desc": "Entity model for mods. Can be exported as .java class files.",
"format.modded_entity.desc": "Entity model for Minecraft mods. Can be exported as .java class files.",
"format.optifine_entity": "OptiFine Entity",
"format.optifine_entity.desc": "Custom entity model for OptiFine",
"format.optifine_part": "OptiFine Part",
@ -250,6 +251,7 @@
"dialog.project.modded_entity_version": "Export Version",
"dialog.project.ao": "Ambient Occlusion",
"dialog.project.layered_textures": "Layered Textures",
"dialog.project.layered_textures.desc": "Enable preview mode for layered textures. Allows you to see up to 3 textures layered on top of each other.",
"dialog.project.uv_mode": "UV Mode",
"dialog.project.uv_mode.box_uv": "Box UV",
"dialog.project.uv_mode.face_uv": "Per-face UV",
@ -425,6 +427,8 @@
"layout.color.light.desc": "Highlighted text",
"layout.color.accent_text": "Accent Text",
"layout.color.accent_text.desc": "Text on bright or accent elements",
"layout.color.subtle_text": "Subtle Text",
"layout.color.subtle_text.desc": "Secondary text color with less contrast",
"layout.color.checkerboard": "Checkerboard",
"layout.color.checkerboard.desc": "Background of canvas and UV editor",
"layout.font.main": "Main Font",
@ -779,6 +783,8 @@
"action.add_cube.desc": "Adds a new cube",
"action.add_locator": "Add Locator",
"action.add_locator.desc": "Adds a new locator to control positions of particles, leashes etc",
"action.add_null_object": "Add Null Object",
"action.add_null_object.desc": "Adds a new null object",
"action.add_group": "Add Group",
"action.add_group.desc": "Adds a new group or bone",
"action.outliner_toggle": "Toggle More Options",
@ -926,6 +932,8 @@
"action.animated_textures": "Play Animated Textures",
"action.animated_textures.desc": "Play and pause the preview of animated textures",
"action.animated_texture_frame": "Animated Texture Frame",
"action.animated_texture_frame.desc": "Set the current frame of the animated texture",
"action.origin_to_geometry": "Center Pivot",
"action.origin_to_geometry.desc": "Set the pivot point to the center of the selection",
"action.rescale_toggle": "Toggle Rescale",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long