画立方体软件开发笔记 js three 投影 参数建模 旋转相机 @tarikjabiri/dxf导出dxf

发布于:2025-05-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

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

急急国王


网站公告

今日签到

点亮在社区的每一天
去签到