Add stroke texture PNG, SVG, and PDF export

Reuse colour artwork serializers and record source hashes. Verified single and multiview exports plus targeted tests.
This commit is contained in:
ykcheungab
2026-10-03 21:17:28 +08:00
parent 6b0694d6cb
commit 5bd7dd7a01
5 changed files with 452 additions and 66 deletions
+22 -15
View File
@@ -93,8 +93,6 @@ def fill_polygon(image, pixels, width, height, polygon, color):
poly = [(int(round(x)), int(round(y))) for x, y in polygon]
if len(poly) < 3:
return
min_x = max(0, min(p[0] for p in poly))
max_x = min(width-1, max(p[0] for p in poly))
min_y = max(0, min(p[1] for p in poly))
max_y = min(height-1, max(p[1] for p in poly))
for y in range(min_y, max_y + 1):
@@ -125,8 +123,6 @@ def fill_polygon_value(grid, width, height, polygon, value):
poly = [(int(round(x)), int(round(y))) for x, y in polygon]
if len(poly) < 3:
return
min_x = max(0, min(p[0] for p in poly))
max_x = min(width - 1, max(p[0] for p in poly))
min_y = max(0, min(p[1] for p in poly))
max_y = min(height - 1, max(p[1] for p in poly))
for y in range(min_y, max_y + 1):
@@ -679,10 +675,19 @@ def _clipped_shapes(shapes, width, height):
return clipped
def save_png_image(image, output_path):
"""Serialize an already-rasterized Blender image as PNG."""
path = Path(output_path)
path.parent.mkdir(parents=True, exist_ok=True)
image.filepath_raw = str(path)
image.file_format = 'PNG'
image.save()
def write_svg_vector(
output_path, texture_width, texture_height, width_mm, height_mm,
mesh_polygons, patch_shapes, cross_rects, cross_color, uv_map,
background=None,
background=None, artwork_label="patch",
):
"""Write editable grouped SVG geometry; no raster data is embedded.
@@ -696,6 +701,9 @@ def write_svg_vector(
background_fill = "#" + "".join(
f"{round(_clamp_channel(channel) * 255):02x}" for channel in background[:3]
)
if artwork_label not in ("patch", "stroke"):
raise ValueError(f"unsupported artwork label: {artwork_label!r}")
plural = "patches" if artwork_label == "patch" else "strokes"
lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
(
@@ -705,10 +713,10 @@ def write_svg_vector(
f'viewBox="0 0 {texture_width} {texture_height}" '
f'preserveAspectRatio="none">'
),
f" <title>Orisys patch texture — {_xml_escape(uv_map)} UV map</title>",
f" <title>Orisys {artwork_label} texture — {_xml_escape(uv_map)} UV map</title>",
(
" <desc>Native vector geometry grouped as background, mesh, "
"camera-space patches, and reference cross.</desc>"
f"camera-space {plural}, and reference cross.</desc>"
),
(
f' <g id="artwork" transform="translate(0 {texture_height}) scale(1 -1)">'
@@ -728,10 +736,10 @@ def write_svg_vector(
f"mesh-face-{index:04d}", polygon, (1.0, 1.0, 1.0, 1.0)
)
)
lines.extend([" </g>", ' <g id="patches">'])
lines.extend([" </g>", f' <g id="{plural}">'])
for index, (polygon, color) in enumerate(patch_shapes, start=1):
lines.append(
_svg_polygon_element(f"patch-{index:04d}", polygon, color)
_svg_polygon_element(f"{artwork_label}-{index:04d}", polygon, color)
)
lines.extend([" </g>", ' <g id="reference-cross">'])
cross_attributes = _svg_fill_attributes(cross_color)
@@ -798,7 +806,7 @@ def _build_pdf(objects):
def write_pdf_vector(
output_path, texture_width, texture_height, width_mm, height_mm,
mesh_polygons, patch_shapes, cross_rects, cross_color,
background=None,
background=None, artwork_label="patch",
):
"""Write native vector PDF paths at the configured physical page size.
@@ -824,7 +832,9 @@ def write_pdf_vector(
for polygon in mesh_polygons:
content.extend(_pdf_polygon_commands(polygon, scale_x, scale_y))
content.append("% camera-space patches")
if artwork_label not in ("patch", "stroke"):
raise ValueError(f"unsupported artwork label: {artwork_label!r}")
content.append(f"% camera-space {'patches' if artwork_label == 'patch' else 'strokes'}")
for polygon, color in patch_shapes:
content.append(_pdf_set_fill(color))
content.extend(_pdf_polygon_commands(polygon, scale_x, scale_y))
@@ -961,10 +971,7 @@ def generate_patch_texture(
image.pixels[:] = pixels
image.update()
out_path = Path(output_path)
out_path.parent.mkdir(parents=True, exist_ok=True)
image.filepath_raw = str(out_path)
image.file_format = 'PNG'
image.save()
save_png_image(image, out_path)
print(f"PNG raster texture saved to {out_path}")
uv_map_name = uv_layer.name if uv_layer is not None else "active"