1 / 7
Hex Lithophane Pump Cover – Cooler Master AIO — zdjęcie 1
1 / 7

Hex Lithophane Pump Cover – Cooler Master AIO

Zamów wydruk tego modelu

Obliczymy ostateczną cenę na podstawie wybranego pliku. Sprawdzisz ją i zaakceptujesz przed płatnością.

Zapisane na tym urządzeniu.

Hex pump cover with slot for custom lithophane inserts generated via included Python script.

Ocena
0 / 5
Polubienia
1
Pobrania
3
Wydruki
0
Wyświetlenia
28
Oceny
0

Aktualizacja u źródła:

Informacje o modelu

Źródło
Printables
Kategoria
Computers
Licencja
Creative Commons — Attribution — Share Alike

This is a hex-style pump cover for Cooler Master AIOs with a slot for custom lithophane inserts.

The cover features a clean cutout designed to hold a lithophane plate.
A Python script is included to generate lithophanes that fit precisely into this opening.

The workflow is simple: generate your image, print it, and insert it into the cover.


Lithophane Generator (Python)

You can generate the lithophane using Thonny and the provided Python script.

Required libraries:

  • PIL

  • numpy

  • numpy-stl

Script:

# Spring 2026 – Hydrolith Generator (Hex Smooth Version)

import tkinter as tk from tkinter import filedialog, messagebox, ttk from PIL import Image, ImageOps, ImageDraw, ImageTk import numpy as np from stl import mesh import os

#********************** HEX PARAMETERS **********************************#

SIDE_LENGTH_MM = 28.28434 WIDTH_MM = 2 * SIDE_LENGTH_MM HEIGHT_MM = np.sqrt(3) * SIDE_LENGTH_MM BORDER_WIDTH_MM = 2.0

#********************** SMOOTH HEX MASK *********************************#

def hex_mask_mm(w_px, h_px, resolution, side_length_mm, scale=1.0, upscale=16): up_w = w_px * upscale up_h = h_px * upscale

mask_img = Image.new("L", (up_w, up_h), 0)
draw = ImageDraw.Draw(mask_img)

cx, cy = up_w / 2, up_h / 2

s_px = (side_length_mm * scale) / resolution * upscale
R = s_px

# Flat-top Hexagon
points = [
    (
        cx + R * np.cos(i * np.pi/3),
        cy + R * np.sin(i * np.pi/3)
    )
    for i in range(6)
]

draw.polygon(points, fill=255)

# Anti-aliased downscale
mask = mask_img.resize((w_px, h_px), resample=Image.LANCZOS)

return np.array(mask) / 255.0  # smooth mask

#********************** Lithophane generation ***************************#

def create_lithophane(image_path, output_path, min_thick, max_thick, resolution, border_height, invert=False, progress_callback=None): px_per_mm = 1 / resolution size_px = (int(WIDTH_MM * px_per_mm), int(HEIGHT_MM * px_per_mm))

img_raw = Image.open(image_path).convert("L")
img = ImageOps.fit(img_raw, size_px, method=Image.LANCZOS, centering=(0.5, 0.5))
img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)

grayscale = np.asarray(img) / 255.0
if invert:
    grayscale = 1.0 - grayscale

height_map = min_thick + (1.0 - grayscale) * (max_thick - min_thick)

h, w = height_map.shape

outer_mask = hex_mask_mm(w, h, resolution, SIDE_LENGTH_MM, scale=1.0)
inner_scale = 1.0 - (BORDER_WIDTH_MM / SIDE_LENGTH_MM)
inner_mask = hex_mask_mm(w, h, resolution, SIDE_LENGTH_MM, scale=inner_scale)

mask = outer_mask
rim_mask = outer_mask * (1.0 - inner_mask)

# weiche Kante → physisch glatter Übergang
height_map = min_thick + (height_map - min_thick) * mask

vertices = []
faces = []
vertex_index = {}

def get_index(x, y, ztype):
    key = (x, y, ztype)
    if key not in vertex_index:
        if ztype == 'top':
            z = height_map[y, x]
        elif ztype == 'rim':
            z = border_height
        else:
            z = 0.0
        vertices.append([x * resolution, y * resolution, z])
        vertex_index[key] = len(vertices) - 1
    return vertex_index[key]

threshold = 0.5

for y in range(h - 1):
    for x in range(w - 1):

        if not (mask[y, x] > threshold and
                mask[y, x+1] > threshold and
                mask[y+1, x] > threshold and
                mask[y+1, x+1] > threshold):
            continue

        def ztype(ix, iy):
            return 'rim' if rim_mask[iy, ix] > threshold else 'top'

        i0 = get_index(x, y, ztype(x, y))
        i1 = get_index(x + 1, y, ztype(x + 1, y))
        i2 = get_index(x, y + 1, ztype(x, y + 1))
        i3 = get_index(x + 1, y + 1, ztype(x + 1, y + 1))

        b0 = get_index(x, y, 'bottom')
        b1 = get_index(x + 1, y, 'bottom')
        b2 = get_index(x, y + 1, 'bottom')
        b3 = get_index(x + 1, y + 1, 'bottom')

        faces += [[i0, i1, i2], [i1, i3, i2]]
        faces += [[b2, b1, b0], [b2, b3, b1]]
        faces += [
            [i0, b0, i1], [i1, b0, b1],
            [i1, b1, i3], [i3, b1, b3],
            [i3, b3, i2], [i2, b3, b2],
            [i2, b2, i0], [i0, b2, b0]
        ]

    if progress_callback and y % 10 == 0:
        progress_callback(y / (h - 1))

vertices_np = np.array(vertices)
faces_np = np.array(faces)

litho = mesh.Mesh(np.zeros(faces_np.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces_np):
    for j in range(3):
        litho.vectors[i][j] = vertices_np[f[j], :]

litho.save(output_path)

#****************************** GUI **************************************#

class LithopaneApp: def init(self, root): self.root = root self.root.title("Hydrolith Hex Smooth")

    self.image_path = ""

    tk.Button(root, text="Select Image", command=self.load_image).grid(row=0, column=0, columnspan=2, pady=10)

    self.entries = {}
    labels = [
        ("Min. Thickness (mm)", "0.8"),
        ("Max. Thickness (mm)", "3.2"),
        ("Resolution (mm/px)", "0.3"),
        ("Border Height (mm)", "2.0")
    ]

    for i, (label, default) in enumerate(labels, start=1):
        tk.Label(root, text=label).grid(row=i, column=0, sticky="e")
        entry = tk.Entry(root)
        entry.insert(0, default)
        entry.grid(row=i, column=1)
        self.entries[label] = entry

    self.invert_var = tk.BooleanVar()
    tk.Checkbutton(root, text="Invert colors (negative)", variable=self.invert_var).grid(row=5, column=0, columnspan=2)

    self.status = tk.Label(root, text="", fg="blue")
    self.status.grid(row=6, column=0, sticky="e")

    self.progress = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate")
    self.progress.grid(row=6, column=1, sticky="w")

    tk.Button(root, text="Export STL", command=self.export_stl).grid(row=7, column=0, columnspan=2, pady=10)
    tk.Button(root, text="Show Preview", command=self.show_preview).grid(row=8, column=0, columnspan=2)

def load_image(self):
    file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.jpg *.png *.bmp")])
    if file_path:
        self.image_path = file_path
        self.status.config(text=os.path.basename(file_path))

def update_progress(self, fraction):
    self.progress["value"] = int(fraction * 100)
    self.root.update_idletasks()

def export_stl(self):
    if not self.image_path:
        messagebox.showwarning("Warning", "Please select an image first.")
        return

    try:
        min_thick = float(self.entries["Min. Thickness (mm)"].get())
        max_thick = float(self.entries["Max. Thickness (mm)"].get())
        resolution = float(self.entries["Resolution (mm/px)"].get())
        border_height = float(self.entries["Border Height (mm)"].get())
    except ValueError:
        messagebox.showerror("Error", "Invalid input.")
        return

    output_path = filedialog.asksaveasfilename(defaultextension=".stl")
    if not output_path:
        return

    create_lithophane(
        self.image_path,
        output_path,
        min_thick,
        max_thick,
        resolution,
        border_height,
        invert=self.invert_var.get(),
        progress_callback=self.update_progress
    )

    self.status.config(text="Done.")

def show_preview(self):
    if not self.image_path:
        return

    resolution = float(self.entries["Resolution (mm/px)"].get())
    px_per_mm = 1 / resolution
    size_px = (int(WIDTH_MM * px_per_mm), int(HEIGHT_MM * px_per_mm))

    img = ImageOps.fit(Image.open(self.image_path).convert("L"), size_px)
    grayscale = np.asarray(img) / 255.0

    mask = hex_mask_mm(size_px[0], size_px[1], resolution, SIDE_LENGTH_MM)

    preview = Image.fromarray((grayscale * mask * 255).astype(np.uint8))
    preview = preview.resize((300, 300))

    win = tk.Toplevel(self.root)
    tk_img = ImageTk.PhotoImage(preview)
    tk.Label(win, image=tk_img).pack()
    win.image = tk_img

Run

if name == "main": root = tk.Tk() app = LithopaneApp(root) root.mainloop()


For best Lithopane results:

  • Use 100% infill

  • Use the lowest possible layer height

This improves light diffusion and overall image clarity.

Tagi