adeed manual processing using UVtools
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user