feat: stl wall thickness

This commit is contained in:
mozzie 2023-09-06 17:01:45 +08:00
parent 075323164d
commit 8fb43bd479
6 changed files with 339 additions and 109 deletions

60
DockerFile Normal file
View 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
View 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
View 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
View 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}"})

View File

@ -2,7 +2,12 @@ import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
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 = () => {
const containerRef = useRef<HTMLDivElement>(null);
@ -44,80 +49,25 @@ export const STLViewer: React.FC = () => {
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
const newTriangles = [];
const newTriangles = getOffsetTriangles(triangles, -2);
triangles.forEach((triangleVertices) => {
const A = triangleVertices[0];
const B = triangleVertices[1];
const C = triangleVertices[2];
console.log("newTriangles", newTriangles);
// 计算三角形的法线
let normal = new THREE.Vector3();
let cb = new THREE.Vector3();
let ab = new THREE.Vector3();
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)
// 使用上面定义的函数
const enclosedGeometry = createEnclosedGeometry(
triangles,
newTriangles
);
g.setIndex(indices);
// 为了确保光照和阴影正确,我们需要计算几何体的面的法线
g.computeVertexNormals();
g.center();
enclosedGeometry.center();
// 创建网格使用MeshPhongMaterial或其他的材料
const material = new THREE.MeshPhongMaterial({
color: 0x00ff00,
const material = new THREE.MeshBasicMaterial({
color: "lightgrey",
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(g, material);
const mesh = new THREE.Mesh(enclosedGeometry, material);
mesh.scale.set(s, s, s);
// 添加网格到场景
scene.add(mesh);
////////////////////////////////////////////////////////////////

View File

@ -11,58 +11,192 @@ export const autoScale = (geometry: THREE.BufferGeometry) => {
return scaleFactor;
};
export const getTriangleVertexs = (geometry: THREE.BufferGeometry) => {
console.time("计算三角面面片顶点");
export const getTriangleVertexs = (
geometry: THREE.BufferGeometry
): THREE.Vector3[][] => {
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) {
const indices = geometry.index.array;
for (let i = 0; i < indices.length; i += 3) {
const triangleVertices = [
new THREE.Vector3(
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);
triangles.push(
getTriangleVertices(indices[i], indices[i + 1], indices[i + 2])
);
}
} else {
for (let i = 0; i < positions.count; i += 3) {
const triangleVertices = [
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(getTriangleVertices(i, i + 1, i + 2));
}
}
triangles.push(triangleVertices);
}
}
console.timeEnd("计算三角面面片顶点");
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;
// };