feat(All):Initial

This commit is contained in:
2025-10-04 23:36:07 +08:00
commit 2b4e5d2668
1321 changed files with 415958 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
import * as THREE from 'three';
import { CSM as _CSM } from 'three/examples/jsm/csm/CSM.js';
import { useDispatchSignal } from '@/hooks';
import App from "@/core/app/App";
// Cascaded Shadow Maps(级联阴影映射,CSM
class CSM {
instance:_CSM | null = null;
constructor(options:IAppProject.CSM){
this.enabled = options.enabled;
}
get enabled(){
return !!this.instance;
}
set enabled(isEnabled:boolean){
if (!isEnabled){
if(!this.instance) return;
// 移除csm创建的对象
this.instance.remove();
// 销毁csm插入的shader
this.instance.dispose();
this.instance = null;
useDispatchSignal("sceneGraphChanged");
return;
}
/* 以下是启用csm的逻辑 */
if(this.instance){
this.reset();
return;
}
const _config = App.project.getKey("csm");
this.instance = new _CSM({
maxFar: _config.maxFar,
cascades: 4,
mode: _config.mode,
shadowMapSize: _config.shadowMapSize,
lightDirection: new THREE.Vector3(_config.lightDirectionX, _config.lightDirectionY, _config.lightDirectionZ).normalize(),
lightIntensity: _config.lightIntensity,
lightNear: 0.1,
lightFar: _config.maxFar * 2,
lightMargin: 200,
camera: App.viewportCamera,
parent: App.scene
});
this.instance.fade = true;
this.instance.lights.forEach(light => {
// 忽略对csm相关object的处理
light.ignore = true;
light.target.ignore = true;
// 设置的灯光颜色
light.color = new THREE.Color(_config.lightColor);
light.shadow.bias = -0.00001;
})
// 将场景中的全部材质添加到csm
Object.values(App.materials).forEach(material => {
this.setupMaterial(material);
})
this.instance.updateFrustums();
useDispatchSignal("sceneGraphChanged");
}
reset() {
if (!this.instance) return;
this.enabled = false;
this.enabled = true;
}
// 材质添加到csm
setupMaterial(material:THREE.Material){
if(!this.instance) return;
material.shadowSide = THREE.BackSide;
this.instance.setupMaterial(material);
}
updateProperty(key,value){
if (!this.instance) return;
this.instance[key] = value;
this.instance.updateFrustums();
useDispatchSignal("sceneGraphChanged");
}
updateLightColor(color: string){
if (!this.instance) return;
this.instance.lights.forEach(light => {
light.color = new THREE.Color(color);
})
useDispatchSignal("sceneGraphChanged");
}
updateLightIntensity(intensity: number){
if (!this.instance) return;
this.instance.lightIntensity = intensity;
this.instance.lights.forEach(light => {
light.intensity = intensity;
})
useDispatchSignal("sceneGraphChanged");
}
updateLightDirection(direction: "x" | "y" | "z", value: number){
if (!this.instance) return;
this.instance.lightDirection[direction] = value;
useDispatchSignal("sceneGraphChanged");
}
updateFrustums(){
if (!this.instance) return;
this.instance.updateFrustums();
useDispatchSignal("sceneGraphChanged");
}
update(){
if (!this.instance) return;
App.viewportCamera.updateMatrixWorld();
this.instance.update();
}
}
export {CSM}
+134
View File
@@ -0,0 +1,134 @@
/**
* @author ErSan
* @email mlt131220@163.com
* @date 2025/4/26 13:59
* @description 应用的全局配置,会存储在本地缓存
*/
import {Storage} from "./Storage";
import {deepAssign, getNestedProperty} from "@/utils";
import {ROAMING_CHARACTERS} from "@/constant";
class Config {
protected storage: Storage;
public config: IAppConfig.Config;
constructor(storage: Storage) {
this.storage = storage;
this.config = {
// UI相关配置
theme: 'os',
mainColor: '#7FE7C4',
// 历史记录功能是否启用
history: false,
// 快捷键相关配置
shortcuts: {
translate: 'w',
rotate: 'e',
scale: 'r',
undo: 'z',
focus: 'f',
},
//漫游角色
roamingCharacter: ROAMING_CHARACTERS.JACKIE
};
this.syncStorage();
}
/**
* 设置初始配置
*/
setConfig(_config:Record<string, any>){
deepAssign(this.config,_config);
this.syncStorage();
}
/**
* 和本地存储中的配置同步
*/
syncStorage(){
for (let key of Object.keys(this.config)) {
this.storage.getConfigItem(key).then(_value => {
if(_value === null){
this.storage.setConfigItem(key, this.config[key])
}else{
let newVal = _value;
// 有可能会在代码开发过程中增加新的配置项
if(this.config[key] && typeof this.config[key] === "object"){
newVal = Object.assign({},this.config[key],_value);
}
this.config[key] = newVal;
if(newVal !== _value){
this.storage.setConfigItem(key, newVal)
}
}
}).catch(() => {
this.storage.setConfigItem(key, this.config[key])
})
}
}
/**
* 获取配置
* @param {string} key 可以多层级,需用.分割,如a.b.c
*/
getKey(key:string): any {
return getNestedProperty(this.config,key);
}
/**
* 设置配置项
* @param {string} key 可以多层级,需用.分割,如a.b.c
* @param {unknown} value 配置项的值
*/
setKey(key:string,value:unknown) {
const keys = key.split(".");
if(keys.length === 1){
this.config[key] = value;
this.storage.setConfigItem(key,value);
return;
}
let obj = this.config;
for (let i = 0; i < keys.length; i++){
if(keys.length - i === 1){
obj[keys[i]] = value;
break;
}
obj = obj[keys[i]];
}
this.storage.setConfigItem(keys[0],this.config[keys[0]]);
}
/**
* 获取快捷键配置
* @param {string} key
*/
getShortcutItem(key: string) {
return this.config.shortcuts[key];
}
/**
* 设置快捷键
* @param {string} key
* @param {any} value
*/
setShortcutItem(key: string,value:any) {
this.config.shortcuts[key] = value;
return this.storage.setConfigItem("shortcuts", this.config.shortcuts)
}
clear() {
for (let key of Object.keys(this.config)) {
this.storage.removeConfigItem(key);
}
}
}
export {Config};
@@ -0,0 +1,224 @@
import type { Object3D } from 'three';
import * as Commands from '@/core/commands/Commands';
import {useSignal} from "@/hooks";
import App from "@/core/app/App";
const {dispatch:useDispathSignal,setActive} = useSignal();
interface Undos{
id:number,
name?:string,
updatable:boolean,
object:Object3D,
type:string,
script,
attributeName:string,
inMemory:boolean,
json:string,
update:(T)=>void,
toJSON:()=>string,
fromJSON:(string)=>void,
undo:()=>void,
execute:()=>void,
}
class History {
public undos:Array<Undos>;
public redos:Array<Undos>;
protected lastCmdTime:number;
protected idCounter:number;
constructor() {
this.undos = [];
this.redos = [];
this.lastCmdTime = Date.now();
this.idCounter = 0;
}
execute( cmd, optionalName ) {
const lastCmd = this.undos[this.undos.length - 1];
const timeDifference = Date.now() - this.lastCmdTime;
const isUpdatableCmd = lastCmd &&
lastCmd.updatable &&
cmd.updatable &&
lastCmd.object === cmd.object &&
lastCmd.type === cmd.type &&
lastCmd.script === cmd.script &&
lastCmd.attributeName === cmd.attributeName;
if ( isUpdatableCmd && cmd.type === 'SetScriptValueCommand' ) {
// 当cmd.type为“SetScriptValueCommand”时,将忽略时间差异
lastCmd.update( cmd );
cmd = lastCmd;
} else if ( isUpdatableCmd && timeDifference < 500 ) {
lastCmd.update( cmd );
cmd = lastCmd;
} else {
// 该命令不可更新,并作为历史记录的新部分添加
this.undos.push( cmd );
cmd.id = ++ this.idCounter;
}
cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
cmd.execute();
cmd.inMemory = true;
if (App.config.getKey('history')) {
//在执行后立即序列化cmd,并将json附加到cmd
cmd.json = cmd.toJSON();
}
this.lastCmdTime = Date.now();
// 清除所有redo命令
this.redos = [];
useDispathSignal("historyChanged",cmd);
}
undo() {
let cmd:Undos | undefined = undefined;
if (this.undos.length > 0) {
cmd = this.undos.pop() as Undos;
if ( cmd.inMemory === false ) {
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
cmd.undo();
this.redos.push( cmd );
useDispathSignal("historyChanged",cmd);
}
return cmd;
}
redo():Undos |undefined {
let cmd:Undos |undefined = undefined;
if ( this.redos.length > 0 ) {
cmd = this.redos.pop() as Undos;
if ( cmd.inMemory === false ) {
cmd.fromJSON( cmd.json );
}
}
if ( cmd !== undefined ) {
cmd.execute();
this.undos.push( cmd );
useDispathSignal( "historyChanged",cmd );
}
return cmd;
}
toJSON() {
const history:{undos?:Array<string>,redos?:Array<string>} = {};
history.undos = [];
history.redos = [];
if (!App.config.getKey('history')) return history;
//将Undos附加到历史记录
for ( let i = 0; i < this.undos.length; i ++ ) {
if (this.undos[ i ].hasOwnProperty( 'json' )) {
history.undos.push(this.undos[i].json);
}
}
//将Redos附加到历史记录
for ( let i = 0; i < this.redos.length; i ++ ) {
if (this.redos[ i ].hasOwnProperty( 'json' )) {
history.redos.push(this.redos[ i ].json);
}
}
return history;
}
fromJSON( json ) {
if ( json === undefined ) return;
for ( let i = 0; i < json.undos.length; i ++ ) {
const cmdJSON = json.undos[ i ];
//创建一个类型为"json.type"的新对象
const cmd = new Commands[cmdJSON.type](App);
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.undos.push( cmd );
//设置最后使用的idCounter
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter;
}
for ( let i = 0; i < json.redos.length; i ++ ) {
const cmdJSON = json.redos[ i ];
const cmd = new Commands[cmdJSON.type](App);
cmd.json = cmdJSON;
cmd.id = cmdJSON.id;
cmd.name = cmdJSON.name;
this.redos.push( cmd );
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter;
}
// 选择最后执行的undo命令
useDispathSignal( "historyChanged",this.undos[ this.undos.length - 1 ] );
}
clear() {
this.undos = [];
this.redos = [];
this.idCounter = 0;
useDispathSignal("historyChanged");
}
goToState( id:number ) {
setActive("sceneGraphChanged",false);
setActive("historyChanged",false);
//下一个弹出的CMD
let cmd:Undos |undefined = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined;
if ( cmd === undefined || id > cmd.id ) {
cmd = this.redo();
while ( cmd !== undefined && id > cmd.id ) {
cmd = this.redo();
}
} else {
while ( true ) {
cmd = this.undos[ this.undos.length - 1 ];
if ( cmd === undefined || id === cmd.id ) break;
this.undo();
}
}
setActive("sceneGraphChanged",true);
setActive("historyChanged",true);
useDispathSignal("sceneGraphChanged");
useDispathSignal("historyChanged",cmd);
}
enableSerialization( id ) {
/**
* 因为可能有命令在 this.undos && this.redos
* 没有被.toJSON()序列化的,我们返回
*/
this.goToState(-1);
setActive("sceneGraphChanged",false);
setActive("historyChanged",false);
let cmd:Undos |undefined = this.redo();
while ( cmd !== undefined ) {
if (!cmd.hasOwnProperty('json')) {
cmd.json = cmd.toJSON();
}
cmd = this.redo();
}
setActive("sceneGraphChanged",true);
setActive("historyChanged",true);
this.goToState( id );
}
}
export { History };
@@ -0,0 +1,402 @@
/**
* @author ErSan
* @email mlt131220@163.com
* @date 2025/4/26 13:59
* @description 当前项目相关信息
*/
import {getNestedProperty} from "@/utils";
import type {App} from "../App";
import {useRemoveSignal, useAddSignal, useDispatchSignal} from '@/hooks';
import {FPS_OPTIONS} from "@/constant";
export const defaultProjectInfo = (): IAppProject.Info => ({
// 项目运行是否启用xr
xr: false,
// 渲染器相关配置
renderer: {
// 渲染帧率上限,默认60
fps: FPS_OPTIONS.HIGH,
antialias: true,
toneMapping: 0, // NoToneMapping
toneMappingExposure: 1,
shadow: {
enabled: true,
type: 2, // PCF Soft
},
},
// 级联阴影映射
csm: {
enabled: false,
fade: false,
maxFar: 1000,
mode: "practical",
shadowMapSize: 2048,
lightDirectionX: -1,
lightDirectionY: -1,
lightDirectionZ: -1,
lightIntensity: 1,
lightColor: "#ffffff"
},
// 后处理
effect: {
enabled: false,
// 描边线
Outline: {
enabled: true,
// 边缘的强度,值越高边框范围越大
edgeStrength: Number(3.0),
// 发光强度
edgeGlow: Number(0.2),
// 边缘浓度
edgeThickness: Number(1.0),
// 闪烁频率,值越大频率越低
pulsePeriod: Number(0.0),
// 禁用纹理以获得纯线的效果
usePatternTexture: false,
// 可见边缘的颜色
visibleEdgeColor: "#ffee00",
// 不可见边缘的颜色
hiddenEdgeColor: "#ff6a00"
},
// 抗锯齿
FXAA: {
enabled: true,
},
// 辉光
UnrealBloom: {
enabled: false,
// 光晕阈值,值越小,效果越明显
threshold: 0,
// 光晕强度
strength: 1,
// 光晕半径
radius: 0
},
// 背景虚化
Bokeh: {
enabled: false,
// 焦距,调整远近,对焦时才会清晰
focus: 500.0,
// 孔径,类似相机孔径调节
aperture: 0.00005,
// 最大模糊程度
maxblur: 0.01
},
// 像素风
Pixelate: {
enabled: false,
// 像素大小
pixelSize: 6,
// 法向边缘强度
normalEdgeStrength: 0.3,
// 深度边缘强度
depthEdgeStrength: 0.4,
},
// 半色调
Halftone: {
enabled: false,
// 形状:点,椭圆,线,正方形
shape: 1,
// 半径
radius: 4,
// R色旋转
rotateR: Math.PI / 12,
// G色旋转
rotateG: Math.PI / 12 * 2,
// B色旋转
rotateB: Math.PI / 12 * 3,
// 分散度
scatter: 0,
// 混合度
blending: 1,
// 混合模式:线性,相乘,相加,明亮,昏暗
blendingMode: 1,
// 灰度
greyscale: false,
},
// LUT颜色滤镜
LUT: {
enabled: false,
lut: 'Bourbon 64.CUBE',
intensity: 1
},
// 运动残影
Afterimage: {
enabled: false,
damp: 0.95
}
},
// 天气
weather: {
fog: {
enabled: false,
type: "Fog", // Fog, FogExp2
color: "#ffffff",
near: 0.10,
far: 50.0,
density: 0.05,
},
rain: {
enabled: false,
speed: 0.4,
color: "#ffffff",
size: 0.5,
radian: 95,
alpha: 0.4
},
snow: {
enabled: false,
size: 0.5,
density: 1.0,
speed: 1.0,
alpha: 0.5,
accumulation: false,
}
},
// 场景信息
sceneInfo: {
// 场景id,使用uuid
id: "",
// 场景名称
sceneName: "",
// 场景分类 城市、园区、工厂、楼宇、设备、其他...
sceneType: "其他",
// 场景描述
sceneIntroduction: "",
// 场景版本
sceneVersion: 1,
// 项目类型。0Web3D-THREE 1WebGIS-Cesium
projectType: 0,
// 场景封面图
coverPicture: "",
// 场景是否包含图纸
hasDrawing: false,
// 场景zip包地址
zip: "",
// 场景zip包大小
zipSize: "0",
// WebGIS-Cesium 类型项目的基础Cesium配置
cesiumConfig: undefined
},
// 图纸
drawing: {
// 是否已上传图纸
isUploaded: false,
// 图片base64 / cad文件路径
imgSrc: "",
// 是否cad
isCad: false,
// cad图层信息
layers: {},
// 是否正在绘制矩形标记
isDrawingRect: false,
// 选中的矩形索引
selectedRectIndex: -1,
// 标记列表
markList: [],
// 标记图纸时的图纸属性信息
imgInfo: {
width: 0,
height: 0
}
}
})
let drawingMarkDoneFn: null | ((type: "add" | "update", rect: IAppProject.DrawingMark) => void) = null;
class Project {
public app: App
public info: IAppProject.Info;
constructor(app: App) {
this.app = app;
this.info = defaultProjectInfo();
drawingMarkDoneFn = this.drawingMarkListChange.bind(this);
useAddSignal("drawingMarkDone", drawingMarkDoneFn);
}
/**
* 获取配置
* @param {string} key 可以多层级,需用.分割,如a.b.c
*/
getKey(key: string): any {
return getNestedProperty(this.info, key);
}
/**
* 设置配置项,配置变更自动执行相应处理
* @param {string} key 可以多层级,需用.分割,如a.b.c
* @param {unknown} value 配置项的值
* @param {boolean} executeAction 是否自动执行相应处理
*/
setKey(key: string, value: unknown,executeAction: boolean = true) {
const keys = key.split(".");
if (keys.length === 1) {
this.info[key] = value;
} else {
let obj = this.info;
for (let i = 0; i < keys.length; i++) {
if (keys.length - i === 1) {
obj[keys[i]] = value;
break;
}
obj = obj[keys[i]];
}
}
/* 执行相应处理 */
if(!executeAction || ["xr","sceneInfo","drawing"].includes(keys[0])) return;
const secondProperty = keys[1];
// 如果setKey传入的是第一层级的变更且不是特殊单层处理的属性,则遍历为第二层级递归以执行相应处理
if(!secondProperty && !["renderer"].includes(key)) {
const propertyValue = this.info[key];
Object.keys(propertyValue).forEach(secondKey => {
this.setKey(`${key}.${secondKey}`, propertyValue[secondKey]);
})
return;
}
if (key.startsWith("renderer")) {
if(!this.app.viewer) return;
if (["renderer.antialias","renderer"].includes(key)) {
this.app.viewer.createEngine();
} else {
this.app.viewer.renderer.shadowMap.enabled = this.info.renderer.shadow.enabled;
this.app.viewer.renderer.shadowMap.type = this.info.renderer.shadow.type;
this.app.viewer.renderer.toneMapping = this.info.renderer.toneMapping;
this.app.viewer.renderer.toneMappingExposure = this.info.renderer.toneMappingExposure;
this.app.FPS = this.info.renderer.fps;
useDispatchSignal("rendererUpdated");
}
} else if (key.startsWith("csm")) {
switch (key) {
case "csm.enabled":
this.app.csm.enabled = this.info.csm.enabled;
break;
case "csm.fade":
case "csm.maxFar":
case "csm.mode":
this.app.csm.updateProperty(secondProperty, this.info.csm[secondProperty]);
break;
case "csm.shadowMapSize":
this.app.csm.reset();
break;
case "csm.lightColor":
this.app.csm.updateLightColor(this.info.csm.lightColor);
break;
case "csm.lightIntensity":
this.app.csm.updateLightIntensity(this.info.csm.lightIntensity);
break;
case "csm.lightDirectionX":
this.app.csm.updateLightDirection('x',this.info.csm.lightDirectionX);
break;
case "csm.lightDirectionY":
this.app.csm.updateLightDirection('y',this.info.csm.lightDirectionY);
break;
case "csm.lightDirectionZ":
this.app.csm.updateLightDirection('z',this.info.csm.lightDirectionZ);
break;
}
}else if(key.startsWith("effect")){
if(key === "effect.enabled"){
useDispatchSignal("effectEnabledChange",this.info.effect.enabled);
}else{
useDispatchSignal("effectPassConfigChange",secondProperty,this.info.effect[secondProperty]);
}
}else if(key.startsWith("weather")){
switch (key){
case "weather.fog":
useDispatchSignal("sceneFogSettingsChanged");
break;
case "weather.rain":
useDispatchSignal("sceneRainSettingsChanged");
break;
case "weather.snow":
useDispatchSignal("sceneSnowSettingsChanged");
break;
}
}
}
/**
* 设置图纸src
*/
setDrawingSrc(src: string) {
this.info.drawing.isCad = src.split(".").pop() === "dxf";
this.info.drawing.imgSrc = src;
}
/**
* 设置图纸图层显示隐藏
* @param layerName
* @param visible
*/
setDrawingLayerVisible(layerName: string, visible: boolean) {
this.info.drawing.layers[layerName].visible = visible;
}
/**
* 设置图纸所有图层显示隐藏
* @param visible
*/
setDrawingLayerAllVisible(visible: boolean) {
for (let key in this.info.drawing.layers) {
this.info.drawing.layers[key].visible = visible;
}
}
/**
* 图纸标记变更
* @param type
* @param rect
*/
drawingMarkListChange(type: "add" | "update", rect: IAppProject.DrawingMark) {
switch (type) {
case "add":
this.info.drawing.markList.push(rect);
break;
case "update":
const index = this.info.drawing.markList.findIndex(item => item.modelUuid === rect.modelUuid);
if (index !== -1) {
this.info.drawing.markList[index] = rect;
}
break;
}
}
/**
* 重置图纸配置,一般用于清除图纸状态
*/
resetDrawing() {
this.info.drawing = defaultProjectInfo().drawing;
}
// /**
// * 清空所有项目配置
// */
// clear(){
// const sceneInfo = {...this.info.sceneInfo};
//
// this.info = defaultProjectInfo();
//
// this.info.sceneInfo = sceneInfo;
// }
dispose() {
if (drawingMarkDoneFn) {
useRemoveSignal("drawingMarkDone", drawingMarkDoneFn);
drawingMarkDoneFn = null;
}
}
}
export {Project};
@@ -0,0 +1,18 @@
import * as THREE from 'three';
import Loader from "@/core/loader/Loader";
class Resource{
constructor() { }
loadURLTexture(url: string | THREE.Texture, onload: (tex: THREE.Texture) => void = ()=>{}, onerror: (err: any) => void = ()=>{}) {
if(url instanceof THREE.Texture) {
onload(url);
return url;
}
const extension = url.split(".").pop()?.toLowerCase() as string;
return Loader.loadUrlTexture(extension, url, onload,onerror);
}
}
export {Resource};
@@ -0,0 +1,85 @@
import {useDispatchSignal, useAddSignal} from '@/hooks';
import { MeshLambertMaterial } from "three";
import App from "@/core/app/App.ts";
import Loader from "@/core/loader/Loader.ts";
import * as THREE from "three";
class Selector {
public lastIsIFC = false; // 上一次选中的是否是IFC模型
public lastIFCModelID :number | null = null; // 上一次选中的IFC模型ID
private preselectMat = new MeshLambertMaterial({
transparent: true,
opacity: 0.6,
color: 0xff88ff,
depthTest: false,
});
constructor() {
// signals
useAddSignal("intersectionsDetected",async (intersects) => {
if(this.lastIFCModelID !== null){
// 移除之前IFC模型的高亮部分
Loader._ifcLoader.ifcManager.removeSubset(this.lastIFCModelID, this.preselectMat);
this.lastIFCModelID = null;
}
if (intersects.length > 0) {
const object = intersects[0].object;
// ---- 2023/8/10 添加IFC模型检测判断-----
if(object.isIFC){
const index = intersects[0].faceIndex;
const geometry = object.geometry;
const ifc = Loader._ifcLoader.ifcManager;
const id = ifc.getExpressId(geometry, index);
this.lastIFCModelID = object.modelID;
const props = await ifc.getItemProperties(this.lastIFCModelID as number, id,true);
useDispatchSignal("IFCPropertiesVisible",true,props)
this.lastIsIFC = true;
// TODO 部件选中
// 创建子集
Loader._ifcLoader.ifcManager.createSubset({
modelID: this.lastIFCModelID as number,
ids: [id],
material: this.preselectMat,
scene: App.scene,
removePrevious: true,
});
return
}
if(this.lastIsIFC){
useDispatchSignal("IFCPropertiesVisible",false)
this.lastIsIFC = false;
}
if(object.proxy){
this.select(object.proxy);
} else {
this.select(object);
}
} else {
this.select( null );
}
})
}
select(object:THREE.Object3D | null) {
if (App.selected === object) return;
App.selected = object;
useDispatchSignal("objectSelected",object, App.locked);
useDispatchSignal("sceneGraphChanged");
}
deselect() {
this.select(null);
}
}
export {Selector};
@@ -0,0 +1,61 @@
import localforage from 'localforage';
class Storage {
public dbs: { modelsDB: LocalForage,otherDB:LocalForage,configDB:LocalForage };
constructor() {
this.dbs = this.initDB();
}
initDB(){
return {
modelsDB: localforage.createInstance({
name: 'modelsDB',
}),
otherDB: localforage.createInstance({
name: 'otherDB'
}),
configDB: localforage.createInstance({
name: 'configDB'
})
}
}
setModel(key: string, value: any){
this.dbs.modelsDB.setItem(key, value);
}
async getModel(key: string){
return await this.dbs.modelsDB.getItem(key);
}
removeModel(key: string){
return this.dbs.modelsDB.removeItem(key);
}
setOtherItem(key: string, value: any){
this.dbs.otherDB.setItem(key, value);
}
async getOtherItem(key:string){
return await this.dbs.otherDB.getItem(key);
}
removeOtherItem(key: string){
return this.dbs.otherDB.removeItem(key);
}
setConfigItem(key: string, value: any){
return this.dbs.configDB.setItem(key, value);
}
async getConfigItem(key:string){
return await this.dbs.configDB.getItem(key);
}
removeConfigItem(key: string){
return this.dbs.configDB.removeItem(key);
}
}
export {Storage};
@@ -0,0 +1,7 @@
export {Config} from "./Config";
export {CSM} from "./CSM";
export {History} from "./History";
export {Project,defaultProjectInfo} from "./Project";
export {Resource} from "./Resource";
export {Selector} from "./Selector";
export {Storage} from "./Storage";