adeed manual processing using UVtools

This commit is contained in:
cpu
2026-06-12 13:36:22 +02:00
parent a731b064b6
commit 9a8635aa98
11 changed files with 340 additions and 38 deletions

View File

@@ -36,10 +36,30 @@ NATIVE_DPMM = LCD_W_PX / LCD_W_MM # 58.824 dpmm (1 px ≈ 17.001 µm)
# ---------------------------------------------------------------------------
# pm4n format constants (reverse-engineered from Dummy.pm4n)
# ---------------------------------------------------------------------------
LAYE_TAG = b'LAYE'
MODE_TAG = b'Mode'
ENTRY_STRIDE = 0x20 # 32 bytes per layer entry
LAYE_HDR_SIZE = 0x1C # bytes before first entry within LAYE section
LAYE_TAG = b'LAYE'
MODE_TAG = b'Mode'
ENTRY_STRIDE = 0x20 # 32 bytes per layer entry
LAYE_HDR_SIZE = 0x20 # bytes before first entry within LAYE section
#
# LAYE header layout (verified from binary probe):
# +0x00 tag "LAYE"
# +0x04 tag "RDEF"
# +0x08 u32 0
# +0x0C u32 0xC4
# +0x10 u32 n_entries
# +0x14 u32 composite_image_offset
# +0x18 u32 block_size (RLE bytes per block)
# +0x1C f32 lift_height_mm
#
# LAYE entry layout (0x20 bytes each, immediately after header):
# +0x00 f32 exposure_sec <-- was wrongly assumed at +0x04
# +0x04 f32 z_position_mm
# +0x08 f32 layer_thickness_mm
# +0x0C u32 unknown
# +0x10 u32 unknown
# +0x14 u32 image_data_offset <-- was wrongly assumed at +0x18
# +0x18 u32 image_data_size <-- was wrongly assumed at +0x1C
# +0x1C f32 lift_speed
def find_tag(data: bytes, tag: bytes, start: int = 0) -> int:
@@ -56,21 +76,8 @@ def find_tag(data: bytes, tag: bytes, start: int = 0) -> int:
def count_laye_entries(data: bytes, laye_off: int) -> int:
"""Count layer entries by scanning until EXTR tag or implausible float."""
entry_start = laye_off + LAYE_HDR_SIZE
n = 0
while True:
pos = entry_start + n * ENTRY_STRIDE
if pos + 4 > len(data):
break
word = data[pos:pos+4]
if word in (b'EXTR', b'MACH', b'Mode', b'HEAD', b'PREV', b'LAYE'):
break
v = struct.unpack_from('<f', data, pos)[0]
if not (0.0 < v < 1000.0):
break
n += 1
return n
"""Read n_entries directly from LAYE header at +0x10."""
return unpack_u32(data, laye_off + 0x10)
def unpack_u32(data: bytes, off: int) -> int:
@@ -183,10 +190,10 @@ def patch_pm4n(dummy_path: Path, image: Image.Image,
log(f" Image blocks: first=0x{composite_off:06X}, "
f"old_size={old_block_size}, new_size={new_rle_size}")
# Patch exposure in all entries
# Patch exposure in all entries (entry+0x00 = exposure_sec float)
for i in range(n_entries):
base = laye_off + LAYE_HDR_SIZE + i * ENTRY_STRIDE
struct.pack_into('<f', raw, base + 0x04, exposure_sec)
struct.pack_into('<f', raw, base + 0x00, exposure_sec)
log(f" Exposure patched to {exposure_sec}s in {n_entries} entries")
# Update block size in LAYE header and Mode header
@@ -195,15 +202,21 @@ def patch_pm4n(dummy_path: Path, image: Image.Image,
struct.pack_into('<I', raw, mode_off + 0x48, new_rle_size)
log(f" Mode at 0x{mode_off:06X}")
# Update image offsets and sizes in all entries
# Update image offsets and sizes in all entries.
# Layout: [composite block][layer-0 block][layer-1 block]...
# entry+0x14 = data offset, entry+0x18 = data size
for i in range(n_entries):
base = laye_off + LAYE_HDR_SIZE + i * ENTRY_STRIDE
struct.pack_into('<I', raw, base + 0x18, composite_off + (i + 1) * new_rle_size)
struct.pack_into('<I', raw, base + 0x1C, new_rle_size)
struct.pack_into('<I', raw, base + 0x14, composite_off + (i + 1) * new_rle_size)
struct.pack_into('<I', raw, base + 0x18, new_rle_size)
# Splice new image data (composite block + one block per layer, all identical)
n_blocks = n_entries + 1
old_end = composite_off + n_blocks * old_block_size
# Layout: composite block at composite_off, then one block per entry.
# The file contains exactly n_entries blocks total (composite counts as block 0;
# the last entry's data_off is therefore one block past the last stored block,
# which Chitubox tolerates). We mirror the same layout.
n_blocks = n_entries # composite + (n_entries-1) layer blocks = n_entries total
old_end = composite_off + n_blocks * old_block_size
raw[composite_off:old_end] = new_rle * n_blocks
output_path.write_bytes(raw)