Skip to content

Commit

Permalink
Merge pull request #562 from KhronosGroup/feature/gltf-validator
Browse files Browse the repository at this point in the history
Feature/gltf validator
  • Loading branch information
UX3D-kanzler committed Aug 9, 2024
2 parents 3563182 + ea95ec5 commit 50b07d5
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 23 deletions.
71 changes: 50 additions & 21 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -263,23 +263,52 @@ <h2 class="title is-spaced">Display</h2>
</div>
</b-tab-item>

<b-tab-item label="Validator" icon="video" class="tabItemScrollable tab-item">
<template #header>
<div @click="collapseActiveTab($event, 2)"
v-bind:style="[tabsHidden === false && activeTab === 2 ? {'height': '100%'} : {}]">
<!-- to get colored icons use: https://stackoverflow.com/a/43916743 -->
<div style="max-width:fit-content; margin-left: auto; margin-right: auto;" v-html="getValidationCounter()"></div>
<span
v-bind:style="[tabsHidden === false && activeTab === 2 ? {'display': 'none'} : {}]">Validator</span>
</div>
</template>

<div class="tabContent" style="display: flex; flex-direction: column; height: inherit;">
<img src="assets/ui/Navigation_right_20px.svg" width="30px"
@click="collapseActiveTab($event, 3)">
<h2 class="title is-spaced" v-bind:data="validationReport">glTF Validator</h2>
<div class="modelCredit">
<p>Number of errors: {{validationReport?.issues?.numErrors ?? 0}}</p>
<p>Number of warnings: {{validationReport?.issues?.numWarnings ?? 0}}</p>
<p>Number of infos: {{validationReport?.issues?.numInfos ?? 0}}</p>
</div>
<button class="button is-rounded" style="width: fit-content; flex-shrink: 0;" v-on:click="copyToClipboard(JSON.stringify(validationReport, undefined, 4))">Copy</button>
<textarea cols="25" readonly style="min-height: 200px; max-width: max-content;resize: none; margin-top: 20px; flex-grow: 1;">
{{ validationReport }}
</textarea>
<span style="margin-top: 5px;">Powered by <a href="https://github.com/KhronosGroup/glTF-Validator" target="_blank" rel="noopener noreferrer">glTF-Validator</a></span>
</b-field>
</div>
</b-tab-item>


<b-tab-item label="Animation" icon="video" class="tabItemScrollable tab-item">
<template #header>
<div @click="collapseActiveTab($event, 2)"
v-bind:style="[tabsHidden === false && activeTab === 2 ? {'height': '100%'} : {}]">
<div @click="collapseActiveTab($event, 3)"
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'height': '100%'} : {}]">
<!-- to get colored icons use: https://stackoverflow.com/a/43916743 -->
<img v-bind:src="[tabsHidden === false && activeTab === 2 ? 'assets/ui/Animation 50X50.svg' : 'assets/ui/Animation 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 2 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 2 ? {'height': '100%'} : {}]">
<img v-bind:src="[tabsHidden === false && activeTab === 3 ? 'assets/ui/Animation 50X50.svg' : 'assets/ui/Animation 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 3 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'height': '100%'} : {}]">
<span
v-bind:style="[tabsHidden === false && activeTab === 2 ? {'display': 'none'} : {}]">Animation</span>
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'display': 'none'} : {}]">Animation</span>
</div>
</template>

<div class="tabContent">
<img src="assets/ui/Navigation_right_20px.svg" width="30px"
@click="collapseActiveTab($event, 2)">
@click="collapseActiveTab($event, 3)">
<h2 class="title is-spaced">Animation</h2>
<label class="subtitle" v-bind:style="[animations.length == 0 ? {'display': 'none'} : {}]">Animation Controls</label>
<toggle-button v-on:buttonclicked="animationPlayChanged.next($event)" ontext="Pause" offtext="Play"
Expand All @@ -303,20 +332,20 @@ <h2 class="title is-spaced">Animation</h2>

<b-tab-item label="Credits" icon="video" class="tabItemScrollable tab-item">
<template #header>
<div @click="collapseActiveTab($event, 3)"
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'height': '100%'} : {}]">
<div @click="collapseActiveTab($event, 4)"
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'height': '100%'} : {}]">
<!-- to get colored icons use: https://stackoverflow.com/a/43916743 -->
<img v-bind:src="[tabsHidden === false && activeTab === 3 ? 'assets/ui/XMP 50X50.svg' : 'assets/ui/XMP 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 3 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'height': '100%'} : {}]">
<img v-bind:src="[tabsHidden === false && activeTab === 4 ? 'assets/ui/XMP 50X50.svg' : 'assets/ui/XMP 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 4 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'height': '100%'} : {}]">
<span
v-bind:style="[tabsHidden === false && activeTab === 3 ? {'display': 'none'} : {}]">Credits</span>
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'display': 'none'} : {}]">Credits</span>
</div>
</template>

<div class="tabContent">
<img src="assets/ui/Navigation_right_20px.svg" width="30px"
@click="collapseActiveTab($event, 3)">
@click="collapseActiveTab($event, 4)">
<h2 class="title">Model Credits</h2>
<div class="modelCredit"><i>Copyright:</i><br/>{{ assetCopyright }}</div>
<div class="modelCredit"><i>Generated by:</i><br/>{{ assetGenerator }}</div>
Expand All @@ -335,21 +364,21 @@ <h3 class="title">{{ xmp ? "XMP" : "" }}</h3>

<b-tab-item label="Advanced Controls" icon="video" class="tabItemScrollable tab-item">
<template #header>
<div @click="collapseActiveTab($event, 4)"
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'height': '100%'} : {}]">
<div @click="collapseActiveTab($event, 5)"
v-bind:style="[tabsHidden === false && activeTab === 5 ? {'height': '100%'} : {}]">
<!-- to get colored icons use: https://stackoverflow.com/a/43916743 -->
<img v-bind:src="[tabsHidden === false && activeTab === 4 ? 'assets/ui/Developer 50X50.svg' : 'assets/ui/Developer 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 4 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'height': '100%'} : {}]">
<img v-bind:src="[tabsHidden === false && activeTab === 5 ? 'assets/ui/Developer 50X50.svg' : 'assets/ui/Developer 30X30.svg']"
v-bind:width="[tabsHidden === false && activeTab === 5 ? '50px' : '30px']"
v-bind:style="[tabsHidden === false && activeTab === 5 ? {'height': '100%'} : {}]">
<span
v-bind:style="[tabsHidden === false && activeTab === 4 ? {'display': 'none'} : {}]">Advanced
v-bind:style="[tabsHidden === false && activeTab === 5 ? {'display': 'none'} : {}]">Advanced
Controls</span>
</div>
</template>

<div class="tabContent">
<img src="assets/ui/Navigation_right_20px.svg" width="30px"
@click="collapseActiveTab($event, 4)">
@click="collapseActiveTab($event, 5)">
<h2 class="title is-spaced">Advanced Controls</h2>
<b-field label="Capture Canvas" class="smallerLabel">
<button v-on:click="captureCanvas.next($event.target.value)" type="button" class="button is-rounded"><i
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"bulma": "^0.9.4",
"fast-png": "^6.2.0",
"gl-matrix": "^3.2.1",
"gltf-validator": "^2.0.0-dev.3.9",
"jpeg-js": "^0.4.3",
"normalize-wheel": "^1.0.1",
"path": "^0.12.7",
Expand Down
5 changes: 5 additions & 0 deletions src/logic/uimodel.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ class UIModel
);
}

updateValidationReport(validationReportObservable)
{
validationReportObservable.subscribe(data => this.app.validationReport = data);
}

disabledAnimations(disabledAnimationsObservable)
{
disabledAnimationsObservable.subscribe(data => this.app.disabledAnimations = data);
Expand Down
65 changes: 64 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
fillEnvironmentWithPaths,
} from "./model_path_provider.js";

import {validateBytes} from "gltf-validator";

export default async () => {
const canvas = document.getElementById("canvas");
const context = canvas.getContext("webgl2", {
Expand Down Expand Up @@ -38,11 +40,71 @@ export default async () => {
Colorful_Studio: "Colorful Studio",
Wide_Street: "Wide Street",
},
"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Environments/low_resolution_hdrs/",
"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Environments/low_resolution_hdrs/"
);

const uiModel = new UIModel(app, pathProvider, environmentPaths);


const validation = uiModel.model.pipe(
mergeMap((model) => {
const func = async(model) => {
try {
const fileType = typeof model.mainFile;
if (fileType == "string"){
const externalRefFunction = (uri) => {
const parent = model.mainFile.substring(0, model.mainFile.lastIndexOf("/") + 1);
return new Promise((resolve, reject) => {
fetch(parent + uri).then(response => {
response.bytes().then(buffer => {
resolve(buffer);
}).catch(error => {
reject(error);
});
}).catch(error => {
reject(error);
});
});
};
const response = await fetch(model.mainFile);
const buffer = await response.bytes();
const result = await validateBytes(buffer, {externalResourceFunction: externalRefFunction, uri: model.mainFile});
return result;
} else if (Array.isArray(model.mainFile)) {
const externalRefFunction = (uri) => {
uri = "/" + uri;
return new Promise((resolve, reject) => {
let foundFile = undefined;
for (let i = 0; i < model.additionalFiles.length; i++) {
const file = model.additionalFiles[i];
if (file[0] == uri) {
foundFile = file[1];
break;
}
}
if (foundFile) {
foundFile.bytes().then((buffer) => {
resolve(buffer);
}).catch((error) => {
reject(error);
});
} else {
reject("File not found");
}
});
};

const buffer = await model.mainFile[1].bytes();
return await validateBytes(buffer, {externalResourceFunction: externalRefFunction, uri: model.mainFile[0]});
}
} catch (error) {
console.error(error);
}
};
return from(func(model));
})
);

// whenever a new model is selected, load it and when complete pass the loaded gltf
// into a stream back into the UI
const gltfLoaded = uiModel.model.pipe(
Expand Down Expand Up @@ -351,6 +413,7 @@ export default async () => {
});

uiModel.attachGltfLoaded(gltfLoaded);
uiModel.updateValidationReport(validation);
uiModel.updateStatistics(statisticsUpdateObservable);
const sceneChangedStateObservable = uiModel.scene.pipe(map(() => state));
uiModel.attachCameraChangeObservable(sceneChangedStateObservable);
Expand Down
13 changes: 12 additions & 1 deletion src/ui/sass.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ html
height: 100%;
}

a:hover
{
color: $primary-light;
}

body
{
margin: 0;
Expand Down Expand Up @@ -130,7 +135,7 @@ nav.tabs
{
margin-top: calc(100vh * 0.11);
}
.tabs li:nth-child(5)
.tabs li:nth-child(6)
{
margin-top: auto;
margin-bottom: calc(100vh * 0.02);
Expand Down Expand Up @@ -169,6 +174,12 @@ nav.tabs
padding: 1em;
}

textarea
{
background-color: #1b1b1b;
color: $text
}

.tab-item h2
{
margin-bottom: 70px;
Expand Down
35 changes: 35 additions & 0 deletions src/ui/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const appCreated = createApp({
selectedAnimationsChanged: new Subject(),
selectedEnvironmentChanged: new Subject(),

validatorChanged: new Subject(),

fullheight: true,
right: true,
models: ["DamagedHelmet"],
Expand All @@ -71,6 +73,8 @@ const appCreated = createApp({
selectedAnimations: [],
disabledAnimations: [],

validationReport: {},

ibl: true,
iblIntensity: 0.0,
punctualLights: true,
Expand Down Expand Up @@ -157,6 +161,37 @@ const appCreated = createApp({
},
methods:
{
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.$buefy.toast.open({
message: "Copied to clipboard",
type: 'is-success'
});
} catch (err) {
this.error("Error copying to clipboard.");
}
},
getValidationCounter: function(){
let number = 0;
let color = "white";
if (this.validationReport?.issues?.numErrors > 0) {
number = this.validationReport?.issues?.numErrors;
color = "red";
} else if (this.validationReport?.issues?.numWarnings > 0) {
number = this.validationReport?.issues?.numWarnings;
color = "yellow";
} else if (this.validationReport?.issues?.numInfos > 0) {
number = this.validationReport?.issues?.numInfos;
}
if (number !== 0) {
return `<div style="display:flex;color:black; font-weight:bold; background-color:${color}; border-radius:50%; width:fit-content; min-width:2rem; align-items:center;aspect-ratio:1/1;justify-content:center;">${number}</div>`;
}
if (this.tabsHidden === false && this.activeTab === 2) {
return `<img src="assets/ui/Capture 50X50.svg" width="50px" height="100%">`;
}
return '<img src="assets/ui/Capture 30X30.svg" width="30px">';
},
setAnimationState: function(value)
{
this.$refs.animationState.setState(value);
Expand Down

0 comments on commit 50b07d5

Please sign in to comment.