画立方体软件开发笔记 js three 投影 参数建模 旋转相机 @tarikjabiri/dxf导出dxf
gitee:
njsgcs/njsgcs_3d
mainwindow.js:4 Uncaught SyntaxError: The requested module '/3dviewport.js' does not provide an export named 'default'一定要default吗
2025-05-10 14-27-58 专门写了个代码画立方体
import{ scene,camera,renderer} from './3dviewport';
import {update2DViewport }from './2dviewport';
const sidebar3d = document.createElement('div');
sidebar3d.id = 'sidebar3d';
// 固定侧边栏宽度sidebar3d.style.padding = '10px';
sidebar3d.style.backgroundColor = '#f0f0f0';import * as THREE from 'three';function init_sidebar3d() {const ox=document.createElement('input');ox.type='text';ox.placeholder = '左下角坐标x'; // 改为placeholder提示,避免覆盖用户输入const oy=document.createElement('input');oy.type='text';oy.placeholder = '左下角坐标y';const oz=document.createElement('input');oz.type='text';oz.placeholder = '左下角坐标z';const sx=document.createElement('input');sx.type='text';sx.placeholder = '尺寸x';const sy=document.createElement('input');sy.type='text';sy.placeholder = '尺寸y';const sz=document.createElement('input');sz.type='text';sz.placeholder = '尺寸z';const createBtn = document.createElement('button');createBtn.textContent = '确定';createBtn.style.width = '100%';createBtn.style.margin = '5px 0';createBtn.addEventListener('click', () => {// 获取输入值并转换为数值const oxVal = parseFloat(ox.value);const oyVal = parseFloat(oy.value);const ozVal = parseFloat(oz.value);const sxVal = parseFloat(sx.value);const syVal = parseFloat(sy.value);const szVal = parseFloat(sz.value);addcube_button(oxVal, oyVal, ozVal, sxVal, syVal, szVal); // 传递参数});ox.style.display="none"oy.style.display="none"oz.style.display="none"sx.style.display="none"sy.style.display="none"sz.style.display="none"createBtn.style.display="none"const addCubeBtn = document.createElement('button');addCubeBtn.textContent = '添加立方体';addCubeBtn.style.width = '100%';addCubeBtn.style.margin = '5px 0';addCubeBtn.addEventListener('click', () => {if (ox.style.display=="none"){ox.style.display="block";oy.style.display="block";oz.style.display="block";sx.style.display="block";sy.style.display="block";sz.style.display="block";createBtn.style.display="block";}else{ox.style.display="none";oy.style.display="none";oz.style.display="none";sx.style.display="none";sy.style.display="none";sz.style.display="none";createBtn.style.display="none";}// 点击后更新按钮列表});sidebar3d.appendChild(addCubeBtn);sidebar3d.appendChild(ox);sidebar3d.appendChild(oy);sidebar3d.appendChild(oz);sidebar3d.appendChild(sx);sidebar3d.appendChild(sy);sidebar3d.appendChild(sz);sidebar3d.appendChild(createBtn);addcube_button(0,0,0,100,100,100);addcube_button(0,100,0,100,100,100);addcube_button(100,0,0,100,100,100);addcube_button(-100,0,0,100,100,100);// 首次生成按钮}
// 创建更新侧边栏按钮的函数function addcube_button(ox,oy,oz,sx,sy,sz){// 使用输入的尺寸创建几何体(默认值防止未输入时出错)const geometry = new THREE.BoxGeometry(sx || 100, sy || 100, sz || 100);const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });const cube = new THREE.Mesh(geometry, material);// 设置位置(默认原点防止未输入时出错)cube.position.set(ox || 0, oy || 0, oz || 0);cube.name = 'CustomCube'; scene.add(cube);renderer.render(scene, camera);const btn = document.createElement('button');btn.textContent = `立方体`; // 按钮文本btn.style.width = '100%'; // 按钮宽度btn.style.margin = '5px 0'; // 按钮间距btn.addEventListener('click', () => {if (deleteBtn.style.display=="none"){deleteBtn.style.display = 'block';}else{deleteBtn.style.display = 'none';}});const deleteBtn = document.createElement('button');deleteBtn.textContent = '删除';deleteBtn.style.width = '100%';deleteBtn.style.margin = '5px 0';deleteBtn.addEventListener('click', () => {scene.remove(cube); // 删除对象renderer.render(scene, camera);sidebar3d.removeChild(deleteBtn); // 移除按钮sidebar3d.removeChild(btn); // 移除对应的按钮});deleteBtn.style.display="none"sidebar3d.appendChild(btn);sidebar3d.appendChild(deleteBtn);update2DViewport();}
// 创建几何体和网格init_sidebar3d();// 不再直接添加到body,改为导出供mainwindow管理
export default sidebar3d;
旋转功能
2025-05-10 14-41-38 旋转视图
ai写的我连看都没看
import * as THREE from 'three';// 创建 canvas 并导出
const viewportCanvas3d = document.createElement('canvas');
viewportCanvas3d.id = 'scene';
viewportCanvas3d.style.flex = '1';
viewportCanvas3d.style.height = '100vh';// 创建场景和相机
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
);// 创建渲染器并绑定 canvas
const renderer = new THREE.WebGLRenderer({ canvas: viewportCanvas3d });
renderer.setSize(window.innerWidth, window.innerHeight);// 设置相机位置
camera.position.z = 500;// ---------- 新增鼠标中键旋转逻辑 ----------
let isRotating = false;
let startX = 0;
let startY = 0;
const target = new THREE.Vector3(0, 0, 0); // 假设零件中心在场景原点// 鼠标按下事件(中键触发旋转)
function onMouseDown(event) {if (event.button === 1) { // 鼠标中键(button=1)isRotating = true;startX = event.clientX;startY = event.clientY;event.preventDefault(); // 阻止默认滚动行为}
}// 鼠标移动事件(旋转相机)
function onMouseMove(event) {if (!isRotating) return;const deltaX = event.clientX - startX;const deltaY = event.clientY - startY;startX = event.clientX;startY = event.clientY;// 计算相机绕目标点的旋转(水平/垂直移动)const cameraPos = camera.position.clone().sub(target); // 转换为相对目标点的坐标// 水平移动绕Y轴旋转const rotateY = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), deltaX * 0.005);cameraPos.applyQuaternion(rotateY);// 垂直移动绕X轴旋转(负号调整方向)const rotateX = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -deltaY * 0.005);cameraPos.applyQuaternion(rotateX);camera.position.copy(cameraPos.add(target)); // 转换回世界坐标camera.lookAt(target); // 始终看向目标点renderer.render(scene, camera); // 更新渲染
}// 鼠标释放事件(结束旋转)
function onMouseUp(event) {if (event.button === 1) isRotating = false;
}// 绑定事件监听
viewportCanvas3d.addEventListener('mousedown', onMouseDown);
viewportCanvas3d.addEventListener('mousemove', onMouseMove);
viewportCanvas3d.addEventListener('mouseup', onMouseUp);
// ---------- 新增逻辑结束 ----------export { viewportCanvas3d as default , scene , camera, renderer };
太复杂了让ai减少方法数量
就差亿点了
没毛病
连出来是右视图
const edges = [[0, 1], [1, 3], [3, 2], [2, 0],[4, 5], [5, 7], [7, 6], [6, 4],[1, 4], [0, 5], [2, 7], [3, 6]];
import * as THREE from 'three';
import { scene } from './3dviewport.js';
import { DxfWriter, point3d } from '@tarikjabiri/dxf';
// 创建 Canvas 元素
const viewportCanvas2d = document.createElement('canvas');
viewportCanvas2d.width = 900;
viewportCanvas2d.height = 600;
viewportCanvas2d.style.border = '1px solid #000';const ctx = viewportCanvas2d.getContext('2d');// 预定义相机集合
const cameras = [{camera: new THREE.OrthographicCamera(-450, 450, 300, -300, -1000, 1000),position: [0, 0, 500],label: "Front View",offset: { x: 0, y: 0 },scale: 0.5,},{// 原参数:-300, 300, 300, -300(水平/垂直范围 600/600)// 调整后:根据画布宽高比 3:2,将垂直范围调整为 400(600/3*2=400)camera: new THREE.OrthographicCamera(-450, 450, 300, -300, -1000, 1000),position: [500, 0, 0],label: "Right View",offset: { x: 450, y: 0 },scale: 0.5,},{camera: new THREE.OrthographicCamera(-450, 450, 300, -300, -1000, 1000),position: [0, 500, 0],label: "Top View",offset: { x: 0, y: 300 },scale: 0.5,}
];// 初始化相机参数
cameras.forEach(({ camera, position }) => {camera.position.set(...position);camera.lookAt(0, 0, 0);
});// 临时变量const tempV1 = new THREE.Vector3();
const tempV2 = new THREE.Vector3();
const linelist=[];function update2DViewport() {ctx.clearRect(0, 0, viewportCanvas2d.width, viewportCanvas2d.height);for (const config of cameras) {const { camera, offset, label, scale } = config;// 获取相机位置const cameraPos = new THREE.Vector3();camera.getWorldPosition(cameraPos);const Edges = [];// 遍历场景中的立方体scene.traverse(obj => {if (!(obj instanceof THREE.Mesh && obj.name === 'CustomCube')) return;const geometry = obj.geometry;if (!(geometry instanceof THREE.BoxGeometry)) return;const vertices = geometry.attributes.position.array;const edges = [[0, 1], [1, 3], [3, 2], [2, 0],[4, 5], [5, 7], [7, 6], [6, 4],[1, 4], [0, 5], [2, 7], [3, 6]];edges.forEach(([v1Idx, v2Idx]) => {tempV1.fromBufferAttribute(geometry.attributes.position, v1Idx).applyMatrix4(obj.matrixWorld);tempV2.fromBufferAttribute(geometry.attributes.position, v2Idx).applyMatrix4(obj.matrixWorld);Edges.push([tempV1.clone(), tempV2.clone()]);});});// 绘制标签ctx.fillStyle = "#000";ctx.font = `${12 * scale}px sans-serif`;ctx.fillText(label, offset.x + 10 * scale, offset.y + 20 * scale);ctx.strokeStyle = '#000';ctx.lineWidth = 1;for (const [p1, p2] of Edges) {const sp1 = projectPoint(p1, camera, scale);const sp2 = projectPoint(p2, camera, scale);ctx.beginPath();ctx.moveTo(sp1.x + offset.x, sp1.y + offset.y);ctx.lineTo(sp2.x + offset.x, sp2.y + offset.y);ctx.stroke();linelist.push([sp1.x + offset.x, sp1.y + offset.y,sp2.x + offset.x, sp2.y + offset.y])}}}
function export_dxf() {// 如果是浏览器环境const dxf = new DxfWriter();// 添加一个图层(可选)dxf.addLayer('Lines', 7); // 颜色索引 7 是黑色// 辅助函数:保留两位小数function toFixed(num) {return parseFloat(parseFloat(num).toFixed(2));}// 遍历 linelist 添加线段for (const [p1x, p1y,p2x,p2y] of linelist) {const start = point3d(toFixed(p1x), toFixed(p1y));const end = point3d(toFixed(p2x), toFixed(p2y));dxf.addLine(start, end, 'Lines'); // 'Lines' 图层名}// 生成 DXF 字符串内容const dxfString = dxf.stringify();// 创建 Blob 并触发下载const blob = new Blob([dxfString], { type: "application/dxf" });const url = URL.createObjectURL(blob);const now = new Date().toISOString().replace(/[:.]/g, '') // 移除冒号和点.replace('T', '_');const a = document.createElement("a");a.href = url;a.download = `${now} output.dxf`;a.click();// 释放资源URL.revokeObjectURL(url);
}
// 投影函数
function projectPoint(point, camera, scale) {const ndc = point.clone().project(camera);const x = (ndc.x + 1) * viewportCanvas2d.width / 2 * scale;const y = (1 - ndc.y) * viewportCanvas2d.height / 2 * scale;return { x, y };
}export {viewportCanvas2d as default,update2DViewport,export_dxf} ;
import {export_dxf} from './2dviewport';
const sidebar2d = document.createElement('div')
sidebar2d.id = 'sidebar2d'// 固定侧边栏宽度const export_dxfBtn = document.createElement('button');export_dxfBtn.textContent = '导出dxf';export_dxfBtn.style.width = '100%';export_dxfBtn.style.margin = '5px 0';export_dxfBtn.addEventListener('click', () => {export_dxf() // 传递参数});sidebar2d.appendChild(export_dxfBtn);
// 不再直接添加到body,改为导出供mainwindow管理
export default sidebar2d
急急国王