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 { 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);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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(triangleVertices);
|
||||
triangles.push(getTriangleVertices(i, i + 1, i + 2));
|
||||
}
|
||||
}
|
||||
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;
|
||||
// };
|
||||
|
|
Loading…
Reference in New Issue
Block a user