diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool copy.ts b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool copy.ts
new file mode 100644
index 0000000..0232b2e
--- /dev/null
+++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool copy.ts
@@ -0,0 +1,351 @@
+import * as THREE from 'three';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
+import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
+import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
+import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
+import hdri from '@/assets/images/three/hdri.hdr'
+
+interface ModelInfo {
+ box: THREE.Box3;
+ center: THREE.Vector3;
+ size: THREE.Vector3;
+ maxSize: number;
+}
+const CONFIG = {
+ hdriIntensity: 7.4,
+ exposureBase: 0.92,
+ backlightBoost: { min: 2.1, max: 4.8 }, // 背光增强系数
+ hdriUrl: hdri,
+};
+export class ThreeManager {
+ threeDom: HTMLElement;
+ scene: THREE.Scene;//场景对象
+ camera: THREE.PerspectiveCamera;//相机对象
+ renderer: THREE.WebGLRenderer;//渲染器对象
+ controls: OrbitControls;//轨道控制器对象
+
+
+ pointLight: THREE.AmbientLight;//环境光对象
+ studioLights: any;//工作室光对象数组
+ v1: THREE.Vector3;//相机前向向量
+ camDir: THREE.Vector3;//相机前向向量
+ camForward: THREE.Vector3;//相机前向向量
+ camToTarget: THREE.Vector3;//相机目标向量
+
+ currentModel: any;//当前模型对象
+ animate: any;//动画对象
+
+ modelInfo: ModelInfo;//模型信息对象
+ defaultSoftboxPositions: Array<{ x: number; y: number; z: number; intensity: number; w: number; h: number }> = [
+ { x: 0, y: 5.8, z: 3.5, intensity: 3.5, w: 6, h: 4.8 }, // 主光
+ { x: 0, y: 2.8, z: 7.5, intensity: 2.2, w: 5, h: 4 }, // 前光
+ { x: 0, y: 2.8, z: -7.5, intensity: 6.5, w: 5, h: 4 }, // 背光
+ { x: -7.5, y: 2.8, z: 0, intensity: 10.8, w: 5, h: 4 }, // 侧光
+ { x: 7.5, y: 2.8, z: 0, intensity: 2.0, w: 5, h: 4 }, // 侧光
+ { x: 0, y: -2.2, z: 3, intensity: 0.8, w: 9, h: 4 } // 地面反光板
+ ];
+ constructor(threeDom: HTMLElement,config:any = {}){
+
+ this.threeDom = threeDom;
+ this.studioLights = []
+ //创建场景
+ this.scene = new THREE.Scene();
+ this.scene.background = new THREE.Color(0xffffff);
+ //创建相机
+ this.camera = new THREE.PerspectiveCamera(45, threeDom.offsetWidth / threeDom.offsetHeight, 0.1, 10000);
+ this.camera.position.set(0, 1.5, 6); //设置相机位置
+
+ this.v1 = new THREE.Vector3();
+ this.camDir = new THREE.Vector3();
+ this.camForward = new THREE.Vector3();
+ this.camToTarget = new THREE.Vector3();
+ //设置渲染器
+ this.renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance", preserveDrawingBuffer: true });
+ //设置环境光
+ this.scene.add(new THREE.AmbientLight(0xffffff, 0.15));
+
+ // 关键优化:物理光照与色彩管理
+ this.renderer.outputEncoding = THREE.sRGBEncoding;
+ this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
+ this.renderer.toneMappingExposure = CONFIG.exposureBase;
+ this.renderer.physicallyCorrectLights = true; // 开启物理光照模式,光衰减更真实
+ this.renderer.setPixelRatio(window.devicePixelRatio);
+ this.renderer.setSize(threeDom.offsetWidth, threeDom.offsetHeight); //设置渲染区域尺寸
+ this.renderer.setClearColor(0xffffff, 1); //设置背景颜色
+ threeDom.innerHTML = '';
+ threeDom.appendChild(this.renderer.domElement);
+
+ RectAreaLightUniformsLib.init();
+ //设置轨道控制器
+ this.controls = new OrbitControls(this.camera,this.renderer.domElement)//监听鼠标、键盘事件;
+ // controls.minDistance = 500; // 设置相机与焦点的最小距离
+ // controls.maxDistance = 4000; // 设置相机与焦点的最大距离
+ this.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)
+ }
+ this.controls.enableDamping = true;
+
+ }
+ /**
+ * 根据模型大小计算自适应配置
+ */
+ calculateAdaptiveConfig(modelInfo: ModelInfo) {
+ const { size, maxSize, center } = modelInfo;
+ // 基础距离系数(可根据需要调整)
+ let distanceFactor = 1.5;
+ // 根据模型形状调整距离系数
+ const aspectRatio = size.x / size.y;
+ if (aspectRatio > 2) {
+ // 扁平模型,拉远一点
+ distanceFactor = 3.0;
+ } else if (aspectRatio < 0.5) {
+ // 高瘦模型,稍微拉近
+ distanceFactor = 2.0;
+ }
+ // 计算相机距离
+ const fov = this.camera.fov * (Math.PI / 180);
+ const cameraDistance = (maxSize / 2) / Math.tan(fov / 2) * distanceFactor;
+ // 灯光缩放系数
+ const lightScale = Math.max(0.5, Math.min(2.0, maxSize / 2.5));
+ // 强度缩放系数(模型越大,灯光需要越强)
+ const intensityScale = Math.max(0.6, Math.min(2.5, maxSize / 2));
+
+ console.log('自适应配置:', {
+ 相机距离: cameraDistance,
+ 灯光缩放: lightScale,
+ 强度缩放: intensityScale,
+ 目标中心: center
+ });
+
+ return {
+ cameraDistance,
+ lightScale,
+ intensityScale,
+ targetCenter: center.clone()
+ };
+ }
+ /**
+ * 获取模型信息(包围盒、中心点、尺寸等)
+ */
+ getModelInfo(model: THREE.Object3D): ModelInfo {
+ const box = new THREE.Box3().setFromObject(model);
+ const center = box.getCenter(new THREE.Vector3());
+ const size = box.getSize(new THREE.Vector3());
+ const maxSize = Math.max(size.x, size.y, size.z);
+
+ console.log('模型信息:', {
+ 中心点: center,
+ 尺寸: { x: size.x, y: size.y, z: size.z },
+ 最大尺寸: maxSize
+ });
+
+ return { box, center, size, maxSize };
+ }
+ /**
+ * 根据模型大小自适应创建柔光箱灯光
+ */
+ createAdaptiveSoftboxes(modelInfo: ModelInfo) {
+ const { lightScale, intensityScale, targetCenter } = this.calculateAdaptiveConfig(modelInfo);
+ // 清除现有灯光
+ this.studioLights.forEach(item => {
+ this.scene.remove(item.light);
+ });
+ this.studioLights = [];
+
+ // 根据模型大小创建自适应灯光
+ this.defaultSoftboxPositions.forEach(pos => {
+ const scaledX = pos.x * lightScale;
+ const scaledY = pos.y * lightScale;
+ const scaledZ = pos.z * lightScale;
+ const scaledW = pos.w * lightScale;
+ const scaledH = pos.h * lightScale;
+ const scaledIntensity = pos.intensity * intensityScale;
+
+ const light = new THREE.RectAreaLight(0xffffff, scaledIntensity, scaledW, scaledH);
+ light.position.set(
+ targetCenter.x + scaledX,
+ targetCenter.y + scaledY,
+ targetCenter.z + scaledZ
+ );
+ light.lookAt(targetCenter);
+ this.scene.add(light);
+
+ this.studioLights.push({
+ light,
+ offset: new THREE.Vector3(scaledX, scaledY, scaledZ),
+ baseIntensity: scaledIntensity
+ });
+ });
+ console.log('自适应灯光已创建,缩放系数:', lightScale, '强度系数:', intensityScale);
+ }
+ /**
+ * 根据模型大小调整相机位置
+ */
+ adjustCameraToModel(modelInfo: ModelInfo) {
+ const { cameraDistance, targetCenter } = this.calculateAdaptiveConfig(modelInfo);
+ // 正面俯视角度:相机在模型正前方,稍高于模型中心
+ // X 轴:模型中心(正面视角,不左右偏移)
+ // Y 轴:相机高度 = 模型中心高度 + 模型高度的 0.3 倍(俯视效果)
+ // Z 轴:相机距离 = 根据模型大小计算的距离
+ const x = targetCenter.x;
+ const y = targetCenter.y + modelInfo.size.y * 0.3; // 相机高度为模型中心的 0.3 倍
+ const z = targetCenter.z + cameraDistance;
+
+ this.camera.position.set(x, y, z);
+ this.controls.target.copy(targetCenter);
+ this.controls.update();
+ }
+
+
+ createSoftbox(x, y, z, intensity, w = 5, h = 4) {
+ const light = new THREE.RectAreaLight(0xffffff, intensity, w, h);
+ light.position.set(x, y, z);
+ light.lookAt(0, 0, 0);
+ this.scene.add(light);
+ // 存储 offset 向量副本,避免后续重复创建
+ this.studioLights.push({
+ light,
+ offset: new THREE.Vector3(x, y, z),
+ baseIntensity: intensity
+ });
+ }
+ async setHDRI(){
+ const rgbeLoader = new RGBELoader()
+ await rgbeLoader.load(CONFIG.hdriUrl, (texture)=>{
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+ const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
+ const envMap = pmremGenerator.fromEquirectangular(texture).texture;
+
+ this.scene.environment = envMap;
+ this.scene.environmentIntensity = CONFIG.hdriIntensity;
+
+ pmremGenerator.dispose();
+ texture.dispose();
+ console.log('✅ HDRI Loaded');
+ });
+ this.createSoftbox(0, 5.8, 3.5, 3.5, 6, 4.8); // 主光
+ this.createSoftbox(0, 2.8, 7.5, 2.2); // 前光
+ this.createSoftbox(0, 2.8, -7.5, 6.5); // 背光 (增强)
+ this.createSoftbox(-7.5, 2.8, 0, 10.8); // 侧光
+ this.createSoftbox(7.5, 2.8, 0, 2.0);
+ this.createSoftbox(0, -2.2, 3, 0.8, 9, 4); // 地面反光板
+ }
+ // 更新工作室光位置
+ updateStudioLighting(){
+ this.camera.getWorldDirection(this.camDir);
+ this.studioLights.forEach(item => {
+ // 使用 applyQuaternion 同步灯光位置
+ this.v1.copy(item.offset).applyQuaternion(this.camera.quaternion);
+ item.light.position.copy(this.controls.target).add(this.v1);
+ item.light.lookAt(this.controls.target);
+
+ // 动态背光逻辑
+ if (item.offset.z < -2) {
+ this.v1.copy(item.light.position).sub(this.controls.target).normalize();
+ const dot = this.v1.dot(this.camDir);
+ const factor = THREE.MathUtils.lerp(CONFIG.backlightBoost.min, CONFIG.backlightBoost.max, Math.max(0, 1 - dot));
+ item.light.intensity = item.baseIntensity * factor;
+ }
+ });
+ }
+ //动态设置曝光
+ updateImagePipeline() {
+ let exposure = 1.0;
+ this.camera.getWorldDirection(this.camForward);
+ this.camToTarget.copy(this.controls.target).sub(this.camera.position).normalize();
+ const facing = Math.abs(this.camForward.dot(this.camToTarget));
+ const targetExposure = THREE.MathUtils.lerp(1.25, 0.90, facing);
+ exposure = THREE.MathUtils.lerp(exposure, targetExposure, 0.08);
+ this.renderer.toneMappingExposure = CONFIG.exposureBase * exposure;
+ }
+
+ async setModel(modelUrl,load){
+ await new Promise((resolve, reject) => {
+ const drac = new DRACOLoader()
+ drac.setDecoderPath('/draco/')
+ const loader = new GLTFLoader().setDRACOLoader(drac);
+
+ if (this.currentModel) {
+ this.scene.remove(this.currentModel);
+ this.dispose(this.currentModel); // 核心优化:释放显存
+ }
+
+ loader.load(modelUrl,
+ (gltf) => {
+ this.currentModel = gltf.scene;
+
+ // 遍历模型:增强材质表现
+ this.currentModel.traverse(child => {
+ if (child.isMesh) {
+ child.castShadow = true;
+ child.receiveShadow = true;
+ if (child.material) {
+ child.material.envMapIntensity = 1.2; // 增强 HDRI 反射强度
+ }
+ }
+ });
+
+ const box = new THREE.Box3().setFromObject(this.currentModel);
+ const center = box.getCenter(new THREE.Vector3());
+ // this.currentModel.position.sub(center);
+ this.scene.add(this.currentModel);
+ this.modelInfo = this.getModelInfo(this.currentModel)
+ // 根据模型大小调整相机位置
+ this.adjustCameraToModel(this.modelInfo);
+
+ // 根据模型大小创建自适应灯光
+ this.createAdaptiveSoftboxes(this.modelInfo);
+ resolve('')
+ },
+ (xhr: any) => { // 加载进度回调
+ const percent = xhr.total == 0 ? 100 : (xhr.loaded / xhr.total * 100).toFixed(2);
+ load.value.progress = percent
+ console.log('模型加载进度:', percent);
+ },
+ (error: any) => { // 加载失败回调
+ console.error('模型加载失败:', error);
+ resolve('')
+ }
+ );
+ })
+ }
+
+ operation(){
+ let this_ = this
+ const animate = () => {
+ requestAnimationFrame(animate);
+ this.controls.update();
+ this.updateStudioLighting();
+ this.updateImagePipeline();
+ this.renderer.render(this.scene, this.camera);
+ }
+ animate();
+ }
+
+ // 释放模型资源
+ dispose(obj){
+ if (!obj) return;
+ obj.traverse(node => {
+ if (node.isMesh) {
+ if (node.geometry) node.geometry.dispose();
+ if (node.material) {
+ if (Array.isArray(node.material)) {
+ node.material.forEach(m => m.dispose());
+ } else {
+ node.material.dispose();
+ }
+ }
+ }
+ });
+ }
+
+
+ exportAsImage(){
+ return this.renderer.domElement.toDataURL('image/png');
+ }
+}
\ No newline at end of file
diff --git a/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts
index bef12eb..8c4b9db 100644
--- a/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts
+++ b/src/components/Canvas/FlowCanvas/components/tools/threeModel/threeTool.ts
@@ -9,18 +9,21 @@ export const initThree = async (threeDom)=>{
const scene = new THREE.Scene();
const group = new THREE.Group()
scene.add(group)
-
+ const studioLights = []
//创建相机对象
// this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
- const camera = new THREE.PerspectiveCamera(45, threeDom.offsetWidth / threeDom.offsetHeight, 0.1, 10000);
- camera.position.set(0, 90, 6); //设置相机位置
+ const camera = new THREE.PerspectiveCamera(45, threeDom.offsetWidth / threeDom.offsetHeight, 0.1, 1000);
+ camera.position.set(0, 1.5, 5);
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+
/**
* 创建渲染器对象
*/
- const width = threeDom.offsetWidth; //窗口宽度
+ const width = threeDom.offsetWidth; //窗口宽度
const height = threeDom.offsetHeight; //窗口高度
const renderer = new THREE.WebGLRenderer({
antialias: true,
@@ -50,6 +53,8 @@ export const initThree = async (threeDom)=>{
RIGHT:THREE.MOUSE.PAN // 右键 旋转(默认拖动:PAN)
// RIGHT:THREE.MOUSE.ROTAafTE // 右键 旋转(默认拖动:PAN)
}
+
+ //使用hdri文件
try {
const rgbeLoader = new RGBELoader()
const hdrTexture = await rgbeLoader.loadAsync(hdri)
@@ -67,6 +72,8 @@ export const initThree = async (threeDom)=>{
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8)
scene.add(ambientLight)
}
+
+
/**
* 光源设置
*/
@@ -78,16 +85,32 @@ export const initThree = async (threeDom)=>{
SpotLight 聚光源
*/
- // 2. 定向光(主光源)
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
- // 设置定向光的目标点(指向原点)
- directionalLight.target.position.set(0, 0, 0);
- scene.add(directionalLight);
//设置环境光全亮
//环境光
const pointLight = new THREE.AmbientLight(0xffffff,.8);
- scene.add(pointLight);
+ // scene.add(pointLight);
+
+ function createSoftbox(x, y, z, intensity, w = 5, h = 4) {
+ const light = new THREE.RectAreaLight(0xffffff, intensity, w, h);
+ light.position.set(x, y, z);
+ light.lookAt(0, 0, 0);
+ scene.add(light);
+ // 存储 offset 向量副本,避免后续重复创建
+ studioLights.push({
+ light,
+ offset: new THREE.Vector3(x, y, z),
+ baseIntensity: intensity
+ });
+ }
+ // 灯光布局优化
+ createSoftbox(0, 5.8, 3.5, 3.5, 6, 4.8); // 主光
+ createSoftbox(0, 2.8, 7.5, 2.2); // 前光
+ createSoftbox(0, 2.8, -7.5, 6.5); // 背光 (增强)
+ createSoftbox(-7.5, 2.8, 0, 10.8); // 侧光
+ createSoftbox(7.5, 2.8, 0, 2.0);
+ createSoftbox(0, -2.2, 3, 0.8, 9, 4); // 地面反光板
+
// const pointLight = new THREE.AmbientLight(0xffffff,1.0);
// pointLight.intensity = 1.2//光源强度
// pointLight.castShadow = true//开启阴影
@@ -108,7 +131,7 @@ export const initThree = async (threeDom)=>{
const textureLoader = new THREE.TextureLoader();
// const texture = textureLoader.load('/3dModel/sketch-thick.jpg');
scene.background = new THREE.Color("#fff");
- return {scene,group,camera,renderer,controls,pointLight}
+ return {scene,group,camera,renderer,controls,pointLight,studioLights}
}
export const clearModel = (group,scene)=>{
const oldGroup:any = group.value;
@@ -257,4 +280,14 @@ export const addModel = async (
}
)
})
+}
+// 导出当前视图为图片
+export const exportAsImage = (renderer, camera, scene, filename = 'model.png')=>{
+ // 渲染当前场景
+ renderer.render(scene, camera)
+
+ // 获取 canvas 数据
+ const canvas = renderer.domElement
+ const dataURL = canvas.toDataURL('image/png')
+ return dataURL
}
\ No newline at end of file
diff --git a/src/components/Canvas/FlowCanvas/flow-canvas.vue b/src/components/Canvas/FlowCanvas/flow-canvas.vue
index 3c17154..ddfd1fd 100644
--- a/src/components/Canvas/FlowCanvas/flow-canvas.vue
+++ b/src/components/Canvas/FlowCanvas/flow-canvas.vue
@@ -59,7 +59,7 @@
-
+
@@ -253,6 +253,27 @@
const openThreeModelPreview = (currentData) => {
threeModelRef.value.open(currentData)
}
+ const captureView = (captureData)=>{
+ const timestamp = Date.now()
+ nodeManager.createResultNode({
+ data: {
+ superiorID_: captureData.nodeId,
+ data: {
+ selectable: false,
+ imageProcessTasks: [
+ {
+ id: timestamp + '',
+ url: captureData.minioUrl,
+ status: 'RETURNED',
+ taskId: timestamp + ''
+ }
+ ],
+ selectTaskId: timestamp + ''
+ }
+ }
+ })
+ threeModelRef.value.close()
+ }
provide('openImagePreview', openImagePreview)
provide('openThreeModelPreview', openThreeModelPreview)
diff --git a/src/components/Canvas/FlowCanvas/manager/NodeManager.ts b/src/components/Canvas/FlowCanvas/manager/NodeManager.ts
index 49c834f..efea285 100644
--- a/src/components/Canvas/FlowCanvas/manager/NodeManager.ts
+++ b/src/components/Canvas/FlowCanvas/manager/NodeManager.ts
@@ -6,7 +6,8 @@ interface NodeData {
data?: object// 节点数据
tier?: string// 节点层级
isHeader?: boolean// 是否显示头
- superiorID?: string// 上级节点ID
+ superiorID?: string// 上级节点ID,有连接线
+ superiorID_?: string// 上级节点ID,没有连接线
superiorNodeType?: string// 上级节点类型
disableDelete?: boolean// 是否禁用删除
disableCopy?: boolean// 是否禁用复制
@@ -45,23 +46,31 @@ export class NodeManager {
/** 创建节点 */
createNode(options: NodeOptions) {
const superiorID = options?.data?.superiorID
+ const superiorID_ = options?.data?.superiorID_// 上级节点ID,用于创建子节点时使用
//获取上级节点所生成的最后一个node,设置位置为最后一个节点的xy 加上 节点间距
- const superiorGenerateNodes = this.stateManager.getSubordNodes(superiorID)
+ const superiorGenerateNodes = superiorID?this.stateManager.getSubordNodes(superiorID):[]
const currentNode = superiorGenerateNodes.find((node) => {
return (node.data.createIndexPosition === options?.data?.createIndexPosition && options?.data?.createIndexPosition)
})
const endGenerateNode = superiorGenerateNodes.reduce((max, current) => {
return current.data.createIndexPosition > max.data.createIndexPosition ? current : max
}, superiorGenerateNodes[0])
- const snode = superiorID ? this.stateManager.flowManager.getNodeById(superiorID) : this.stateManager.flowManager.getLastNode();
+ let snode = null as any
+ if(superiorID){
+ snode = this.stateManager.flowManager.getNodeById(superiorID)
+ }else if(superiorID_){
+ snode = this.stateManager.flowManager.getNodeById(superiorID_)
+ }else{
+ snode = this.stateManager.flowManager.getLastNode()
+ }
const id = options.id || createId()
const positionX = options.positionX || 0
const positionY = options.positionY || 0
const position = options.position ||
(
- currentNode ?
+ currentNode ?//当前节点位置,覆盖操作
currentNode.position :
- endGenerateNode ?
+ endGenerateNode ?//最大子级位置
{
x: endGenerateNode.position.x + positionX,
y: endGenerateNode.position.y + positionY + this.ranksep + 200
diff --git a/src/components/Canvas/components/base-modal.vue b/src/components/Canvas/components/base-modal.vue
index f127769..62b4ed6 100644
--- a/src/components/Canvas/components/base-modal.vue
+++ b/src/components/Canvas/components/base-modal.vue
@@ -20,7 +20,7 @@