2022-08-04 03:14:59 +08:00
const Validator = {
checks : [ ] ,
warnings : [ ] ,
errors : [ ] ,
2022-08-23 23:29:45 +08:00
_timeout : null ,
2022-08-04 03:14:59 +08:00
validate ( trigger ) {
2022-08-23 23:29:45 +08:00
if ( this . _timeout ) {
clearTimeout ( this . _timeout ) ;
this . _timeout = null ;
}
2022-08-04 03:14:59 +08:00
if ( ! Project ) return ;
2022-08-23 23:29:45 +08:00
this . _timeout = setTimeout ( ( ) => {
this . _timeout = null ;
Validator . warnings . empty ( ) ;
Validator . errors . empty ( ) ;
2022-08-04 03:14:59 +08:00
2022-08-23 23:29:45 +08:00
Validator . checks . forEach ( check => {
try {
if ( ! Condition ( check . condition ) ) return ;
if ( ! trigger || check . update _triggers . includes ( trigger ) ) {
check . update ( ) ;
}
Validator . warnings . push ( ... check . warnings ) ;
Validator . errors . push ( ... check . errors ) ;
} catch ( error ) {
console . error ( error ) ;
}
} )
2023-04-13 03:10:57 +08:00
} , 40 )
2022-08-04 03:14:59 +08:00
} ,
openDialog ( ) {
if ( ! Validator . dialog ) {
Validator . dialog = new Dialog ( {
id : 'validator' ,
title : 'action.validator_window' ,
singleButton : true ,
2022-08-23 06:19:57 +08:00
width : 800 ,
2022-08-04 03:14:59 +08:00
component : {
data ( ) { return {
warnings : Validator . warnings ,
errors : Validator . errors ,
} } ,
computed : {
problems ( ) {
this . errors . forEach ( error => error . error = true ) ;
return [ ... this . errors , ... this . warnings ]
}
} ,
methods : {
2022-08-23 06:19:57 +08:00
getIconNode : Blockbench . getIconNode ,
2023-01-25 02:05:25 +08:00
pureMarked
2022-08-04 03:14:59 +08:00
} ,
template : `
< template >
< ul >
< li v - for = "problem in problems" class = "validator_dialog_problem" : class = "problem.error ? 'validator_error' : 'validator_warning'" : key = "problem.message" >
< i class = "material-icons" > { { problem . error ? 'error' : 'warning' } } < / i >
2023-01-25 02:05:25 +08:00
< span class = "markdown" v - html = "pureMarked(problem.message.replace(/\\n/g, '\\n\\n'))" > < / s p a n >
2022-08-04 03:14:59 +08:00
< template v - if = "problem.buttons" >
< div v - for = "button in problem.buttons" class = "tool" : title = "button.name" @ click = "button.click($event)" >
< div class = "icon_wrapper plugin_icon normal" v - html = "getIconNode(button.icon, button.color).outerHTML" > < / d i v >
< / d i v >
< / d i v >
< / l i >
< / u l >
< / t e m p l a t e >
`
}
} ) ;
}
Validator . dialog . show ( ) ;
} ,
triggers : [ ] ,
updateCashedTriggers ( ) {
Validator . triggers . empty ( ) ;
Validator . checks . forEach ( check => {
Validator . triggers . safePush ( ... check . update _triggers ) ;
} )
}
} ;
class ValidatorCheck {
constructor ( id , options ) {
this . id = id ;
this . type = options . type ;
this . update _triggers = options . update _triggers
? ( options . update _triggers instanceof Array ? options . update _triggers : [ options . update _triggers ] )
: [ ] ;
this . condition = options . condition ;
this . run = options . run ;
this . errors = [ ] ;
this . warnings = [ ] ;
2023-06-09 02:19:47 +08:00
this . _timeout = null ;
2023-10-30 07:14:41 +08:00
this . plugin = options . plugin || ( typeof Plugins != 'undefined' ? Plugins . currently _loading : '' ) ;
2022-08-04 03:14:59 +08:00
Validator . checks . push ( this ) ;
Validator . updateCashedTriggers ( ) ;
}
delete ( ) {
Validator . checks . remove ( this ) ;
Validator . updateCashedTriggers ( ) ;
}
update ( ) {
this . errors . empty ( ) ;
this . warnings . empty ( ) ;
try {
this . run ( ) ;
if ( this . errors . length > 100 ) this . errors . splice ( 100 ) ;
if ( this . warnings . length > 100 ) this . warnings . splice ( 100 ) ;
} catch ( error ) {
console . error ( error ) ;
}
}
2023-06-09 02:19:47 +08:00
validate ( ) {
if ( this . _timeout ) {
clearTimeout ( this . _timeout ) ;
this . _timeout = null ;
}
this . _timeout = setTimeout ( ( ) => {
this . _timeout = null ;
this . update ( ) ;
Validator . warnings . empty ( ) ;
Validator . errors . empty ( ) ;
Validator . checks . forEach ( check => {
Validator . warnings . push ( ... check . warnings ) ;
Validator . errors . push ( ... check . errors ) ;
} )
} , 40 )
}
2022-08-04 03:14:59 +08:00
warn ( ... warnings ) {
this . warnings . push ( ... warnings ) ;
}
fail ( ... errors ) {
this . errors . push ( ... errors ) ;
}
}
BARS . defineActions ( function ( ) {
new Action ( 'validator_window' , {
icon : 'checklist' ,
category : 'file' ,
condition : ( ) => Project ,
click ( ) {
Validator . openDialog ( ) ;
}
} )
} )
2022-08-26 00:21:13 +08:00
new ValidatorCheck ( 'cube_size_limit' , {
condition : ( ) => Format . cube _size _limiter && settings . deactivate _size _limit . value ,
update _triggers : [ 'update_selection' ] ,
run ( ) {
Cube . all . forEach ( cube => {
if ( Format . cube _size _limiter . test ( cube ) ) {
this . warn ( {
message : ` The cube " ${ cube . name } " is outside the allowed size restrictions. It may not work correctly on the target platform. ` ,
buttons : [
{
name : 'Select Cube' ,
icon : 'fa-cube' ,
click ( ) {
Validator . dialog . hide ( ) ;
cube . select ( ) ;
}
}
]
} )
}
} )
}
} )
2022-09-03 05:38:51 +08:00
new ValidatorCheck ( 'box_uv' , {
2022-09-25 07:12:03 +08:00
condition : ( ) => Cube . all . find ( cube => cube . box _uv ) ,
2022-09-03 05:38:51 +08:00
update _triggers : [ 'update_selection' ] ,
run ( ) {
Cube . all . forEach ( cube => {
2022-09-25 07:12:03 +08:00
if ( ! cube . box _uv ) return ;
2022-09-03 05:38:51 +08:00
let size = cube . size ( ) ;
2023-04-05 07:57:45 +08:00
let invalid _size _axes = size . filter ( ( value , axis ) => value < 0.999 && ( value + cube . inflate * 2 ) * cube . stretch [ axis ] > 0.005 ) ;
2022-09-03 05:38:51 +08:00
if ( invalid _size _axes . length ) {
let buttons = [
{
name : 'Select Cube' ,
icon : 'fa-cube' ,
click ( ) {
Validator . dialog . hide ( ) ;
cube . select ( ) ;
}
}
] ;
if ( Format . optional _box _uv ) {
buttons . push ( {
name : 'Switch to Per-face UV' ,
icon : 'toggle_on' ,
click ( ) {
Validator . dialog . hide ( ) ;
2023-06-09 02:19:47 +08:00
Undo . initEdit ( { elements : [ cube ] , uv _only : true } ) ;
cube . setUVMode ( false ) ;
Undo . finishEdit ( 'Change UV mode' )
updateSelection ( ) ;
2022-09-03 05:38:51 +08:00
}
} )
}
this . warn ( {
2022-09-11 06:05:54 +08:00
message : ` The cube " ${ cube . name } " has ${ invalid _size _axes . length * 2 } faces that are smaller than one unit along an axis, which may render incorrectly in Box UV. Increase the size of the cube to at least 1 or switch to Per-face UV and resize the UV to fix this. ` ,
2022-09-03 05:38:51 +08:00
buttons
} )
}
} )
}
} )
2022-08-04 03:14:59 +08:00
new ValidatorCheck ( 'texture_names' , {
condition : { formats : [ 'java_block' ] } ,
update _triggers : [ 'add_texture' , 'change_texture_path' ] ,
run ( ) {
Texture . all . forEach ( texture => {
2024-03-03 02:54:20 +08:00
let used _path = texture . folder ? ( texture . folder + '/' + texture . name ) : texture . name ;
let characters = used _path . replace ( /^#/ , '' ) . match ( /[^a-z0-9._/\\-]/ )
2022-08-04 03:14:59 +08:00
if ( characters ) {
this . warn ( {
2024-03-03 02:54:20 +08:00
message : ` Texture " ${ used _path } " contains the following invalid characters: " ${ characters . join ( '' ) } ". Valid characters are: a-z0-9._/ \\ -. Uppercase letters are invalid. ` ,
2022-08-04 03:14:59 +08:00
buttons : [
{
name : 'Select Texture' ,
icon : 'mouse' ,
click ( ) {
Validator . dialog . hide ( ) ;
texture . select ( ) ;
}
}
]
} )
}
} )
}
} )
2022-08-10 05:14:03 +08:00
new ValidatorCheck ( 'catmullrom_keyframes' , {
condition : { features : [ 'animation_files' ] } ,
update _triggers : [ 'update_keyframe_selection' ] ,
run ( ) {
2022-08-27 20:53:11 +08:00
function getButtons ( kf ) {
return [ {
name : 'Reveal Keyframe' ,
icon : 'icon-keyframe' ,
click ( ) {
Dialog . open . close ( ) ;
kf . showInTimeline ( ) ;
}
} ]
}
2022-08-10 05:14:03 +08:00
Animation . all . forEach ( animation => {
for ( let key in animation . animators ) {
let animator = animation . animators [ key ] ;
if ( animator instanceof BoneAnimator ) {
for ( let channel in animator . channels ) {
if ( ! animator [ channel ] || ! animator [ channel ] . find ( kf => kf . interpolation == 'catmullrom' ) ) continue ;
let keyframes = animator [ channel ] . slice ( ) . sort ( ( a , b ) => a . time - b . time ) ;
keyframes . forEach ( ( kf , i ) => {
if ( kf . interpolation == 'catmullrom' ) {
if ( kf . data _points . find ( dp => isNaN ( dp . x ) || isNaN ( dp . y ) || isNaN ( dp . z ) ) ) {
this . fail ( {
2022-08-27 20:53:11 +08:00
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains non-numeric values. Smooth keyframes cannot contain math expressions. ` ,
buttons : getButtons ( kf )
2022-08-10 05:14:03 +08:00
} )
2022-08-27 20:53:11 +08:00
2022-08-10 05:14:03 +08:00
}
if ( ( ! keyframes [ i - 1 ] || keyframes [ i - 1 ] . interpolation != 'catmullrom' ) && ( ! keyframes [ i + 1 ] || keyframes [ i + 1 ] . interpolation != 'catmullrom' ) ) {
this . warn ( {
2022-08-23 06:19:57 +08:00
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " is not surrounded by smooth keyframes. Multiple smooth keyframes are required to create a smooth spline. ` ,
2022-08-27 20:53:11 +08:00
buttons : getButtons ( kf )
} )
} else if ( ! keyframes [ i + 1 ] && animation . length && ( kf . time + 0.01 ) < animation . length && kf . data _points . find ( dp => parseFloat ( dp . x ) || parseFloat ( dp . y ) || parseFloat ( dp . z ) ) ) {
this . warn ( {
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " is the last smooth keyframe, but does not line up with the end of the animation. The last keyframe should either be linear, or line up with the animation end. ` ,
buttons : getButtons ( kf )
2022-08-10 05:14:03 +08:00
} )
2022-08-27 20:53:11 +08:00
2022-08-31 22:49:10 +08:00
} else if ( ! keyframes [ i + 1 ] && animation . loop === 'hold' && kf . data _points . find ( dp => parseFloat ( dp . x ) || parseFloat ( dp . y ) || parseFloat ( dp . z ) ) ) {
this . warn ( {
message : ` ${ animator . channels [ channel ] . name } keyframe on " ${ animator . name } " in " ${ animation . name } " is the last keyframe, and is smooth. The last keyframe per channel on "hold on last frame" animations should generally be set to linear, since splines cannot hold their value past the last keyframe. ` ,
buttons : getButtons ( kf )
} )
2022-08-10 05:14:03 +08:00
}
2022-08-27 20:53:11 +08:00
} else if ( keyframes [ i + 1 ] && keyframes [ i + 1 ] . interpolation == 'catmullrom' && kf . data _points . find ( dp => isNaN ( dp . x ) || isNaN ( dp . y ) || isNaN ( dp . z ) ) ) {
this . fail ( {
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains non-numeric values. Keyframes that are adjacent to smooth keyframes cannot contain math expressions. ` ,
buttons : getButtons ( kf )
} )
} else if ( keyframes [ i - 1 ] && keyframes [ i - 1 ] . interpolation == 'catmullrom' && kf . data _points . find ( dp => isNaN ( dp . x ) || isNaN ( dp . y ) || isNaN ( dp . z ) ) ) {
this . fail ( {
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains non-numeric values. Keyframes that are adjacent to smooth keyframes cannot contain math expressions. ` ,
buttons : getButtons ( kf )
} )
} else if ( keyframes [ i - 2 ] && keyframes [ i - 2 ] . interpolation == 'catmullrom' && kf . data _points . find ( dp => isNaN ( dp . x ) || isNaN ( dp . y ) || isNaN ( dp . z ) ) ) {
this . warn ( {
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains non-numeric values. Keyframes that are adjacent to smooth keyframes cannot contain math expressions. ` ,
buttons : getButtons ( kf )
} )
2022-08-10 05:14:03 +08:00
}
} )
}
}
}
} )
}
} )