mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
solving lots of production issues
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
# Useful .gitignore templates: https://github.com/github/gitignore
|
# Useful .gitignore templates: https://github.com/github/gitignore
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/dist/
|
/dist/
|
||||||
|
/yacv_server/frontend/
|
||||||
/.cache/
|
/.cache/
|
||||||
/.parcel-cache/
|
/.parcel-cache/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
|||||||
7
build.py
Normal file
7
build.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# When building the backend, make sure the frontend is built first
|
||||||
|
subprocess.run(['yarn', 'install'], check=True)
|
||||||
|
subprocess.run(['yarn', 'build', '--dist-dir', 'yacv_server/frontend'], check=True)
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"author": "Yeicor",
|
"author": "Yeicor",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "parcel src/index.html",
|
"start": "parcel src/index.html",
|
||||||
"build": "yarn update-licenses && parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer --detailed-report --public-url ./",
|
"build": "yarn update-licenses && parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer --detailed-report --public-url ./ --no-scope-hoist",
|
||||||
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
"update-licenses": "generate-license-file --input package.json --output assets/licenses.txt --overwrite"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "yacv-server"
|
name = "yacv-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0" # TODO: Update automatically by CI on release
|
||||||
description = "Yet Another CAD Viewer (server)"
|
description = "Yet Another CAD Viewer (server)"
|
||||||
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
include = [
|
||||||
|
{ path = 'yacv_server/frontend/*', format = 'wheel' },
|
||||||
|
{ path = 'yacv_server/frontend/*', format = 'sdist' },
|
||||||
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
@@ -22,6 +26,10 @@ aiohttp-devtools = "^1.1.2"
|
|||||||
pygltflib = "^1.16.1"
|
pygltflib = "^1.16.1"
|
||||||
pillow = "^10.2.0"
|
pillow = "^10.2.0"
|
||||||
|
|
||||||
|
[tool.poetry.build]
|
||||||
|
generate-setup-file = false
|
||||||
|
script = "build.py"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ globalThis.__VUE_OPTIONS_API__ = process.env.NODE_ENV === "development"
|
|||||||
globalThis.__VUE_PROD_DEVTOOLS__ = process.env.NODE_ENV === "development"
|
globalThis.__VUE_PROD_DEVTOOLS__ = process.env.NODE_ENV === "development"
|
||||||
globalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = process.env.NODE_ENV === "development"
|
globalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = process.env.NODE_ENV === "development"
|
||||||
|
|
||||||
// import {createApp} from 'vue/dist/vue.esm-browser.prod.js'
|
|
||||||
// import {createApp} from 'vue/dist/vue.esm-browser.js'
|
|
||||||
import {createApp} from 'vue'
|
import {createApp} from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
import {createVuetify} from 'vuetify';
|
import {createVuetify} from 'vuetify';
|
||||||
import * as directives from 'vuetify/lib/directives';
|
import * as directives from 'vuetify/lib/directives';
|
||||||
|
import 'vuetify/dist/vuetify.css';
|
||||||
|
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
directives,
|
directives,
|
||||||
@@ -20,7 +19,3 @@ const vuetify = createVuetify({
|
|||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(vuetify)
|
app.use(vuetify)
|
||||||
app.mount('body')
|
app.mount('body')
|
||||||
|
|
||||||
// Start non-blocking loading of Vuetify styles
|
|
||||||
// @ts-ignore
|
|
||||||
import('vuetify/dist/vuetify.css');
|
|
||||||
@@ -39,6 +39,7 @@ export class NetworkManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private monitorWebSocket(url: string) {
|
private monitorWebSocket(url: string) {
|
||||||
|
// WARNING: This will spam the console logs with failed requests when the server is down
|
||||||
let ws = new WebSocket(url);
|
let ws = new WebSocket(url);
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
let data = JSON.parse(event.data);
|
let data = JSON.parse(event.data);
|
||||||
@@ -48,13 +49,10 @@ export class NetworkManager extends EventTarget {
|
|||||||
urlObj.searchParams.set("api_object", data.name);
|
urlObj.searchParams.set("api_object", data.name);
|
||||||
this.foundModel(data.name, data.hash, urlObj.toString());
|
this.foundModel(data.name, data.hash, urlObj.toString());
|
||||||
};
|
};
|
||||||
ws.onerror = (event) => {
|
ws.onerror = () => ws.close();
|
||||||
console.error("WebSocket error", event);
|
ws.onclose = () => setTimeout(() => this.monitorWebSocket(url), settings.monitorEveryMs);
|
||||||
}
|
let timeoutFaster = setTimeout(() => ws.close(), settings.monitorOpenTimeoutMs);
|
||||||
ws.onclose = () => {
|
ws.onopen = () => clearTimeout(timeoutFaster);
|
||||||
console.debug("WebSocket closed, reconnecting very soon");
|
|
||||||
setTimeout(() => this.monitorWebSocket(url), settings.checkServerEveryMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private foundModel(name: string, hash: string, url: string) {
|
private foundModel(name: string, hash: string, url: string) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Ref, ShallowRef} from 'vue';
|
import {Ref} from 'vue';
|
||||||
import {Document} from '@gltf-transform/core';
|
import {Document} from '@gltf-transform/core';
|
||||||
import {extrasNameKey, extrasNameValueHelpers, mergeFinalize, mergePartial, removeModel, toBuffer} from "./gltf";
|
import {extrasNameKey, extrasNameValueHelpers, mergeFinalize, mergePartial, removeModel, toBuffer} from "./gltf";
|
||||||
import {newAxes, newGridBox} from "./helpers";
|
import {newAxes, newGridBox} from "./helpers";
|
||||||
@@ -19,6 +19,7 @@ export class SceneMgr {
|
|||||||
|
|
||||||
if (name !== extrasNameValueHelpers) {
|
if (name !== extrasNameValueHelpers) {
|
||||||
// Reload the helpers to fit the new model
|
// Reload the helpers to fit the new model
|
||||||
|
// TODO: Only reload the helpers after a few milliseconds of no more models being added/removed
|
||||||
document = await this.reloadHelpers(sceneUrl, document);
|
document = await this.reloadHelpers(sceneUrl, document);
|
||||||
} else {
|
} else {
|
||||||
// Display the final fully loaded model
|
// Display the final fully loaded model
|
||||||
|
|||||||
@@ -2,18 +2,19 @@
|
|||||||
export const settings = {
|
export const settings = {
|
||||||
preloadModels: [
|
preloadModels: [
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new URL('../../assets/fox.glb', import.meta.url).href,
|
// new URL('../../assets/fox.glb', import.meta.url).href,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new URL('../../assets/logo_build/base.glb', import.meta.url).href,
|
// new URL('../../assets/logo_build/base.glb', import.meta.url).href,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new URL('../../assets/logo_build/location.glb', import.meta.url).href,
|
// new URL('../../assets/logo_build/location.glb', import.meta.url).href,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new URL('../../assets/logo_build/img.jpg.glb', import.meta.url).href,
|
// new URL('../../assets/logo_build/img.jpg.glb', import.meta.url).href,
|
||||||
// Websocket URLs automatically listen for new models from the python backend
|
// Websocket URLs automatically listen for new models from the python backend
|
||||||
// "ws://192.168.1.132:32323/"
|
"ws://127.0.0.1:32323/"
|
||||||
],
|
],
|
||||||
displayLoadingEveryMs: 1000, /* How often to display partially loaded models */
|
displayLoadingEveryMs: 1000, /* How often to display partially loaded models */
|
||||||
checkServerEveryMs: 100, /* How often to check for a new server */
|
monitorEveryMs: 100,
|
||||||
|
monitorOpenTimeoutMs: 10000,
|
||||||
// ModelViewer settings
|
// ModelViewer settings
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
arModes: 'webxr scene-viewer quick-look',
|
arModes: 'webxr scene-viewer quick-look',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ onMounted(() => {
|
|||||||
// Delete the initial load banner
|
// Delete the initial load banner
|
||||||
let banner = elem.value.querySelector('.initial-load-banner');
|
let banner = elem.value.querySelector('.initial-load-banner');
|
||||||
if (banner) banner.remove();
|
if (banner) banner.remove();
|
||||||
// Set the scene
|
// Set the scene and renderer
|
||||||
scene.value = elem.value[$scene] as ModelScene;
|
scene.value = elem.value[$scene] as ModelScene;
|
||||||
renderer.value = elem.value[$renderer] as Renderer;
|
renderer.value = elem.value[$renderer] as Renderer;
|
||||||
// Emit the load event
|
// Emit the load event
|
||||||
@@ -162,11 +162,11 @@ watch(disableTap, (value) => {
|
|||||||
<line :x1="line.start2D[0]" :y1="line.start2D[1]" :x2="line.end2D[0]"
|
<line :x1="line.start2D[0]" :y1="line.start2D[1]" :x2="line.end2D[0]"
|
||||||
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
:y2="line.end2D[1]" v-bind="line.lineAttrs"/>
|
||||||
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
<rect :x="(line.start2D[0] + line.end2D[0]) / 2 - line.centerTextSize[0]/2 - 4"
|
||||||
:y="(line.start2D[1] + line.end2D[1]) / 2 - line.centerTextSize[1]/2 - 4"
|
:y="(line.start2D[1] + line.end2D[1]) / 2 - line.centerTextSize[1]/2 - 2"
|
||||||
:width="line.centerTextSize[0] + 8" :height="line.centerTextSize[1] - 4"
|
:width="line.centerTextSize[0] + 8" :height="line.centerTextSize[1] + 4"
|
||||||
fill="gray" fill-opacity="0.75" rx="4" ry="4" stroke="black" v-if="line.centerText"/>
|
fill="gray" fill-opacity="0.75" rx="4" ry="4" stroke="black" v-if="line.centerText"/>
|
||||||
<text :x="(line.start2D[0] + line.end2D[0]) / 2" :y="(line.start2D[1] + line.end2D[1]) / 2"
|
<text :x="(line.start2D[0] + line.end2D[0]) / 2" :y="(line.start2D[1] + line.end2D[1]) / 2"
|
||||||
text-anchor="middle" alignment-baseline="middle" font-size="16" fill="black"
|
text-anchor="middle" dominant-baseline="middle" font-size="16" fill="black"
|
||||||
:class="'line' + lineId + '_text'" v-if="line.centerText">
|
:class="'line' + lineId + '_text'" v-if="line.centerText">
|
||||||
{{ line.centerText }}
|
{{ line.centerText }}
|
||||||
</text>
|
</text>
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ show_all = server.show_cad_all
|
|||||||
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 import build_logo
|
from logo import build_logo, ASSETS_DIR
|
||||||
logo, img_location, img_path = build_logo()
|
logo, img_location, img_path = build_logo()
|
||||||
server.show_cad(logo, 'Logo')
|
server.show_cad(logo, 'logo')
|
||||||
server.show_cad(img_location, 'Location')
|
server.show_cad(img_location, 'location')
|
||||||
server.show_image(img_path, img_location, 20)
|
server.show_image(img_path, img_location, 20)
|
||||||
|
server.show_gltf(open(os.path.join(ASSETS_DIR, 'fox.glb'), 'rb').read(), 'fox')
|
||||||
return server.app
|
return server.app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,16 @@ from mylogger import logger
|
|||||||
from pubsub import BufferedPubSub
|
from pubsub import BufferedPubSub
|
||||||
from tessellate import _hashcode, tessellate
|
from tessellate import _hashcode, tessellate
|
||||||
|
|
||||||
FRONTEND_BASE_PATH = os.getenv('FRONTEND_BASE_PATH', '../dist')
|
# Find the frontend folder (optional, but recommended)
|
||||||
|
FILE_DIR = os.path.dirname(__file__)
|
||||||
|
FRONTEND_BASE_PATH = os.getenv('FRONTEND_BASE_PATH', os.path.join(FILE_DIR, 'frontend'))
|
||||||
|
if not os.path.exists(FRONTEND_BASE_PATH):
|
||||||
|
if os.path.exists(os.path.join(FILE_DIR, '..', 'dist')): # Fallback to dev build
|
||||||
|
FRONTEND_BASE_PATH = os.path.join(FILE_DIR, '..', 'dist')
|
||||||
|
else:
|
||||||
|
logger.warning('Frontend not found at %s', FRONTEND_BASE_PATH)
|
||||||
|
|
||||||
|
# Define the API paths (also available at the root path for simplicity)
|
||||||
UPDATES_API_PATH = '/api/updates'
|
UPDATES_API_PATH = '/api/updates'
|
||||||
OBJECTS_API_PATH = '/api/object' # /{name}
|
OBJECTS_API_PATH = '/api/object' # /{name}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user