Browse Source

3D模型查看器

master
修宁 7 months ago
parent
commit
068858ba02
  1. 228
      src/components/propertyEdit/TransformEdit.vue
  2. 325
      src/designer/Model3DView.vue

228
src/components/propertyEdit/TransformEdit.vue

@ -0,0 +1,228 @@
<template>
<div class="gui-row">
<div class="gui-item-name"></div>
<div class="gui-item">X</div>
<div class="gui-item">Y</div>
<div class="gui-item">Z</div>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('element Rank')"></component>
</div>
<template v-if="!object3DIsNull">
<div class="gui-item">
<el-input-number v-model="position.x" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="position.y" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="position.z" size="small" :precision="3" :controls="false" />
</div>
</template>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('element RefreshLeft')"></component>
</div>
<template v-if="!object3DIsNull">
<div class="gui-item">
<el-input-number v-model="radianX" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="radianY" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="radianZ" size="small" :precision="3" :controls="false" />
</div>
</template>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('element ScaleToOriginal')"></component>
</div>
<template v-if="!object3DIsNull">
<div class="gui-item">
<el-input-number v-model="scale.x" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="scale.y" size="small" :precision="3" :controls="false" />
</div>
<div class="gui-item">
<el-input-number v-model="scale.z" size="small" :precision="3" :controls="false" />
</div>
</template>
</div>
</template>
<script>
import _ from 'lodash'
import { renderIcon } from '@/utils/webutils.js'
import { markRaw } from 'vue'
export default {
data() {
return {
object3D: null,
object3DIsNull: true,
isBindable: false,
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 }
}
},
mounted() {
window['cc'] = this
},
methods: {
renderIcon,
refreshData(object3D) {
if (!object3D) {
object3D = this.object3D
}
if (!object3D) {
return
}
this.isBindable = false
this.$nextTick(() => {
this.isBindable = true
})
this.position.x = object3D.position.x
this.position.y = object3D.position.y
this.position.z = object3D.position.z
this.rotation.x = object3D.rotation.x
this.rotation.y = object3D.rotation.y
this.rotation.z = object3D.rotation.z
this.scale.x = object3D.scale.x
this.scale.y = object3D.scale.y
this.scale.z = object3D.scale.z
},
attachObject3D(object3D) {
if (!object3D) {
this.detach()
return
}
this.object3D = markRaw(object3D)
this.refreshData(object3D)
this.object3DIsNull = false
},
detach() {
console.log('detach Object3D')
this.object3D = null
this.object3DIsNull = true
}
},
watch: {
position: {
deep: true,
handler(newVal) {
if (!this.object3DIsNull && this.isBindable) {
this.object3D.position.x = newVal.x
this.object3D.position.y = newVal.y
this.object3D.position.z = newVal.z
}
}
},
rotation: {
deep: true,
handler(newVal) {
if (!this.object3DIsNull && this.isBindable) {
this.object3D.rotation.x = newVal.x
this.object3D.rotation.y = newVal.y
this.object3D.rotation.z = newVal.z
}
}
},
scale: {
deep: true,
handler(newVal) {
if (!this.object3DIsNull && this.isBindable) {
this.object3D.scale.x = newVal.x
this.object3D.scale.y = newVal.y
this.object3D.scale.z = newVal.z
}
}
}
},
computed: {
//
radianX: {
get() {
return Math.round(this.rotation.x * 180 / Math.PI)
},
set(newVal) {
this.rotation.x = newVal * Math.PI / 180
}
},
radianY: {
get() {
return Math.round(this.rotation.y * 180 / Math.PI)
},
set(newVal) {
this.rotation.y = newVal * Math.PI / 180
}
},
radianZ: {
get() {
return Math.round(this.rotation.z * 180 / Math.PI)
},
set(newVal) {
this.rotation.z = newVal * Math.PI / 180
}
}
}
}
</script>
<style lang="less">
.gui-toolbar {
color: #fff;
.el-input-number.is-without-controls .el-input__wrapper {
padding-left: 2px;
padding-right: 2px;
}
.gui-row {
display: flex;
flex-direction: row;
gap: 3px;
padding: 3px 0;
.gui-item-name {
width: 26px;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
.el-icon {
font-size: 16px;
}
}
.gui-item {
flex: 1;
text-align: center;
font-size: 12px;
.el-input-number {
width: 100%;
.el-input__wrapper {
background-color: #424242;
box-shadow: none
}
}
}
}
}
</style>

325
src/designer/Model3DView.vue

@ -3,59 +3,58 @@
<el-space :gutter="10" class="toolbar"> <el-space :gutter="10" class="toolbar">
<el-upload :on-change="handleFileChange" <el-upload :on-change="handleFileChange"
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false"> :show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false">
<el-button type="primary">上传文件</el-button> <el-button type="primary">打开模型</el-button>
</el-upload> </el-upload>
<el-upload :on-change="handleTextureUpload" <el-upload :on-change="handleTextureUpload"
:show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false" :show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false"
list-type="picture"> list-type="picture">
<el-button>上传贴图</el-button> <el-button>打开贴图</el-button>
</el-upload> </el-upload>
<el-upload :on-change="handleMtlUpload"
:show-file-list="false" accept=".mtl" action="" :auto-upload="false"
list-type="picture">
<el-button>打开材质</el-button>
</el-upload>
<div class="demo-color-block">
<span class="demonstration">材质颜色</span>
<el-color-picker v-model="restate.targetColor" />
</div>
<!-- 1:1 / 1:0.001 比例 --> <!-- 1:1 / 1:0.001 比例 -->
<el-radio-group v-model="restate.loadScale" size="small"> <div class="demo-color-block">
<el-radio-button label="1:1" :value="1" /> <span class="demonstration">默认比例</span>
<el-radio-button label="1:0.001" :value="0.001" /> <el-radio-group v-model="restate.loadScale">
<el-radio-button label="1" :value="1" />
<el-radio-button label="0.01" :value="0.01" />
<el-radio-button label="0.001" :value="0.001" />
</el-radio-group> </el-radio-group>
</div>
</el-space> </el-space>
<div class="main-content"> <div class="main-content">
<Split class="model3d-content" :direction="'horizontal'"> <Split class="model3d-content" :direction="'horizontal'">
<SplitArea class="model3d-canvas" :size="70"> <SplitArea class="model3d-canvas" :size="70">
<!-- Three.js 渲染画布 --> <!-- Three.js 渲染画布 -->
<div class="canvas-container" ref="canvasContainer"></div> <div class="canvas-container" ref="canvasContainer">
<div class="canvas-left-toolbar">
<button class="Button" :class="{selected:(restate.mode==='translate')}" title="平移"
@mousedown="restate.mode='translate'">
<component :is="renderIcon('element Rank')" />
</button>
<button class="Button" :class="{selected:(restate.mode==='rotate')}" title="旋转"
@mousedown="restate.mode='rotate'">
<component :is="renderIcon('element RefreshLeft')" />
</button>
<button class="Button" :class="{selected:(restate.mode==='scale')}" title="缩放"
@mousedown="restate.mode='scale'">
<component :is="renderIcon('element ScaleToOriginal')" />
</button>
</div>
</div>
</SplitArea> </SplitArea>
<SplitArea class="model3d-gui" :size="30"> <SplitArea class="model3d-gui" :size="30">
<div class="model3d-gui-wrap"> <div class="model3d-gui-wrap">
<!-- 右侧面板 --> <!-- 右侧面板 -->
<div class="gui-toolbar"> <div class="gui-toolbar">
<div class="gui-row"> <TransformEdit ref="transformEditCtl" />
<div class="gui-item-name"></div>
<div class="gui-item">X</div>
<div class="gui-item">Y</div>
<div class="gui-item">Z</div>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('element Aim')"></component>
</div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('antd RotateLeftOutlined')"></component>
</div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
</div>
<div class="gui-row">
<div class="gui-item-name">
<component :is="renderIcon('element ScaleToOriginal')"></component>
</div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div>
</div>
</div> </div>
<div class="gui-panel" ref="guiPanel"></div> <div class="gui-panel" ref="guiPanel"></div>
</div> </div>
@ -65,22 +64,25 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { renderIcon } from '@/utils/webutils.js' import TransformEdit from '@/components/propertyEdit/TransformEdit.vue'
import { ref, onMounted, nextTick, reactive } from 'vue' import { ref, onMounted, nextTick, reactive, watch } from 'vue'
import * as THREE from 'three' import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader'
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js' import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js'
import Stats from 'three/examples/jsm/libs/stats.module' import Stats from 'three/examples/jsm/libs/stats.module'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js' import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import Split from '@/components/split/split.vue' import Split from '@/components/split/split.vue'
import SplitArea from '@/components/split/split-area.vue' import SplitArea from '@/components/split/split-area.vue'
import { renderIcon } from '@/utils/webutils.js'
// DOM refs // DOM refs
const canvasContainer = ref(null) const canvasContainer = ref(null)
const guiPanel = ref(null) const guiPanel = ref(null)
const transformEditCtl = ref(null)
// Three.js // Three.js
let scene, camera, renderer, controls let scene, camera, renderer, controls
@ -88,24 +90,19 @@ let statsControls, axesHelper, gridHelper
let gui, tcontrols, modelGroup let gui, tcontrols, modelGroup
const restate = reactive({ const restate = reactive({
num, targetColor: '#ff0000',
loadScale: 1 loadScale: 1,
mode: 'translate'
}) })
// //
const state = { const state = {
showAxesHelper: true, showAxesHelper: true,
showGridHelper: true, showGridHelper: true,
obj: {
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 }
},
camera: { camera: {
position: { x: 0, y: 5, z: 10 }, position: { x: 0, y: 5, z: 10 },
rotation: { x: 0, y: 0, z: 0 } rotation: { x: 0, y: 0, z: 0 }
}, }
mode: 'translate'
} }
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
@ -114,6 +111,23 @@ onMounted(() => {
}) })
}) })
watch(() => restate.targetColor, (newVal) => {
if (modelGroup) {
modelGroup.traverse((child) => {
if (child.isMesh && child.material?.color) {
child.material.color.set(newVal)
child.material.needsUpdate = true
}
})
}
})
watch(() => restate.mode, (newVal) => {
if (tcontrols) {
tcontrols.setMode(newVal)
}
})
function initThree() { function initThree() {
const viewerDom = canvasContainer.value const viewerDom = canvasContainer.value
@ -125,6 +139,8 @@ function initThree() {
renderer = new THREE.WebGLRenderer({ renderer = new THREE.WebGLRenderer({
logarithmicDepthBuffer: true, logarithmicDepthBuffer: true,
antialias: true, antialias: true,
physicallyCorrectLights: true,
outputEncoding: THREE.SRGBColorSpace,
alpha: true, alpha: true,
precision: 'mediump', precision: 'mediump',
premultipliedAlpha: true, premultipliedAlpha: true,
@ -133,7 +149,7 @@ function initThree() {
}) })
renderer.clearDepth() renderer.clearDepth()
renderer.shadowMap.enabled = true renderer.shadowMap.enabled = true
renderer.outputColorSpace = THREE.SRGBColorSpace // renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.setPixelRatio(window.devicePixelRatio) renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height) renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height)
viewerDom.appendChild(renderer.domElement) viewerDom.appendChild(renderer.domElement)
@ -149,24 +165,46 @@ function initThree() {
scene.add(gridHelper) scene.add(gridHelper)
// //
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6) const ambientLight = new THREE.AmbientLight(0xffffff, 1.5)
scene.add(ambientLight) scene.add(ambientLight)
// const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
// directionalLight.position.set(10, 10, 10) const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
// scene.add(directionalLight) directionalLight.position.set(5, 5, 5).multiplyScalar(3)
directionalLight.castShadow = true
scene.add(directionalLight)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1)
scene.add(hemisphereLight)
// //
statsControls = new Stats() statsControls = new Stats()
statsControls.showPanel(0)
statsControls.dom.style.position = 'absolute' statsControls.dom.style.position = 'absolute'
statsControls.dom.style.bottom = '5px'
statsControls.dom.style.left = '5px'
statsControls.dom.style.top = 'unset'
viewerDom.appendChild(statsControls.dom) viewerDom.appendChild(statsControls.dom)
// //
// const geometry = new THREE.BoxGeometry(1, 1, 1)
// const material = new THREE.MeshStandardMaterial({
// color: 0xcccccc,
// metalness: 0.9,
// roughness: 0.1
// })
//
// const cube = new THREE.Mesh(geometry, material)
// scene.add(cube)
// camera.position.z = 5
animate() animate()
// TransformControls // TransformControls
tcontrols = new TransformControls(camera, renderer.domElement) tcontrols = new TransformControls(camera, renderer.domElement)
tcontrols.addEventListener('change', () => { tcontrols.addEventListener('change', () => {
renderer.render(scene, camera) renderer.render(scene, camera)
reloadState() transformEditCtl.value?.refreshData()
}) })
tcontrols.addEventListener('dragging-changed', function(event) { tcontrols.addEventListener('dragging-changed', function(event) {
controls.enabled = !event.value controls.enabled = !event.value
@ -194,10 +232,6 @@ function initGUI() {
gui = new dat.GUI({ autoPlace: false }) gui = new dat.GUI({ autoPlace: false })
guiDom.appendChild(gui.domElement) guiDom.appendChild(gui.domElement)
gui.add(state, 'mode', ['translate', 'rotate', 'scale']).onChange(function(value) {
tcontrols.setMode(value)
})
// 线 // 线
gui.add(state, 'showAxesHelper').name('显示坐标轴').onChange(val => { gui.add(state, 'showAxesHelper').name('显示坐标轴').onChange(val => {
axesHelper.visible = val axesHelper.visible = val
@ -233,46 +267,6 @@ function initGUI() {
}) })
cameraRotationFolder.close() cameraRotationFolder.close()
// Position
const positionFolder = gui.addFolder('Position')
positionFolder.add(state.obj.position, 'x', -10, 10).listen().onChange(val => {
modelGroup.position.x = val
})
positionFolder.add(state.obj.position, 'y', -10, 10).listen().onChange(val => {
modelGroup.position.y = val
})
positionFolder.add(state.obj.position, 'z', -10, 10).listen().onChange(val => {
modelGroup.position.z = val
})
positionFolder.close()
// Rotation (in radians)
const rotationFolder = gui.addFolder('Rotation')
rotationFolder.add(state.obj.rotation, 'x', -Math.PI, Math.PI).listen().onChange(val => {
modelGroup.rotation.x = val
})
rotationFolder.add(state.obj.rotation, 'y', -Math.PI, Math.PI).listen().onChange(val => {
modelGroup.rotation.y = val
})
rotationFolder.add(state.obj.rotation, 'z', -Math.PI, Math.PI).listen().onChange(val => {
modelGroup.rotation.z = val
})
rotationFolder.close()
// Scale
const scaleFolder = gui.addFolder('Scale')
scaleFolder.add(state.obj.scale, 'x', 0.001, 10).listen().onChange(val => {
modelGroup.scale.x = val
})
scaleFolder.add(state.obj.scale, 'y', 0.001, 10).listen().onChange(val => {
modelGroup.scale.y = val
})
scaleFolder.add(state.obj.scale, 'z', 0.001, 10).listen().onChange(val => {
modelGroup.scale.z = val
})
scaleFolder.close()
// //
// const cameraFolder = gui.addFolder('') // const cameraFolder = gui.addFolder('')
// cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X') // cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X')
@ -333,7 +327,7 @@ function initMode3DCamera() {
const viewerDom = canvasContainer.value const viewerDom = canvasContainer.value
// //
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 1, 2000) const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 0.1, 2000)
// //
// cameraNew.position.set(4, 2, -3); // cameraNew.position.set(4, 2, -3);
// cameraNew.position.set(30, 30, 30) // cameraNew.position.set(30, 30, 30)
@ -371,20 +365,6 @@ function reloadState() {
state.camera.rotation.y = camera.rotation.y state.camera.rotation.y = camera.rotation.y
state.camera.rotation.z = camera.rotation.z state.camera.rotation.z = camera.rotation.z
} }
if (modelGroup) {
state.obj.position.x = modelGroup.position.x
state.obj.position.y = modelGroup.position.y
state.obj.position.z = modelGroup.position.z
state.obj.rotation.x = modelGroup.rotation.x
state.obj.rotation.y = modelGroup.rotation.y
state.obj.rotation.z = modelGroup.rotation.z
state.obj.scale.x = modelGroup.scale.x
state.obj.scale.y = modelGroup.scale.y
state.obj.scale.z = modelGroup.scale.z
console.log('state.obj', state.obj)
}
} }
function renderView() { function renderView() {
@ -438,6 +418,53 @@ function handleTextureUpload(file) {
} }
} }
let lastObjfile = undefined
function handleMtlUpload(file) {
console.log('file', file)
if (!file) return
file = file.raw
//
if (modelGroup) {
scene.remove(modelGroup)
}
tcontrols.detach()
transformEditCtl.value.detach()
modelGroup = null
const fileName = file.name.toLowerCase()
const reader = new FileReader()
reader.onerror = (error) => {
system.showErrorDialog('加载文件失败', error.toString())
system.clearLoading()
}
if (fileName.endsWith('.mtl')) {
reader.onload = () => {
const mtlLoader = new MTLLoader()
mtlLoader.load(reader.result, (materials) => {
materials.preload()
const objLoader = new OBJLoader()
objLoader.setMaterials(materials)
modelGroup = objLoader.parse(lastObjfile)
if (restate.loadScale) {
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale)
}
scene.add(modelGroup)
tcontrols.attach(modelGroup)
transformEditCtl.value?.attachObject3D(modelGroup)
})
}
reader.readAsText(file)
} else {
alert('不支持的文件类型!')
}
}
function handleFileChange(file) { function handleFileChange(file) {
console.log('file', file) console.log('file', file)
if (!file) return if (!file) return
@ -448,6 +475,8 @@ function handleFileChange(file) {
if (modelGroup) { if (modelGroup) {
scene.remove(modelGroup) scene.remove(modelGroup)
} }
tcontrols.detach()
transformEditCtl.value.detach()
const fileName = file.name.toLowerCase() const fileName = file.name.toLowerCase()
const reader = new FileReader() const reader = new FileReader()
@ -465,12 +494,10 @@ function handleFileChange(file) {
modelGroup = content modelGroup = content
if (restate.loadScale) { if (restate.loadScale) {
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale)
state.obj.scale.x = modelGroup.scale.x
state.obj.scale.y = modelGroup.scale.y
state.obj.scale.z = modelGroup.scale.z
} }
scene.add(modelGroup) scene.add(modelGroup)
tcontrols.attach(modelGroup) tcontrols.attach(modelGroup)
transformEditCtl.value?.attachObject3D(modelGroup)
controls.target.copy(modelGroup.position) controls.target.copy(modelGroup.position)
controls.update() controls.update()
@ -483,16 +510,15 @@ function handleFileChange(file) {
reader.readAsText(file) reader.readAsText(file)
reader.onload = () => { reader.onload = () => {
const loader = new OBJLoader() const loader = new OBJLoader()
lastObjfile = reader.result
//@ts-ignore //@ts-ignore
modelGroup = loader.parse(reader.result) modelGroup = loader.parse(reader.result)
if (restate.loadScale) { if (restate.loadScale) {
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale)
state.obj.scale.x = modelGroup.scale.x
state.obj.scale.y = modelGroup.scale.y
state.obj.scale.z = modelGroup.scale.z
} }
scene.add(modelGroup) scene.add(modelGroup)
tcontrols.attach(modelGroup) tcontrols.attach(modelGroup)
transformEditCtl.value?.attachObject3D(modelGroup)
system.clearLoading() system.clearLoading()
} }
@ -506,12 +532,10 @@ function handleFileChange(file) {
modelGroup = content modelGroup = content
if (restate.loadScale) { if (restate.loadScale) {
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale)
state.obj.scale.x = modelGroup.scale.x
state.obj.scale.y = modelGroup.scale.y
state.obj.scale.z = modelGroup.scale.z
} }
scene.add(modelGroup) scene.add(modelGroup)
tcontrols.attach(modelGroup) tcontrols.attach(modelGroup)
transformEditCtl.value?.attachObject3D(modelGroup)
system.clearLoading() system.clearLoading()
} }
@ -522,8 +546,6 @@ function handleFileChange(file) {
} }
} }
window['state'] = state
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.model3d-view { .model3d-view {
@ -532,6 +554,31 @@ window['state'] = state
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
.canvas-left-toolbar {
position: absolute;
left: 5px;
top: 5px;
width: 32px;
background: #eee;
text-align: center;
button {
color: #555;
background-color: #ddd;
border: 0;
margin: 0;
padding: 5px 8px;
font-size: 12px;
text-transform: uppercase;
cursor: pointer;
outline: none;
}
button.selected {
background-color: #fff;
}
}
.toolbar { .toolbar {
padding: 10px; padding: 10px;
background: #f5f5f5; background: #f5f5f5;
@ -569,37 +616,7 @@ window['state'] = state
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
} }
.gui-toolbar{
color:#fff;
.gui-row{
display: flex;
flex-direction: row;
gap: 3px;
padding:3px 0;
.gui-item-name{
width: 26px;
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
.el-icon{
font-size: 16px;
}
}
.gui-item{
flex:1;
text-align: center;
font-size: 12px;
.el-input-number{
width: 100%;
.el-input__wrapper{
background-color: #424242;
box-shadow:none
}
}
}
}
}
.gui-panel { .gui-panel {
:deep(.lil-gui.root) { :deep(.lil-gui.root) {
width: 100%; width: 100%;

Loading…
Cancel
Save