feat: stl wall thickness
This commit is contained in:
parent
075323164d
commit
8fb43bd479
60
DockerFile
Normal file
60
DockerFile
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
FROM ubuntu:latest
|
||||||
|
|
||||||
|
# 使用阿里云的Ubuntu源替换默认源
|
||||||
|
# RUN echo "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list && \
|
||||||
|
# echo "deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse" >> /etc/apt/sources.list && \
|
||||||
|
# echo "deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse" >> /etc/apt/sources.list && \
|
||||||
|
# echo "deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse" >> /etc/apt/sources.list && \
|
||||||
|
# echo "deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse" >> /etc/apt/sources.list
|
||||||
|
|
||||||
|
# 更新软件包列表并安装依赖项
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
software-properties-common \
|
||||||
|
libgl1-mesa-glx \
|
||||||
|
libxi6 \
|
||||||
|
libxxf86vm1 \
|
||||||
|
libxfixes3 \
|
||||||
|
libxrender1 \
|
||||||
|
libx11-6 \
|
||||||
|
libsdl2-2.0-0 \
|
||||||
|
libopenal1 \
|
||||||
|
libxrandr2 \
|
||||||
|
libfreetype6 \
|
||||||
|
libtiff5 \
|
||||||
|
libjpeg8 \
|
||||||
|
libpng16-16 \
|
||||||
|
zlib1g \
|
||||||
|
wget \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
libxi6 \
|
||||||
|
libglu1-mesa \
|
||||||
|
libxext6
|
||||||
|
|
||||||
|
# 下载并安装Blender
|
||||||
|
RUN wget https://mirrors.tuna.tsinghua.edu.cn/blender/release/Blender2.93/blender-2.93.1-linux-x64.tar.xz && \
|
||||||
|
tar xf blender-2.93.1-linux-x64.tar.xz && \
|
||||||
|
mv blender-2.93.1-linux-x64 /opt/blender && \
|
||||||
|
rm blender-2.93.1-linux-x64.tar.xz
|
||||||
|
|
||||||
|
# 将Blender的可执行文件路径添加到PATH中
|
||||||
|
ENV PATH="$PATH:/opt/blender"
|
||||||
|
|
||||||
|
# 安装Python依赖项
|
||||||
|
RUN pip3 install fastapi[all] uvicorn
|
||||||
|
|
||||||
|
# 工作目录
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
RUN mkdir /workspace/temp
|
||||||
|
|
||||||
|
|
||||||
|
# 将你的API代码和Blender Python脚本复制到工作目录中
|
||||||
|
COPY ./api_script.py /workspace/
|
||||||
|
COPY ./add_thickness.py /workspace/
|
||||||
|
|
||||||
|
# 暴露API使用的端口
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# 启动API
|
||||||
|
CMD ["uvicorn", "api_script:app", "--host", "0.0.0.0", "--port", "8000"]
|
24
PyBlender.dockerfile
Normal file
24
PyBlender.dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# 使用预先构建的Blender镜像
|
||||||
|
FROM zocker160/blender-bpy:stable
|
||||||
|
|
||||||
|
# 安装Python库和工具
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
python3-pip
|
||||||
|
|
||||||
|
# 安装Python依赖
|
||||||
|
RUN pip3 install fastapi uvicorn python-multipart
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
RUN mkdir /workspace/temp
|
||||||
|
|
||||||
|
# 复制API和处理脚本到容器内
|
||||||
|
COPY ./api_script.py /workspace/
|
||||||
|
COPY ./add_thickness.py /workspace/
|
||||||
|
|
||||||
|
# 暴露FastAPI使用的端口
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# 设置启动命令
|
||||||
|
CMD ["uvicorn", "api_script:app", "--host", "0.0.0.0", "--port", "8000"]
|
43
add_thickness.py
Normal file
43
add_thickness.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
# 删除所有meshes,这样我们可以从一个干净的环境开始
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
bpy.ops.object.select_by_type(type='MESH')
|
||||||
|
bpy.ops.object.delete()
|
||||||
|
|
||||||
|
def add_thickness_to_stl(input_file_path, output_file_path, thickness=0.1):
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
# 激活STL导入插件
|
||||||
|
bpy.ops.preferences.addon_enable(module="io_mesh_stl")
|
||||||
|
|
||||||
|
# 导入STL
|
||||||
|
bpy.ops.import_mesh.stl(filepath=input_file_path)
|
||||||
|
|
||||||
|
# 确保导入的模型是当前活跃的
|
||||||
|
obj = bpy.context.selected_objects[0]
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
|
||||||
|
# 进入编辑模式
|
||||||
|
bpy.ops.object.editmode_toggle()
|
||||||
|
|
||||||
|
# 选择所有顶点
|
||||||
|
bpy.ops.mesh.select_all(action='SELECT')
|
||||||
|
|
||||||
|
# 使用Solidify修饰器来增加壁厚
|
||||||
|
bpy.ops.object.modifier_add(type='SOLIDIFY')
|
||||||
|
solidify = obj.modifiers["Solidify"]
|
||||||
|
solidify.thickness = thickness
|
||||||
|
|
||||||
|
# 应用修饰器
|
||||||
|
bpy.ops.object.modifier_apply({"object": obj}, modifier="Solidify")
|
||||||
|
|
||||||
|
# 返回对象模式
|
||||||
|
bpy.ops.object.editmode_toggle()
|
||||||
|
|
||||||
|
# 导出为STL
|
||||||
|
bpy.ops.export_mesh.stl(filepath=output_file_path)
|
||||||
|
|
||||||
|
cleanup()
|
19
api_script.py
Normal file
19
api_script.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from fastapi import FastAPI, UploadFile, File
|
||||||
|
from add_thickness import add_thickness_to_stl
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
@app.post("/process_stl/")
|
||||||
|
async def process_stl(file: UploadFile = File(...)):
|
||||||
|
input_file_path = f"temp/{file.filename}"
|
||||||
|
output_file_path = f"processed/{file.filename}"
|
||||||
|
|
||||||
|
# 保存上传的文件
|
||||||
|
with open(input_file_path, 'wb') as buffer:
|
||||||
|
buffer.write(file.file.read())
|
||||||
|
|
||||||
|
# 调用修改过的 add_thickness 函数
|
||||||
|
add_thickness_to_stl(input_file_path, output_file_path)
|
||||||
|
|
||||||
|
# 返回处理后的文件
|
||||||
|
return FileResponse(output_file_path, headers={"Content-Disposition": f"attachment; filename={file.filename}"})
|
|
@ -2,7 +2,12 @@ import React, { useEffect, useRef } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
|
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||||
import { autoScale, getTriangleVertexs } from "./util";
|
import {
|
||||||
|
autoScale,
|
||||||
|
createEnclosedGeometry,
|
||||||
|
getOffsetTriangles,
|
||||||
|
getTriangleVertexs,
|
||||||
|
} from "./util";
|
||||||
|
|
||||||
export const STLViewer: React.FC = () => {
|
export const STLViewer: React.FC = () => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -44,80 +49,25 @@ export const STLViewer: React.FC = () => {
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const newTriangles = [];
|
const newTriangles = getOffsetTriangles(triangles, -2);
|
||||||
|
|
||||||
triangles.forEach((triangleVertices) => {
|
console.log("newTriangles", newTriangles);
|
||||||
const A = triangleVertices[0];
|
|
||||||
const B = triangleVertices[1];
|
|
||||||
const C = triangleVertices[2];
|
|
||||||
|
|
||||||
// 计算三角形的法线
|
// 使用上面定义的函数
|
||||||
let normal = new THREE.Vector3();
|
const enclosedGeometry = createEnclosedGeometry(
|
||||||
let cb = new THREE.Vector3();
|
triangles,
|
||||||
let ab = new THREE.Vector3();
|
newTriangles
|
||||||
cb.subVectors(C, B);
|
|
||||||
ab.subVectors(A, B);
|
|
||||||
cb.cross(ab).normalize();
|
|
||||||
normal.copy(cb);
|
|
||||||
|
|
||||||
// 创建新的顶点,它们沿着法线方向内移2个单位
|
|
||||||
let A1 = new THREE.Vector3()
|
|
||||||
.copy(A)
|
|
||||||
.add(normal.clone().multiplyScalar(-2));
|
|
||||||
let B1 = new THREE.Vector3()
|
|
||||||
.copy(B)
|
|
||||||
.add(normal.clone().multiplyScalar(-2));
|
|
||||||
let C1 = new THREE.Vector3()
|
|
||||||
.copy(C)
|
|
||||||
.add(normal.clone().multiplyScalar(-2));
|
|
||||||
|
|
||||||
// 添加新的三角形
|
|
||||||
newTriangles.push([A1, B1, C1]);
|
|
||||||
|
|
||||||
// 创建封闭表面的新面片
|
|
||||||
newTriangles.push([A, B, A1]);
|
|
||||||
newTriangles.push([B, B1, A1]);
|
|
||||||
|
|
||||||
newTriangles.push([B, C, B1]);
|
|
||||||
newTriangles.push([C, C1, B1]);
|
|
||||||
|
|
||||||
newTriangles.push([C, A, C1]);
|
|
||||||
newTriangles.push([A, A1, C1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const g = new THREE.BufferGeometry();
|
|
||||||
|
|
||||||
// 将newTriangles数据分解为顶点和索引数组
|
|
||||||
const vertices = [];
|
|
||||||
const indices = [];
|
|
||||||
newTriangles.forEach((triangle, index) => {
|
|
||||||
triangle.forEach((vertex) => {
|
|
||||||
vertices.push(vertex.x, vertex.y, vertex.z);
|
|
||||||
});
|
|
||||||
indices.push(index * 3, index * 3 + 1, index * 3 + 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置vertices和indices到geometry
|
|
||||||
g.setAttribute(
|
|
||||||
"position",
|
|
||||||
new THREE.Float32BufferAttribute(vertices, 3)
|
|
||||||
);
|
);
|
||||||
g.setIndex(indices);
|
|
||||||
|
|
||||||
// 为了确保光照和阴影正确,我们需要计算几何体的面的法线
|
enclosedGeometry.center();
|
||||||
g.computeVertexNormals();
|
|
||||||
g.center();
|
|
||||||
|
|
||||||
// 创建网格使用MeshPhongMaterial或其他的材料
|
const material = new THREE.MeshBasicMaterial({
|
||||||
const material = new THREE.MeshPhongMaterial({
|
color: "lightgrey",
|
||||||
color: 0x00ff00,
|
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(g, material);
|
const mesh = new THREE.Mesh(enclosedGeometry, material);
|
||||||
mesh.scale.set(s, s, s);
|
mesh.scale.set(s, s, s);
|
||||||
|
|
||||||
// 添加网格到场景
|
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -11,58 +11,192 @@ export const autoScale = (geometry: THREE.BufferGeometry) => {
|
||||||
return scaleFactor;
|
return scaleFactor;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTriangleVertexs = (geometry: THREE.BufferGeometry) => {
|
export const getTriangleVertexs = (
|
||||||
console.time("计算三角面面片顶点");
|
geometry: THREE.BufferGeometry
|
||||||
|
): THREE.Vector3[][] => {
|
||||||
const positions = geometry.attributes.position;
|
const positions = geometry.attributes.position;
|
||||||
const triangles = [];
|
const triangles: THREE.Vector3[][] = [];
|
||||||
|
|
||||||
|
const getTriangleVertices = (
|
||||||
|
index1: number,
|
||||||
|
index2: number,
|
||||||
|
index3: number
|
||||||
|
) => [
|
||||||
|
new THREE.Vector3(
|
||||||
|
positions.getX(index1),
|
||||||
|
positions.getY(index1),
|
||||||
|
positions.getZ(index1)
|
||||||
|
),
|
||||||
|
new THREE.Vector3(
|
||||||
|
positions.getX(index2),
|
||||||
|
positions.getY(index2),
|
||||||
|
positions.getZ(index2)
|
||||||
|
),
|
||||||
|
new THREE.Vector3(
|
||||||
|
positions.getX(index3),
|
||||||
|
positions.getY(index3),
|
||||||
|
positions.getZ(index3)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
if (geometry.index) {
|
if (geometry.index) {
|
||||||
const indices = geometry.index.array;
|
const indices = geometry.index.array;
|
||||||
|
|
||||||
for (let i = 0; i < indices.length; i += 3) {
|
for (let i = 0; i < indices.length; i += 3) {
|
||||||
const triangleVertices = [
|
triangles.push(
|
||||||
new THREE.Vector3(
|
getTriangleVertices(indices[i], indices[i + 1], indices[i + 2])
|
||||||
positions.getX(indices[i]),
|
);
|
||||||
positions.getY(indices[i]),
|
|
||||||
positions.getZ(indices[i])
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(indices[i + 1]),
|
|
||||||
positions.getY(indices[i + 1]),
|
|
||||||
positions.getZ(indices[i + 1])
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(indices[i + 2]),
|
|
||||||
positions.getY(indices[i + 2]),
|
|
||||||
positions.getZ(indices[i + 2])
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
triangles.push(triangleVertices);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < positions.count; i += 3) {
|
for (let i = 0; i < positions.count; i += 3) {
|
||||||
const triangleVertices = [
|
triangles.push(getTriangleVertices(i, i + 1, i + 2));
|
||||||
new THREE.Vector3(
|
}
|
||||||
positions.getX(i),
|
}
|
||||||
positions.getY(i),
|
|
||||||
positions.getZ(i)
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(i + 1),
|
|
||||||
positions.getY(i + 1),
|
|
||||||
positions.getZ(i + 1)
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(i + 2),
|
|
||||||
positions.getY(i + 2),
|
|
||||||
positions.getZ(i + 2)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
triangles.push(triangleVertices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.timeEnd("计算三角面面片顶点");
|
|
||||||
return triangles;
|
return triangles;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getOffsetTriangles = (
|
||||||
|
triangles: THREE.Vector3[][],
|
||||||
|
offset: number
|
||||||
|
): THREE.Vector3[][] => {
|
||||||
|
const newTriangles: THREE.Vector3[][] = [];
|
||||||
|
|
||||||
|
triangles.forEach((triangle) => {
|
||||||
|
// 计算三角形的法线
|
||||||
|
const normal = new THREE.Vector3();
|
||||||
|
const edge1 = new THREE.Vector3().subVectors(triangle[1], triangle[0]);
|
||||||
|
const edge2 = new THREE.Vector3().subVectors(triangle[2], triangle[0]);
|
||||||
|
normal.crossVectors(edge1, edge2).normalize();
|
||||||
|
|
||||||
|
// 使用法线和偏移量计算新的三角形顶点
|
||||||
|
const offsetNormal = normal.clone().multiplyScalar(offset);
|
||||||
|
const newTriangle = triangle.map((vertex) =>
|
||||||
|
vertex.clone().sub(offsetNormal)
|
||||||
|
);
|
||||||
|
newTriangles.push(newTriangle);
|
||||||
|
});
|
||||||
|
|
||||||
|
return newTriangles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEnclosedGeometry = (
|
||||||
|
triangles: THREE.Vector3[][],
|
||||||
|
newTriangles: THREE.Vector3[][]
|
||||||
|
): THREE.BufferGeometry => {
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
|
const vertices: THREE.Vector3[] = [];
|
||||||
|
const indices: number[] = [];
|
||||||
|
|
||||||
|
const processedEdges: Set<string> = new Set();
|
||||||
|
|
||||||
|
function edgeId(v1: THREE.Vector3, v2: THREE.Vector3): string {
|
||||||
|
return `${v1.x},${v1.y},${v1.z}->${v2.x},${v2.y},${v2.z}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
triangles.forEach((triangle, index) => {
|
||||||
|
const innerTriangle = newTriangles[index];
|
||||||
|
|
||||||
|
triangle.forEach((vertex) => {
|
||||||
|
vertices.push(vertex);
|
||||||
|
});
|
||||||
|
|
||||||
|
innerTriangle.forEach((vertex) => {
|
||||||
|
vertices.push(vertex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process the three edges for each triangle
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const next = (i + 1) % 3;
|
||||||
|
const edge = edgeId(triangle[i], triangle[next]);
|
||||||
|
|
||||||
|
if (!processedEdges.has(edge)) {
|
||||||
|
processedEdges.add(edge);
|
||||||
|
|
||||||
|
// Add the vertices for the quad (two triangles)
|
||||||
|
const quadVertices = [
|
||||||
|
triangle[i],
|
||||||
|
triangle[next],
|
||||||
|
innerTriangle[i],
|
||||||
|
innerTriangle[next],
|
||||||
|
];
|
||||||
|
|
||||||
|
quadVertices.forEach((vertex) => {
|
||||||
|
vertices.push(vertex);
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseIndex = vertices.length - 4;
|
||||||
|
|
||||||
|
// First triangle of the quad
|
||||||
|
indices.push(baseIndex, baseIndex + 2, baseIndex + 1);
|
||||||
|
// Second triangle of the quad
|
||||||
|
indices.push(baseIndex + 1, baseIndex + 2, baseIndex + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
geometry.setFromPoints(vertices);
|
||||||
|
geometry.setIndex(indices);
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const createEnclosedGeometry = (
|
||||||
|
// triangles: THREE.Vector3[][],
|
||||||
|
// newTriangles: THREE.Vector3[][]
|
||||||
|
// ): THREE.BufferGeometry => {
|
||||||
|
// const geometry = new THREE.BufferGeometry();
|
||||||
|
|
||||||
|
// const vertices: number[] = [];
|
||||||
|
// const indices: number[] = [];
|
||||||
|
|
||||||
|
// // 添加原始三角形和新三角形的顶点到vertices数组
|
||||||
|
// triangles.forEach((triangle) => {
|
||||||
|
// triangle.forEach((v) => {
|
||||||
|
// vertices.push(v.x, v.y, v.z);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// newTriangles.forEach((triangle) => {
|
||||||
|
// triangle.forEach((v) => {
|
||||||
|
// vertices.push(v.x, v.y, v.z);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const totalTriangles = triangles.length;
|
||||||
|
|
||||||
|
// // 创建索引来连接triangles和newTriangles中的三角形
|
||||||
|
// for (let i = 0; i < totalTriangles; i++) {
|
||||||
|
// const offset = totalTriangles * 3; // newTriangles在vertices中的偏移量
|
||||||
|
|
||||||
|
// const a1 = i * 3;
|
||||||
|
// const b1 = a1 + 1;
|
||||||
|
// const c1 = a1 + 2;
|
||||||
|
|
||||||
|
// const a2 = offset + i * 3;
|
||||||
|
// const b2 = a2 + 1;
|
||||||
|
// const c2 = a2 + 2;
|
||||||
|
|
||||||
|
// // 添加原始和新三角形的索引
|
||||||
|
// indices.push(a1, b1, c1);
|
||||||
|
// indices.push(a2, c2, b2);
|
||||||
|
|
||||||
|
// // 为每一对相邻的三角形添加边的索引
|
||||||
|
// indices.push(a1, b1, a2);
|
||||||
|
// indices.push(b1, b2, a2);
|
||||||
|
|
||||||
|
// indices.push(b1, c1, b2);
|
||||||
|
// indices.push(c1, c2, b2);
|
||||||
|
|
||||||
|
// indices.push(c1, a1, c2);
|
||||||
|
// indices.push(a1, a2, c2);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// geometry.setIndex(indices);
|
||||||
|
// geometry.setAttribute(
|
||||||
|
// "position",
|
||||||
|
// new THREE.Float32BufferAttribute(vertices, 3)
|
||||||
|
// );
|
||||||
|
// geometry.computeVertexNormals(); // 计算顶点法线
|
||||||
|
|
||||||
|
// return geometry;
|
||||||
|
// };
|
||||||
|
|
Loading…
Reference in New Issue
Block a user