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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user