mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-20 06:27:04 +01:00
cleaning up
This commit is contained in:
56
src/App.vue
56
src/App.vue
@@ -1,58 +1,42 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineAsyncComponent, ref, Ref} from "vue";
|
import {defineAsyncComponent, ref, Ref} from "vue";
|
||||||
import Sidebar from "./Sidebar.vue";
|
import Sidebar from "./models/Sidebar.vue";
|
||||||
import Loading from "./Loading.vue";
|
import Loading from "./viewer/Loading.vue";
|
||||||
import ModelViewerOverlay from "./ModelViewerOverlay.vue";
|
import ModelViewerOverlay from "./viewer/ModelViewerOverlay.vue";
|
||||||
import Tools from "./Tools.vue";
|
import Tools from "./tools/Tools.vue";
|
||||||
import {
|
import Models from "./models/Models.vue";
|
||||||
VExpansionPanel,
|
import {VLayout, VMain, VToolbarTitle} from "vuetify/lib/components";
|
||||||
VExpansionPanels,
|
import type {ModelViewerInfo} from "./viewer/ModelViewerWrapper.vue";
|
||||||
VExpansionPanelText,
|
import {settings} from "./tools/settings";
|
||||||
VExpansionPanelTitle,
|
|
||||||
VLayout,
|
|
||||||
VMain,
|
|
||||||
VToolbarTitle
|
|
||||||
} from "vuetify/lib/components";
|
|
||||||
import type {ModelViewerInfo} from "./ModelViewerWrapper.vue";
|
|
||||||
|
|
||||||
// NOTE: The ModelViewer library is big, so we split it and import it asynchronously
|
// NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously
|
||||||
const ModelViewerWrapper = defineAsyncComponent({
|
const ModelViewerWrapper = defineAsyncComponent({
|
||||||
loader: () => import('./ModelViewerWrapper.vue'),
|
loader: () => import('./viewer/ModelViewerWrapper.vue'),
|
||||||
loadingComponent: Loading,
|
loadingComponent: Loading,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open models by default on wide screens
|
let openSidebarsByDefault: Ref<boolean> = ref(window.innerWidth > 1200);
|
||||||
let openSidebarsDefault: Ref<boolean> = ref(window.innerWidth > 1200);
|
|
||||||
let modelViewerInfo: Ref<typeof ModelViewerInfo | null> = ref(null);
|
let modelViewerInfo: Ref<typeof ModelViewerInfo | null> = ref(null);
|
||||||
|
let modelSrc: Ref<string | Uint8Array> = ref(settings.preloadModels[0]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-layout full-height>
|
<v-layout full-height>
|
||||||
<!-- The main content of the app is the model-viewer with the SVG "2D" overlay -->
|
<!-- The main content of the app is the model-viewer with the SVG "2D" overlay -->
|
||||||
<v-main id="main">
|
<v-main id="main">
|
||||||
<model-viewer-wrapper @load-viewer="(args) => {
|
<model-viewer-wrapper :src="modelSrc" @load-viewer="(args) => modelViewerInfo = args"/>
|
||||||
console.log('Model-Viewer finished loading:', args)
|
|
||||||
modelViewerInfo = args
|
|
||||||
}"/>
|
|
||||||
<model-viewer-overlay v-if="modelViewerInfo"/>
|
<model-viewer-overlay v-if="modelViewerInfo"/>
|
||||||
</v-main>
|
</v-main>
|
||||||
<!-- The left collapsible sidebar has the list of models -->
|
<!-- The left collapsible sidebar has the list of models -->
|
||||||
<sidebar :opened-init="openSidebarsDefault" side="left">
|
<sidebar :opened-init="openSidebarsByDefault" side="left">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Models</v-toolbar-title>
|
<v-toolbar-title>Models</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
<v-expansion-panels>
|
<models :modelViewerInfo="modelViewerInfo"/>
|
||||||
<v-expansion-panel key="model-id">
|
|
||||||
<v-expansion-panel-title>? F ? E ? V | Model Name</v-expansion-panel-title>
|
|
||||||
<v-expansion-panel-text>
|
|
||||||
Content
|
|
||||||
</v-expansion-panel-text>
|
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
</sidebar>
|
</sidebar>
|
||||||
<!-- The right collapsible sidebar has the list of tools -->
|
<!-- The right collapsible sidebar has the list of tools -->
|
||||||
<sidebar :opened-init="openSidebarsDefault" side="right" :width="120">
|
<sidebar :opened-init="openSidebarsByDefault" side="right" :width="120">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<v-toolbar-title>Tools</v-toolbar-title>
|
<v-toolbar-title>Tools</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
@@ -67,12 +51,4 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
<!--suppress CssUnusedSymbol -->
|
|
||||||
<style scoped>
|
|
||||||
/* Fix bug in hidden expansion panel text next to active expansion panel */
|
|
||||||
.v-expansion-panel-title--active + .v-expansion-panel-text {
|
|
||||||
display: flex !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -19,7 +19,6 @@ const vuetify = createVuetify({
|
|||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(vuetify)
|
app.use(vuetify)
|
||||||
// noinspection JSUnresolvedReference
|
|
||||||
app.mount('body')
|
app.mount('body')
|
||||||
|
|
||||||
// Start non-blocking loading of Vuetify and icon styles
|
// Start non-blocking loading of Vuetify and icon styles
|
||||||
|
|||||||
24
src/models/Models.vue
Normal file
24
src/models/Models.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {VExpansionPanel, VExpansionPanels, VExpansionPanelText, VExpansionPanelTitle} from "vuetify/lib/components";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelViewerInfo: Object
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel key="model-id">
|
||||||
|
<v-expansion-panel-title>? F ? E ? V | Model Name</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>Content</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!--suppress CssUnusedSymbol -->
|
||||||
|
<style scoped>
|
||||||
|
/* Fix bug in hidden expansion panel text next to active expansion panel */
|
||||||
|
.v-expansion-panel-title--active + .v-expansion-panel-text {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
|
||||||
|
|
||||||
window.THREE = THREE // HACK: Required for the gizmo to work
|
|
||||||
|
|
||||||
export class OrientationGizmo {
|
|
||||||
element: OrientationGizmoRaw
|
|
||||||
|
|
||||||
constructor(scene: ModelScene) {
|
|
||||||
// noinspection SpellCheckingInspection
|
|
||||||
this.element = new OrientationGizmoRaw(scene.camera, {
|
|
||||||
size: 120,
|
|
||||||
bubbleSizePrimary: 12,
|
|
||||||
bubbleSizeSeconday: 10,
|
|
||||||
fontSize: "14px"
|
|
||||||
});
|
|
||||||
// Place in the top right corner
|
|
||||||
this.element.style.position = "absolute";
|
|
||||||
this.element.style.top = "0px";
|
|
||||||
this.element.style.right = "0px";
|
|
||||||
this.element.style.zIndex = "1000";
|
|
||||||
// HACK: Swap axes to match A-Frame
|
|
||||||
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
|
||||||
let indexA = this.element.bubbles.findIndex((bubble) => bubble.axis == swap[0])
|
|
||||||
let indexB = this.element.bubbles.findIndex((bubble) => bubble.axis == swap[1])
|
|
||||||
let dirA = this.element.bubbles[indexA].direction.clone();
|
|
||||||
let dirB = this.element.bubbles[indexB].direction.clone();
|
|
||||||
this.element.bubbles[indexA].direction.copy(dirB);
|
|
||||||
this.element.bubbles[indexB].direction.copy(dirA);
|
|
||||||
}
|
|
||||||
// Append and listen for events
|
|
||||||
this.element.onAxisSelected = (axis: { direction: { x: any; y: any; z: any; }; }) => {
|
|
||||||
let lookFrom = scene.getCamera().position.clone();
|
|
||||||
let lookAt = scene.getTarget().clone().add(scene.target.position);
|
|
||||||
let magnitude = lookFrom.clone().sub(lookAt).length()
|
|
||||||
let direction = new THREE.Vector3(axis.direction.x, axis.direction.y, axis.direction.z);
|
|
||||||
let newLookFrom = lookAt.clone().add(direction.clone().multiplyScalar(magnitude));
|
|
||||||
//console.log("New camera position", newLookFrom)
|
|
||||||
scene.getCamera().position.copy(newLookFrom);
|
|
||||||
scene.getCamera().lookAt(lookAt);
|
|
||||||
scene.queueRender();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
install() {
|
|
||||||
document.body.appendChild(this.element);
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.element.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// These are the default values for the settings, which are overridden below
|
// These are the default values for the settings, which are overridden below
|
||||||
export const settings = {
|
export const settings = {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
preloadModels: [new URL('../assets/fox.glb', import.meta.url).href, "ws://localhost:8080/api/updates"],
|
preloadModels: [new URL('../../assets/fox.glb', import.meta.url).href, new URL('../../assets/logo.glbs', import.meta.url).href, "ws://localhost:8080"],
|
||||||
// ModelViewer settings
|
// ModelViewer settings
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
arModes: 'webxr scene-viewer quick-look',
|
arModes: 'webxr scene-viewer quick-look',
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {settings} from "./settings";
|
import {settings} from "../tools/settings";
|
||||||
import {ModelViewerElement} from '@google/model-viewer';
|
import {ModelViewerElement} from '@google/model-viewer';
|
||||||
import {onMounted, ref} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {$scene} from "@google/model-viewer/lib/model-viewer-base";
|
import {$scene} from "@google/model-viewer/lib/model-viewer-base";
|
||||||
@@ -11,14 +11,17 @@ let _ = ModelViewerElement; // HACK: Needed to avoid tree shaking
|
|||||||
|
|
||||||
const emit = defineEmits(['load-viewer']);
|
const emit = defineEmits(['load-viewer']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
src: String
|
||||||
|
});
|
||||||
|
|
||||||
let viewer = ref<ModelViewerElement | null>(null);
|
let viewer = ref<ModelViewerElement | null>(null);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('ModelViewerWrapper mounted');
|
|
||||||
viewer.value.addEventListener('load', () =>
|
viewer.value.addEventListener('load', () =>
|
||||||
emit('load-viewer', {
|
emit('load-viewer', {
|
||||||
viewer: viewer.value,
|
viewer: viewer.value,
|
||||||
scene: viewer.value[$scene] as ModelScene,
|
scene: viewer.value[$scene] as ModelScene,
|
||||||
})
|
} as ModelViewerInfo)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<!--suppress VueMissingComponentImportInspection -->
|
<!--suppress VueMissingComponentImportInspection -->
|
||||||
<model-viewer ref="viewer"
|
<model-viewer ref="viewer"
|
||||||
style="width: 100%; height: 100%" :src="settings.preloadModels[0]" alt="The 3D model(s)" camera-controls
|
style="width: 100%; height: 100%" :src="props.src" alt="The 3D model(s)" camera-controls
|
||||||
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto"
|
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto"
|
||||||
min-camera-orbit="-Infinity 0deg auto"
|
min-camera-orbit="-Infinity 0deg auto"
|
||||||
:exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none"
|
:exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none"
|
||||||
@@ -23,7 +23,7 @@ show_object = show
|
|||||||
def _get_app() -> web.Application:
|
def _get_app() -> web.Application:
|
||||||
"""Required by aiohttp-devtools"""
|
"""Required by aiohttp-devtools"""
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
from logo.logo import build_logo
|
from logo import build_logo
|
||||||
server.show_cad(build_logo(), 'logo')
|
server.show_cad(build_logo(), 'logo')
|
||||||
return server.app
|
return server.app
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
|
|
||||||
async def glb_sequence_to_glbs(glb_sequence: AsyncGenerator[bytes, None]) -> AsyncGenerator[bytes, None]:
|
async def glb_sequence_to_glbs(glb_sequence: AsyncGenerator[bytes, None], count: int = -1) -> AsyncGenerator[bytes, None]:
|
||||||
"""Converts a sequence of GLB files into a single GLBS file.
|
"""Converts a sequence of GLB files into a single GLBS file.
|
||||||
|
|
||||||
This is a streaming response in the custom GLBS format which consists of the "GLBS" magic text followed by
|
This is a streaming response in the custom GLBS format which consists of the "GLBS" magic text followed by
|
||||||
a sequence of GLB files, each with a 4-byte little-endian length prefix."""
|
a count of GLB files (0xffffffff if unknown) and a sequence of GLB files, each with a length prefix. All numbers are
|
||||||
|
4-byte little-endian unsigned integers."""
|
||||||
|
|
||||||
# Write the magic text
|
# Write the magic text
|
||||||
yield b'GLBS'
|
yield b'GLBS'
|
||||||
|
|
||||||
|
# Write the count
|
||||||
|
yield count.to_bytes(4, 'little')
|
||||||
|
|
||||||
# Write the GLB files
|
# Write the GLB files
|
||||||
async for glb in glb_sequence:
|
async for glb in glb_sequence:
|
||||||
# Write the length prefix
|
# Write the length prefix
|
||||||
@@ -26,7 +30,7 @@ if __name__ == '__main__':
|
|||||||
yield b'glb00001'
|
yield b'glb00001'
|
||||||
yield b'glb2'
|
yield b'glb2'
|
||||||
|
|
||||||
async for chunk in glb_sequence_to_glbs(glb_sequence()):
|
async for chunk in glb_sequence_to_glbs(glb_sequence(), 2):
|
||||||
print(chunk)
|
print(chunk)
|
||||||
|
|
||||||
asyncio.run(test_glb_sequence_to_glbs())
|
asyncio.run(test_glb_sequence_to_glbs())
|
||||||
@@ -202,7 +202,7 @@ class Server:
|
|||||||
if logger.isEnabledFor(logging.INFO):
|
if logger.isEnabledFor(logging.INFO):
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
export_data = tqdm.asyncio.tqdm(export_data, total=total_parts)
|
export_data = tqdm.asyncio.tqdm(export_data, total=total_parts)
|
||||||
async for chunk in glb_sequence_to_glbs(export_data):
|
async for chunk in glb_sequence_to_glbs(export_data, total_parts):
|
||||||
await response.write(chunk)
|
await response.write(chunk)
|
||||||
finally:
|
finally:
|
||||||
# Close the export data subscription
|
# Close the export data subscription
|
||||||
@@ -308,7 +308,7 @@ class Server:
|
|||||||
if logger.isEnabledFor(logging.INFO):
|
if logger.isEnabledFor(logging.INFO):
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
glbs_parts = tqdm.asyncio.tqdm(glbs_parts, total=total_export_size, position=0)
|
glbs_parts = tqdm.asyncio.tqdm(glbs_parts, total=total_export_size, position=0)
|
||||||
glbs_parts = glb_sequence_to_glbs(glbs_parts)
|
glbs_parts = glb_sequence_to_glbs(glbs_parts, total_export_size)
|
||||||
async for glbs_part in glbs_parts:
|
async for glbs_part in glbs_parts:
|
||||||
yield glbs_part
|
yield glbs_part
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user