调整部分bug

This commit is contained in:
X1627315083
2025-05-28 10:28:07 +08:00
parent 608a4dec89
commit 9fc2bcaedf
78 changed files with 2034 additions and 10193 deletions

View File

@@ -0,0 +1,213 @@
<template>
<div class="threeDownPage" ref="threeDownPage">
<!-- height="65rem" -->
<a-modal class="generalModel"
v-model:visible="threeDown"
:footer="null"
width="77rem"
height="35rem"
:maskClosable="false"
:mask="true"
:centered="true"
:closable="false"
:get-container="() => $refs.threeDownPage"
wrapClassName="#app"
:keyboard="false"
>
<div class="generalModel_btn">
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="23" cy="23" r="23" fill="white" fill-opacity="0.3"/>
<rect x="32.5063" y="12" width="3" height="29" rx="1.5" transform="rotate(45 32.5063 12)" fill="black"/>
<rect x="34.6274" y="32.5059" width="3" height="29" rx="1.5" transform="rotate(135 34.6274 32.5059)" fill="black"/>
</svg>
</div>
</div>
<div style="display: flex; flex-direction: column; height: 100%;">
<div class="modal_title_text" style="text-align: center;">
<div>Please select the size of the clothing</div>
</div>
<div class="content">
<div class="downItem" v-for="item,key in loadList">
<div class="title">{{ key }}</div>
<div class="keyList">
<div class="item" :class="{active:(DSizeItem == select?.size && key == select?.sizeType)}" v-for="DSizeItem in item" @click="downloadIamge(DSizeItem,key)">
{{ DSizeItem }}
</div>
</div>
</div>
</div>
<div style="width: 100%; display: flex; margin-top: auto;">
<div class="gallery_btn" :class="{btnActive:!select.sizeType}" @click="setDown" style="width: 13rem; margin-left: auto;">Download</div>
</div>
</div>
<div class="mark_loading" v-show="isShowMark">
<a-spin size="large" />
</div>
</a-modal>
</div>
</template>
<script lang="ts">
import { defineComponent,computed,ref,provide,nextTick,createVNode,toRefs, reactive, onMounted} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Https } from "@/tool/https";
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
import { getCookie } from "@/tool/cookie";
import { Modal,message } from 'ant-design-vue';
import {getUploadUrl,rgbToHsv,isMoible} from '@/tool/util'
export default defineComponent({
components:{
},
props:{
},
emits:['submitBrandAdd'],
setup(props,{emit}) {
const {t} = useI18n()
const store = useStore();
const data = reactive({
threeDown:false,
isShowMark:Object(''),
loadList:{
aa:[{a:1}],
bb:[{a:1}],
cc:[{a:1}]
},
select:{
sizeType:'',
size:'',
threeDSimpleId:-1,
},
})
const dataDom = reactive({
})
const cancelDsign = ()=>{
data.threeDown = false;
data.select.sizeType = ''
data.select.size = ''
}
const getDowList = (id:any)=>{
data.isShowMark = true
let value = {
threeDSimpleId:id,
// threeDSimpleId:id,
}
Https.axiosPost(Https.httpUrls.getThreeDSize,{},{params:value}).then((res:any)=>{
data.loadList = res.sizeTypeMap
data.isShowMark = false
data.select.threeDSimpleId = id
}).catch((err:any)=>{
data.isShowMark = false
})
}
const setDown = ()=>{
data.isShowMark = true
let value = {
...data.select,
}
let config = {
params:value
}
Https.axiosGet(Https.httpUrls.downloadZip,config).then((res:any)=>{
let a = document.createElement('a');
a.href = res;
a.download = 'model.zip'; // 设置下载的文件名
a.click(); // 触发下载
URL.revokeObjectURL(a.href); // 释放 URL 对象
data.isShowMark = false
}).catch((err:any)=>{
data.isShowMark = false
})
}
const openDown = (id:any)=>{
data.threeDown = true;
getDowList(id)
}
const downloadIamge = (DSizeItem:any,key:any)=>{
data.select.sizeType = key
data.select.size = DSizeItem
}
return{
...toRefs(dataDom),
...toRefs(data),
cancelDsign,
setDown,
openDown,
downloadIamge,
}
},
provide() {
return {
}
},
})
</script>
<style lang="less" scoped>
:deep(.ant-modal-mask){
background: rgba(0,0,0,0.3);
}
.threeDownPage{
width: 100%;
height: 100%;
position: relative;
:deep(.generalModel){
.gallery_btn{
&.btnActive{
pointer-events: none;
background: rgba(0, 0, 0, .4);
border: none;
}
}
.content{
display: flex;
flex-direction: column;
flex: 1;
padding-left: 6rem;
// width: 55rem;
// margin: 0 auto;
> .downItem{
display: flex;
font-size: 2rem;
font-weight: 600;
margin-bottom: 1rem;
text-align: center;
align-items:center;
> .title{
margin-right: 1.5rem;
width: 3.5rem;
text-align: right;
white-space: nowrap;
}
> .keyList{
display: flex;
flex-wrap: wrap;
> .item{
font-weight: 300;
width: 5.5rem;
height: 5.5rem;
border: 1px solid #000;
display: flex;
align-items: center;
justify-content: center;
transition: all .3s;
&.active{
background: #000;
color: #fff;
}
&:hover{
background: #000;
color: #fff;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,372 @@
<template>
<div class="patternMaking3D">
<div class="selectModel">
<div class="heard selectList">
<div :class="{active:libraryOrModel == 'model'}" @click="setLibraryOrModel('model')">Clothing</div>
<div :class="{active:libraryOrModel == 'print'}" @click="setLibraryOrModel('print')">Print</div>
</div>
<div class="list" v-show="libraryOrModel == 'model'" v-if="maskShow">
<div v-for="item in modelList" class="modelItem" :class="{active:item.id == selectModel.id}" @click="setSelectModel(item)">
<img :src="item.url" alt="">
</div>
<div v-show="!isNoData" class="material_content_list_loding">
<span class="page_loading" v-show="!isShowLoading" v-observe></span>
<span v-show="isShowLoading">
<a-spin size="large" />
</span>
</div>
</div>
<div class="list printList" v-show="libraryOrModel == 'print'">
<div v-for="item in printList" class="modelItem" :class="{active:item.id == selectModel.id}" @click="setMaterial(item)">
<img :src="item.url || item.imgUrl" alt="">
</div>
</div>
</div>
<div class="model" v-show="selectModel.id != -1">
<div class="heard">
<div class="text">Technical sketch</div>
<div class="switch">
front
<a-switch v-model:checked="isFront" />
back
</div>
</div>
<div class="modelBox">
<div v-show="!imgOrThree" class="img">
<img v-show="!isFront" :src="selectModel.threeDLayoutList?.[0]?.url" alt="">
<img v-show="isFront" :src="selectModel.threeDLayoutList?.[1]?.url" alt="">
</div>
<threeBox v-if="imgOrThree" ref="threeBox"></threeBox>
</div>
<div class="gallery_btn" v-show="!imgOrThree" @click="setImgOrThree(true)">3D view</div>
<div class="gallery_btn" v-show="imgOrThree" @click="setImgOrThree(false)">Img view</div>
</div>
<div class="flatPatterm" v-show="selectModel.id != -1">
<div class="heard">Flat pattern</div>
<div class="modelBox">
<div class="img">
<img :src="selectModel.threeDPatternLayoutUrl" alt="">
<div class="btn">
<i class="fi fi-bs-expand-arrows-alt" @click.stop="openScaleImage()"></i>
</div>
</div>
</div>
<div class="gallery_btn" @click="openDown()">Download</div>
</div>
<div class="download">
<download ref="download"></download>
</div>
<scaleImage ref="scaleImage"></scaleImage>
</div>
</template>
<script lang="ts">
import { defineComponent,computed,ref,provide,nextTick,createVNode,toRefs, reactive, watch} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Https } from "@/tool/https";
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
import threeBox from "./three.vue"
import download from "./download.vue"
import scaleImage from "@/component/HomePage/scaleImage.vue";
export default defineComponent({
components:{
threeBox,download,scaleImage
},
props:{
},
emits:[],
setup(props,{emit}) {
const store = useStore();
const data = reactive({
selectModel:{
id:-1,
} as any,
// printCatecoryList:computed(()=>{
// return store.state.UserHabit.printType
// }),
selectObject:computed(()=>store.state.Workspace.probjects),//选择的项目
modelList:[] as any,
printList:computed(()=>store.state.UploadFilesModule.allBoardData.printboardFiles),
isShowMark:false,
isNoData:false,
isShowLoading:false,
currentPage:1,
pageSize:10,
imgOrThree:false,
isFront:false,
maskShow:false,
libraryOrModel:'model'
})
watch(()=>data.selectObject.sex,(newVal)=>{
data.modelList = []
data.currentPage = 0,
data.isNoData = false
data.isShowMark = false
})
const setSelectModel = (item:any)=>{
data.isShowMark = true
const value = {
threeDSimpleId:item.threeDSimpleId,
}
Https.axiosPost(Https.httpUrls.getLayoutDetail,{},{params:value}).then((res:any)=>{
data.selectModel = res
data.selectModel.id = item.id
data.isShowMark = false
data.imgOrThree = false
// if(data.imgOrThree){
// dataDom.threeBox.openSetData()
// }
let stateData = {
threeDsimpleId:item.id
}
store.commit('setPatternMaking3D',stateData)
}).catch((err:any)=>{
data.isShowMark = false
})
}
const dataDom = reactive({
threeBox:null as any,
download:null as any,
scaleImage:null as any,
})
const openSetData = ()=>{
nextTick(()=>{
let id = store.state.HomeStoreModule.patternMaking3D.threeDsimpleId
if(id && data.selectModel.id == -1)setSelectModel({threeDSimpleId:id})
})
setTimeout(()=>{
data.maskShow = true
},500)
}
const getModelList = ()=>{
if(data.isShowMark && !data.isNoData)return
let value = {
page: data.currentPage,
size:data.pageSize,
sex:data.selectObject.sex,
}
data.isShowLoading = true
Https.axiosPost(Https.httpUrls.threeDPage,value).then(
(rv: any) => {
if(rv.content.length == 0)data.isNoData = true
data.modelList.push(...rv.content)
data.isShowLoading = false
}
).catch((res)=>{
data.isNoData = true
});
}
const setImgOrThree = (boolean:boolean)=>{
data.imgOrThree = boolean
if(boolean){
nextTick(()=>{
dataDom.threeBox.openSetData(data.selectModel)
// dataDom.threeBox.openSetData(data.selectModel.threeDPatternLayoutUrl)
})
}
}
const downList = ()=>{
let value = {
}
Https.axiosGet(Https.httpUrls.threeDPage,value).then(
)
}
const openDown = ()=>{
dataDom.download.openDown(data.selectModel.id)
}
const setLibraryOrModel = (str:any)=>{
if(str == 'print' && data.selectModel.id == -1)return
data.libraryOrModel = str
}
const setMaterial = (item:any)=>{
dataDom.threeBox.addMaterial(item)
}
const openScaleImage = ()=>{
let scaleImage:any = dataDom.scaleImage
scaleImage.isLike = false
scaleImage.init([{imgUrl:data.selectModel.threeDPatternLayoutUrl}],0)
}
return{
...toRefs(dataDom),
...toRefs(data),
setSelectModel,
openSetData,
getModelList,
setImgOrThree,
openDown,
setLibraryOrModel,
setMaterial,
openScaleImage
}
},
directives:{
observe:{
mounted (el,binding) {
// console.log(binding.instance);
let this_:any = binding.instance
this_.isShowMark = false
this_.isNoData = false
let parentDom = el.parentNode
this_.getModelList()
new IntersectionObserver(
(entries, observer) => {
// 如果不是相交,则直接返回
// console.log(entries[0]);
if (!entries[0].intersectionRatio) return;
this_.currentPage += 1
this_.getModelList()
},
).observe(el);
},
},
},
provide() {
return {
}
},
})
</script>
<style lang="less" scoped>
.patternMaking3D{
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
display: flex;
font-size: 1.4rem;
> .selectModel,> .model,>.flatPatterm{
padding: 3rem 2rem;
display: flex;
flex-direction: column;
> .heard{
font-size: 2rem;
font-weight: 600;
display: flex;
justify-content: space-between;
width: 100%;
&.selectList{
justify-content: flex-start;
> div{
position: relative;
margin-right: 2rem;
margin-bottom: 2rem;
}
> div::before{
position: absolute;
content: "";
display: block;
background: #000;
height: calc(.4rem*1.2);
left: 50%;
transform: translateX(-50%);
bottom: -.5rem;
width: 0px;
transition: 0.3s all;
}
> .active {
color: #000;
font-weight: 600;
}
> .active::before {
width: 100%;
}
}
> .switch{
font-weight: 400;
}
}
}
> .selectModel{
width: 40%;
// width: 70rem;
flex-shrink: 0;
height: 100%;
background: #f7f8fa;
border-radius: 3rem;
> .list{
flex: 1;
display: flex;
flex-wrap: wrap;
overflow-y: auto;
align-content: flex-start;
&.printList{
> .modelItem{
aspect-ratio: 1 / 1;
>img{
padding: 0;
}
}
}
> .modelItem{
width: calc(100% / 4 - 1rem);
margin: .5rem;
aspect-ratio: 1 / 1.2;
// height: 10rem;
border-radius: 2rem;
border: 2px solid #D4D4D4;
cursor: pointer;
> img{
width: 100%;
height: 100%;
object-fit: contain;
padding: 1rem;
}
&.active{
border: 2px solid #000;
}
}
}
}
> .model ,> .flatPatterm{
flex: 1;
overflow: hidden;
align-items: center;
> .modelBox{
width: 100%;
// height: 20rem;
// height: 75rem;
flex: 1;
padding: 1rem 0;
overflow: hidden;
margin: auto;
>.img{
width: 100%;
height: 100%;
position: relative;
> .btn{
position: absolute;
right: 2rem;
top: 2rem;
> i{
cursor: pointer;
}
}
img{
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
}
.material_content_list_loding{
text-align: center;
height: 50px;
width: 100%;
.page_loading{
display: block;
width: 50px;
height: 50px;
margin: 0 auto;
}
}
.download{
width: 0;
height: 0;
}
}
</style>

View File

@@ -0,0 +1,552 @@
<template>
<div class="three">
<div class="parameter">
<label>
<span>scaleX:</span>
<a-slider class="system_silder"
v-model:value="repeat.x"
:tooltipVisible="false"
@change="changeRepeat"
:max="6"
:step="0.001"
:min="0.002"
>
</a-slider>
</label>
<label>
<span>scaleY:</span>
<a-slider class="system_silder"
v-model:value="repeat.y"
:tooltipVisible="false"
@change="changeRepeat"
:max="6"
:min="0.002"
:step="0.001"
>
</a-slider>
</label>
<i class="fi fi-br-link" :class="{'fi-br-link':isLock,'fi-bs-link-slash':!isLock}" @click="setLock"></i>
</div>
<div class="model" ref="threeDom">
</div>
<div class="load" v-show="load.state">
<i class="fi fi-rr-cubes"></i>
<div class="text">Load...</div>
<div class="loadBox">
<div class="schedule" :style="{width:load.progress+'%'}"></div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent,computed,shallowRef,provide,nextTick,onMounted,toRefs, reactive, onBeforeUnmount} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { Https } from "@/tool/https";
import { useStore } from "vuex";
import { useI18n } from 'vue-i18n'
// @ts-ignore
import * as THREE from 'three';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import gsap from 'gsap';
import { env } from 'echarts';
export default defineComponent({
components:{
},
props:{
},
emits:[],
setup(props,{emit}) {
const store = useStore();
const data = reactive({
scene:shallowRef() as any,//场景
group:shallowRef() as any,//组
camera:shallowRef() as any,//相机
renderer:shallowRef() as any,//渲染器
pointLight:shallowRef() as any,//光
controls:shallowRef() as any,//监听鼠标、键盘事件
textureLoader:shallowRef() as any,//材质
load:{
state:false,
progress:0 as any,
},
repeat:{
x:1,
y:1,
},
animationId:null as any,
isLock:false,
})
const dataDom = reactive({
threeDom:null as any,
})
const dataTime = reactive({
updataRepeat:null as any
})
const init = ()=>{
data.scene = new THREE.Scene();
data.group = new THREE.Group()
data.scene.add(data.group)
//创建相机对象
// this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
data.camera = new THREE.PerspectiveCamera(45, dataDom.threeDom.offsetWidth / dataDom.threeDom.offsetHeight, 0.1, 10000);
data.camera.position.set(0, 90, 6); //设置相机位置
data.camera.lookAt(data.scene.position); //设置相机方向(指向的场景对象)
/**
* 创建渲染器对象
*/
let width = dataDom.threeDom.offsetWidth; //窗口宽度
let height = dataDom.threeDom.offsetHeight; //窗口高度
data.renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true,//深度缓存 防止模型闪烁重影
});
// data.renderer.outpuEncoding = THREE?.RGBEEncoding//设置输出颜色编码格式
data.renderer.toneMapping = THREE.ACESFilmicToneMapping;//设置色调
data.renderer.toneMappingExposure = 1.3;
data.renderer.shadowMap.enabled = true;
data.renderer.setPixelRatio(window.devicePixelRatio);
data.renderer.setSize(width, height); //设置渲染区域尺寸
data.renderer.setClearColor(0xffffff, 1); //设置背景颜色
dataDom.threeDom.innerHTML = '';
dataDom.threeDom.appendChild(data.renderer.domElement);
// 设置渲染器大小
//环境光
let ambient = new THREE.AmbientLight(0xffffff,.8);
data.scene.add(ambient);
data.controls = new OrbitControls(data.camera,data.renderer.domElement)//监听鼠标、键盘事件;
// data.controls.minDistance = 500; // 设置相机与焦点的最小距离
// data.controls.maxDistance = 4000; // 设置相机与焦点的最大距离
data.controls.mouseButtons = {
// LEFT:THREE.MOUSE.PAN, // 左键 拖动(默认旋转ROTATE)
LEFT:THREE.MOUSE.ROTATE, // 左键 拖动(默认旋转ROTATE)
MIDDLE:THREE.MOUSE.DOLLY, // 滑轮 缩放
RIGHT:THREE.MOUSE.PAN // 右键 旋转默认拖动PAN
// RIGHT:THREE.MOUSE.ROTAafTE // 右键 旋转默认拖动PAN
}
/**
* 光源设置
*/
//点光源
/**
* AmbientLight 环境光
PointLight 点光源
DirectionalLight 平行光,比如太阳光
SpotLight 聚光源
*/
data.pointLight = new THREE.DirectionalLight(0xffffff,.5);
data.pointLight.intensity = 1.2
data.pointLight.castShadow = true//开启阴影
data.pointLight.shadow.mapSize = new THREE.Vector2(width, height)
data.scene.add(data.pointLight); //点光源添加到场景中
// data.pointLight.position.set(400, 200, 300); //点光源位置
data.pointLight.position.y = 400;
data.pointLight.position.z = 200;
data.pointLight.position.x = 200;
let floorGeometry = new THREE.PlaneGeometry(5000, 3000)//地板大小
let floorMaterial = new THREE.MeshPhongMaterial({ color: "#7e7ab0", })
let floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
floorMesh.rotation.x = -0.5 * Math.PI;
floorMesh.receiveShadow = true;
floorMesh.position.y = -0.001;
// data.scene.add(floorMesh);
const textureLoader = new THREE.TextureLoader();
// const texture = textureLoader.load('/3dModel/sketch-thick.jpg');
data.scene.background = new THREE.Color("#fff");
// data.scene.background = texture;
let openModel = (event:any)=>{
let mouse=new THREE.Vector2();
let raycaster=new THREE.Raycaster();
mouse.x=(event.clientX/window.innerWidth)*2-1;
mouse.y=-(event.clientY/window.innerHeight)*2+1;
raycaster.setFromCamera(mouse, data.camera);
let intersects = raycaster.intersectObjects(data.scene.children);
return intersects
}
dataDom.threeDom.ondblclick = (event:any)=>{
let intersects = openModel(event);
if(!intersects || intersects.length<=0) return
const bbox = new THREE.Box3().setFromObject(intersects[0].object);
const size = new THREE.Vector3();
let target2 = bbox.getCenter(size);//获取选中包围起来后的中心坐标
animateCamera(data.camera.position,intersects[0].point,data.controls.target,target2)
}
let isTweening = false;
function animateCamera(current1:any, target1:any, current2:any, target2:any){
if (isTweening) return
isTweening = true
let options = {
x1: current1.x, // 相机当前位置x
y1: current1.y, // 相机当前位置y
z1: current1.z, // 相机当前位置z
x2: current2.x, // 控制当前的中心点x
y2: current2.y, // 控制当前的中心点y
// z2: current2.z // 控制当前的中心点z
}
gsap.to(options,{
x1: 0, // 新的相机位置x
y1: target2.y, // 新的相机位置y
z1: 2500, // 新的相机位置z
x2: 0, // 新的控制中心点位置x
y2: target2.y, // 新的控制中心点位置x
duration:1,
ease:'linear',
onUpdate:()=>{
data.camera.position.x = options.x1;
data.camera.position.y = options.y1;
data.camera.position.z = options.z1;
data.controls.target.x = options.x2;
data.controls.target.y = options.y2;
// data.controls.target.z = object.z2;
data.controls.update();
},
onComplete:()=>{
isTweening = false
}
// z2: target2.z // 新的控制中心点位置x
})
}
// let setHighlight = (obj:any)=>{
// outlinePass.selectedObjects = obj;
// }
data.controls.enableDamping = true;
let animate = ()=>{
data.animationId = requestAnimationFrame(animate);
// data.renderer.render(data.scene, data.camera);
// model.rotation.x += 0.01; //旋转物体
var vector = data.camera.position.clone()
data.controls.update();
data.renderer.render(data.scene, data.camera);
// point.position.set(vector.x,vector.y,vector.z);
// group.rotation.y += 0.01;
// composer.render();
};
animate();
}
const setModel = async (url:any)=>{
clearModel()
await addModel(url)
// addMaterial()
}
const addMaterial =async (item:any)=>{
//添加图片材质
let url = item.url || item.url || item.imgUrl
data.load.state = true
let textureLoader = new THREE.TextureLoader()
await textureLoader.load(url, // 图片放在public/textures目录下
(texture:any) => {
data.textureLoader = texture
// 3. 配置纹理参数
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// texture.wrapS = THREE.ClampToEdgeWrapping; // 水平方向不重复
// texture.wrapT = THREE.ClampToEdgeWrapping; // 垂直方向不重复
// texture.repeat.set(1, 1); // 纹理重复次数
texture.anisotropy = 32; // 提高纹理清晰度
data.group?.traverse((child:any) => {
if (child.isMesh) {
console.log(child.name)
// 5. 创建新材质(根据需求选择材质类型)
const textureWidth = texture.image.width;
const textureHeight = texture.image.height;
const box = new THREE.Box3().setFromObject(child);
const modelWidth = box.getSize(new THREE.Vector3()).x;
const modelHeight = box.getSize(new THREE.Vector3()).y;
data.repeat.x = 2 - modelWidth / textureWidth;
data.repeat.y = 2 - modelHeight / textureHeight;
let patternMaking3D = store.state.HomeStoreModule.patternMaking3D
if(patternMaking3D.x)data.repeat.x = patternMaking3D.x
if(patternMaking3D.y)data.repeat.y = patternMaking3D.y
// texture.repeat.set(1, 1); // 纹理重复次数
texture.repeat.set(2 - data.repeat.x, 2 - data.repeat.y); // 纹理重复次数
const newMaterial = new THREE.MeshStandardMaterial({
map: texture, // 基础颜色贴图
roughness: 0.7, // 表面粗糙度 (0-1)
metalness: .2, // 金属质感 (0-1)
side: THREE.DoubleSide // 双面渲染
});
// 7. 如果需要单独控制某些子模型的UV
if (child.geometry.attributes.uv) {
const uvs = child.geometry.attributes.uv.array;
// 计算UV边界
let minU = Infinity, maxU = -Infinity;
let minV = Infinity, maxV = -Infinity;
for (let i = 0; i < uvs.length; i += 2) {
minU = Math.min(minU, uvs[i]);
maxU = Math.max(maxU, uvs[i]);
minV = Math.min(minV, uvs[i+1]);
maxV = Math.max(maxV, uvs[i+1]);
}
const uvWidth = maxU - minU;
const uvHeight = maxV - minV;
// 仅对非小UV区域设置材质
if (!(uvWidth < 1.2 || uvHeight < 1.2)) {
const newMaterial = new THREE.MeshStandardMaterial({
map: texture,
roughness: 0.7,
metalness: 0.2,
side: THREE.DoubleSide
});
child.material = newMaterial;
}else{
// child.material = new THREE.MeshStandardMaterial({
// transparent: true,
// opacity: 0, // 完全透明
// side: THREE.DoubleSide
// });
}
child.geometry.attributes.uv.needsUpdate = true;
}
}
data.load.state = false
},(xhr:any) => { // 加载进度回调
const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2);
data.load.progress = percent
// updateProgressBar(Number(percent));
},(error:any) => {
console.error('纹理加载失败:', error);
data.load.state = false
});
let value = {
collectionElementId:item.id,
x:data.repeat.x,
y:data.repeat.y,
}
store.commit('setPatternMaking3D',value)
})
}
const addModel = async (url:any)=>{
await new Promise((resolve, reject) => {
var fbxLoader = new GLTFLoader();
let drac = new DRACOLoader()
drac.setDecoderPath('/draco/')
fbxLoader.setDRACOLoader(drac)
// fbxLoader.load('/3dModel/222/1111.glb',
fbxLoader.load(url,
(obj:any) => {
let scene = obj.scene;
var box = new THREE.Box3().setFromObject(scene);
var center = box.getCenter(new THREE.Vector3());
data.controls.target.copy(center);
// data.controls.autoRotate = true
data.camera.position.y = center.y;
data.camera.position.z = 1000;
data.pointLight.position.y = 250;
data.pointLight.position.z = 1250;
data.group.add(scene);
resolve('')
},(xhr:any) => { // 加载进度回调
const percent = xhr.total == 0?100:(xhr.loaded / xhr.total * 100).toFixed(2);
data.load.progress = percent
// updateProgressBar(Number(percent));
},(error:any) => { // 加载失败回调
console.error('模型加载失败:', error);
reject('')
})
})
}
const clearModel = ()=>{
const oldGroup:any = data.group;
data.group = new THREE.Group();
data.scene.add(data.group);
data.scene.remove(oldGroup);
}
// const loadThree = ()=>{
// init()
// }
const getModelUrl = (value:any)=>{
return new Promise((resolve, reject) => {
// Https.axiosGet(Https.httpUrls.getThreeDGlb,{params:{threeDSimpleId:value.id},env:{binary:true}}).then((rv)=>{
// //二进制流转本地路径
// console.log(rv)
// resolve(rv)
// }).catch(()=>{
// reject('')
// })
// //fetch get请求携带token
// fetch("https://develop.api.aida.com.hk/api/project/getThreeDGlb?threeDSimpleId=1", {
// headers:{
// authorization:'Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI4OCIsInN1YiI6IntcImNvdW50cnlcIjpcIkNoaW5hXCIsXCJpZFwiOjg4LFwibGFuZ3VhZ2VcIjpcIkVOR0xJU0hcIixcInVzZXJuYW1lXCI6XCJzaGJcIn0iLCJpYXQiOjE3NDMzNDkwNjQsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE3NTE5ODkwNjR9.gmL0JufYy9wd23qCY-ibwhgpXZ2X68WAiHSeC99I4x7cipWyxLaQmuIBk2SJSdWBm0tTN2Mx-etXO9a7MtQmpw',
// }
// }).then(res => {
// return res.blob();
// }).then((res) => {
// var url = URL.createObjectURL(res);
// console.log(url, res)
// resolve(url)
// }).catch(err => {
// console.log(err);
// })
resolve(value.threeDSimpleUrl)
})
}
const openSetData = async (value:any)=>{
if(!data.scene){
init()
}
data.load.state = true
const modeUrl = await getModelUrl(value)
await setModel(modeUrl)
let patternMaking3D = store.state.HomeStoreModule.patternMaking3D
if(patternMaking3D.printMinioUrl)await addMaterial({url:patternMaking3D.printMinioUrl})
data.load.state = false
}
const changeRepeat = (e:any)=>{
if(data.isLock)data.repeat.x = e
if(data.isLock)data.repeat.y = e
clearTimeout(dataTime.updataRepeat)
dataTime.updataRepeat = setTimeout(()=>{
data.repeat.x = data.repeat.x == 6 ? 5.999 : data.repeat.x
data.repeat.y = data.repeat.y == 6 ? 5.999 : data.repeat.y
data.textureLoader.repeat.set(6 - data.repeat.x,6 - data.repeat.y); // 纹理重复次数
let value = {
x:data.repeat.x,
y:data.repeat.y,
}
store.commit('setPatternMaking3D',value)
},1000)
}
const setLock = ()=>{
data.isLock = !data.isLock
}
onMounted(()=>{
})
onBeforeUnmount(()=>{
if(data.animationId){
cancelAnimationFrame(data.animationId);
data.animationId = null;
}
data.scene.traverse((child:any) => {
if (child.material) {
child.material.dispose();
}
if (child.geometry) {
child.geometry.dispose();
}
child = null;
});
data.renderer.forceContextLoss();
data.renderer.dispose();
data.scene.clear();
data.scene = null;
data.camera = null;
data.controls = null;
data.renderer.domElement = null;
data.renderer = null;
})
return{
...toRefs(dataDom),
...toRefs(data),
openSetData,
addMaterial,
changeRepeat,
setLock,
}
},
provide() {
return {
}
},
})
</script>
<style lang="less" scoped>
.three{
width: 100%;
height: 100%;
position: relative;
flex: 1;
overflow: hidden;
> .parameter{
display: flex;
align-items: center;
> label{
display: flex;
align-items: center;
flex: 1;
padding-right: 2rem;
// &:not(:last-child){
// margin-right: 3rem;
// }
> span{
font-size: 2rem;
margin-right: 1rem;
}
}
> i{
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.active{
opacity: .7;
}
}
}
> .model{
width: 100%;
height: 100%;
}
> .load{
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, .2);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: #fff;
> i{
font-size: 3rem;
}
> .loadBox{
width: 15rem;
height: 1rem;
border-radius: 1rem;
background: #fff;
overflow: hidden;
> .schedule{
height: 100%;
background: greenyellow;
}
}
}
}
</style>