写在前面

  • C++很难吧
  • STL 的书汗牛充栋了吧
  • 但是好像也不是完全不行呀

Create Cube STL

July 8, 2024
https://chatgpt.com/share/7e9bdcab-dba8-4b68-8742-877be9f1cc7e


Create Cube STL

🧑‍🦰User

1
2
3
4
5
6
7
I want to generate an STL file with the following type

Header: solid name

Body: facet normal ni nj nk outer loop vertex v 1 x v 1 y v 1 z vertex v 2 x v 2 y v 2 z vertex v 3 x v 3 y v 3 z endloop endfacet

我需要一个 STL 文件,包含一个六面正方体。请按照以下格式生成这个文件。

🤖 Assistant

一个立方体的 STL 文件包含 12 个三角形(每个面由 2 个三角形组成)。

在生成 STL 文件之前,我们需要确定立方体的尺寸和位置。为了简化,我选择一个中心在原点、边长为 2 个单位的立方体。

以下是 STL 文件的内容:

1
2
3
4
5
6
7
B: ( 1, -1, -1)
C: ( 1, 1, -1)
D: (-1, 1, -1)
E: (-1, -1, 1)
F: ( 1, -1, 1)
G: ( 1, 1, 1)
H: (-1, 1, 1)

Here’s the STL file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Bottom Face
facet normal 0 0 -1
outer loop
vertex -1 -1 -1
vertex 1 -1 -1
vertex 1 1 -1
endloop
endfacet

facet normal 0 0 -1
outer loop
vertex -1 -1 -1
vertex 1 1 -1
vertex -1 1 -1
endloop
endfacet

// Top Face
facet normal 0 0 1
outer loop
vertex -1 -1 1
vertex 1 1 1
vertex 1 -1 1
endloop
endfacet

facet normal 0 0 1
outer loop
vertex -1 -1 1
vertex -1 1 1
vertex 1 1 1
endloop
endfacet

// Left Face
facet normal -1 0 0
outer loop
vertex -1 -1 -1
vertex -1 1 1
vertex -1 -1 1
endloop
endfacet

facet normal -1 0 0
outer loop
vertex -1 -1 -1
vertex -1 1 -1
vertex -1 1 1
endloop
endfacet

// Right Face
facet normal 1 0 0
outer loop
vertex 1 -1 -1
vertex 1 -1 1
vertex 1 1 1
endloop
endfacet

facet normal 1 0 0
outer loop
vertex 1 -1 -1
vertex 1 1 1
vertex 1 1 -1
endloop
endfacet

// Front Face
facet normal 0 -1 0
outer loop
vertex -1 -1 -1
vertex -1 -1 1
vertex 1 -1 1
endloop
endfacet

facet normal 0 -1 0
outer loop
vertex -1 -1 -1
vertex 1 -1 1
vertex 1 -1 -1
endloop
endfacet

// Back Face
facet normal 0 1 0
outer loop
vertex -1 1 -1
vertex 1 1 1
vertex -1 1 1
endloop
endfacet

facet normal 0 1 0
outer loop
vertex -1 1 -1
vertex 1 1 -1
vertex 1 1 1
endloop
endfacet

endsolid cube

每个面的法线方向取决于该面的朝向。每个面由两个三角形构成。这种 STL 文件使用 ASCII STL 格式,可以兼容大多数 STL 查看器和 3 D 打印机。如果需要更紧凑的表示,可以使用二进制 STL 格式。

🧑‍🦰User

现在我们进行程序化处理。我有一个名为 “img. Png” 的二维图像,其中像素有明暗之分。如果像素是亮的,我想在该位置生成一个小立方体;如果是暗的,则不生成。请编写一个 python 程序实现这一功能。

以下是一个生成立方体的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def generate_cube(x, y, z, size=1):
if type(size) != tuple:
size = (size, size, size)
sx = size[0]
sy = size[1]
sz = size[2]
vertices = [
(x, y, z+0),
(x+sx, y, z+0),
(x+sx, y+sy, z+0),
(x, y+sy, z+0),
(x, y, z+sz),
(x+sx, y, z+sz),
(x+sx, y+sy, z+sz),
(x, y+sy, z+sz)
]
faces = [
(0, 1, 2, 3),
(4, 5, 6, 7),
(0, 1, 5, 4),
(1, 2, 6, 5),
(2, 3, 7, 6),
(3, 0, 4, 7)
]

stl_faces = []
for face in faces:
v1, v2, v3, v4 = [vertices[i] for i in face]
# Split each square face into 2 triangle faces
stl_faces.append((v1, v2, v3, v4))

return stl_faces

Start with this

def image_to_stl(img_path, output_path, cube_size=1):
img = Image.open(img_path)
width, height = img.size
pixels = np.array(img.resize((width, height), Image.BICUBIC))[:, :, :3].mean(axis=2) > 128
pixels = np.array([[1, 1, 1]])
height, width = pixels.shape

all_faces = []

#faces = generate_cube(0, 0, 0, (width*cube_size, height*cube_size, cube_size))
#all_faces.extend(faces)

接下来,如果图像中一列的像素值为 1,则生成立方体。

🤖 Assistant

当然!为了从图像生成 STL 文件,可以在每个亮像素的位置生成立方体。首先需要将图像转换为灰度并进行阈值处理,以创建一个由亮暗像素组成的二值矩阵。对于每个亮像素,我们生成立方体的顶点,并将生成的三角形添加到 STL 数据中。

generate_cube 函数生成立方体的顶点,并提供构成立方体面的索引。不过,由于 STL 格式使用三角形表示面,所以我们需要将每个四边形分解为两个三角形。

我已经修改并完成了 image_to_stl 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from PIL import Image
import numpy as np

def generate_cube(x, y, z, size=1):
if type(size) != tuple:
size = (size, size, size)
sx = size[0]
sy = size[1]
sz = size[2]
vertices = [
(x, y, z),
(x+sx, y, z),
(x+sx, y+sy, z),
(x, y+sy, z),
(x, y, z+sz),
(x+sx, y, z+sz),
(x+sx, y+sy, z+sz),
(x, y+sy, z+sz)
]
faces = [
(0, 1, 2, 3),
(4, 5, 6, 7),
(0, 1, 5, 4),
(1, 2, 6, 5),
(2, 3, 7, 6),
(3, 0, 4, 7)
]

stl_faces = []
for face in faces:
v1, v2, v3, v4 = [vertices[i] for i in face]
# Split each square face into 2 triangle faces
stl_faces.append((v1, v2, v3))
stl_faces.append((v1, v3, v4))

return stl_faces

def image_to_stl(img_path, output_path, cube_size=1):
img = Image.open(img_path)
width, height = img.size
pixels = np.array(img.convert("L")) > 128 # Convert image to grayscale and threshold

all_faces = []

for y in range(height):
for x in range(width):
if pixels[y, x]: # If pixel is light
faces = generate_cube(x*cube_size, y*cube_size, 0, cube_size)
all_faces.extend(faces)

# Now, write the faces to an STL file
with open(output_path, 'w') as f:
f.write("solid img_to_stl\n")
for face in all_faces:
f.write("facet normal 0 0 0\n")
f.write(" outer loop\n")
for vertex in face:
f.write(" vertex {} {} {}\n".format(vertex[0], vertex[1], vertex[2]))
f.write(" endloop\n")
f.write("endfacet\n")
f.write("endsolid img_to_stl")

# Test
image_to_stl("img.png", "output.stl", 1)

This script utilizes the PIL library from Pillow to handle image operations. Make sure you have Pillow installed:

1
pip install Pillow

Remember that the generated STL will have cubes for every light pixel in the image. Depending on the image resolution and the cube size, the resulting STL file could be large. Adjust thecube_size` parameter to change the size of the generated cubes.

🧑‍🦰User

1
I want to not make too many extra faces. If two pixels are adjacent and are both 1 make only one box. I don't want an approximation I want to merge only if it's exact.

🤖 Assistant

If you want to merge adjacent cubes into a larger block, then you need a different approach. Essentially, you’d like to merge regions of adjacent light pixels into larger rectangles. This process can be likened to image segmentation, where we group connected components of light pixels.

The most straightforward method to achieve this is by performing a flood fill for each light pixel, and then marking those pixels as visited. This way, we’d determine the bounding box of each group of adjacent light pixels.

Once we determine these bounding boxes, we can generate cubes (or rather, rectangular prisms) based on these bounding boxes.

Here’s how this can be done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from PIL import Image
import numpy as np

def generate_cube(x, y, z, size):
sx, sy, sz = size
vertices = [
(x, y, z),
(x+sx, y, z),
(x+sx, y+sy, z),
(x, y+sy, z),
(x, y, z+sz),
(x+sx, y, z+sz),
(x+sx, y+sy, z+sz),
(x, y+sy, z+sz)
]
faces = [
(0, 1, 2, 3),
(4, 5, 6, 7),
(0, 1, 5, 4),
(1, 2, 6, 5),
(2, 3, 7, 6),
(3, 0, 4, 7)
]

stl_faces = []
for face in faces:
v1, v2, v3, v4 = [vertices[i] for i in face]
stl_faces.append((v1, v2, v3))
stl_faces.append((v1, v3, v4))

return stl_faces

def flood_fill(x, y, pixels, visited):
stack = [(x, y)]
min_x, min_y = x, y
max_x, max_y = x, y

while stack:
x, y = stack.pop()

if x < min_x: min_x = x
if x > max_x: max_x = x
if y < min_y: min_y = y
if y > max_y: max_y = y

if 0 <= x < pixels.shape[1] and 0 <= y < pixels.shape[0] and not visited[y, x] and pixels[y, x]:
visited[y, x] = True
stack.extend([(x+1, y), (x-1, y), (x, y+1), (x, y-1)])

return (min_x, min_y, max_x+1, max_y+1) # Returns bounding box coordinates

def image_to_stl(img_path, output_path, z_size=1):
img = Image.open(img_path)
pixels = np.array(img.convert("L")) > 128
visited = np.zeros_like(pixels, dtype=bool)

boxes = []
for y in range(pixels.shape[0]):
for x in range(pixels.shape[1]):
if pixels[y, x] and not visited[y, x]:
box = flood_fill(x, y, pixels, visited)
boxes.append(box)

all_faces = []
for box in boxes:
x1, y1, x2, y2 = box
size = (x2 - x1, y2 - y1, z_size)
faces = generate_cube(x1, y1, 0, size)
all_faces.extend(faces)

# Now, write the faces to an STL file
with open(output_path, 'w') as f:
f.write("solid img_to_stl\n")
for face in all_faces:
f.write("facet normal 0 0 0\n")
f.write(" outer loop\n")
for vertex in face:
f.write(" vertex {} {} {}\n".format(vertex[0], vertex[1], vertex[2]))
f.write(" endloop\n")
f.write("endfacet\n")
f.write("endsolid img_to_stl")

# Test
image_to_stl("img.png", "output.stl")

This code takes an image and generates an STL where each region of light pixels gets transformed into a single rectangular prism. Adjacent light pixels are merged into larger blocks, reducing the number of generated faces.

🧑‍🦰User

1
Compute the outer combination of [1, 2, 4, 8, 16[ with itself using itertools

🤖 Assistant

You can use the product function from itertools to compute the outer combination (or outer product) of a list with itself. Here’s how you can achieve this with your list:

1
2
3
4
5
6
7
import itertools

lst = [1, 2, 4, 8, 16]
outer_combination = list(itertools.product(lst, repeat=2))

print(outer_combination)

This will produce a list of tuples where each tuple contains a combination from the list with itself.