fixed xynova texture generation problem
This commit is contained in:
@@ -33,6 +33,11 @@ import bpy
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
|
||||
# Blender -P does not consistently include the script directory on sys.path.
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from patch_geometry import clip_rect
|
||||
from patch_projection import build_patch_geometry
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Common Utilities
|
||||
# ----------------------------------------------------------------------
|
||||
@@ -522,6 +527,20 @@ def generate_valid_color(neighbors, r):
|
||||
color[channel] = sampled
|
||||
return color
|
||||
|
||||
def precompute_cell_colors(nx, ny, r):
|
||||
"""Assign every seeded grid cell once, independent of geometry/UV visibility."""
|
||||
colors = np.zeros((ny, nx, 3), dtype=np.float32)
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
neighbors = []
|
||||
if j > 0:
|
||||
neighbors.append(colors[j - 1, i])
|
||||
if i > 0:
|
||||
neighbors.append(colors[j, i - 1])
|
||||
colors[j, i] = generate_valid_color(neighbors, r) if neighbors else np.random.rand(3)
|
||||
return colors
|
||||
|
||||
|
||||
def checker_patch_color(i, j):
|
||||
"""Return a black/white checker color for patch index (i, j)."""
|
||||
if (i + j) % 2 == 0:
|
||||
@@ -848,7 +867,7 @@ def generate_patch_texture(
|
||||
vector_width_mm=164.0, vector_height_mm=160.0,
|
||||
uv_centre=None, cross_length=40,
|
||||
cross_thickness=3, cross_color=(1,0,0,1), edge_sample_grid=5,
|
||||
uv_layer=None, image_name="PatchTexture"
|
||||
uv_layer=None, image_name="PatchTexture", patch_colors=None, geometry=None
|
||||
):
|
||||
if not output_path:
|
||||
raise ValueError("output_path is required (absolute PNG path)")
|
||||
@@ -856,50 +875,22 @@ def generate_patch_texture(
|
||||
_warn_if_page_aspect_differs(
|
||||
texture_width, texture_height, vector_width_mm, vector_height_mm
|
||||
)
|
||||
cam_data = cam.data
|
||||
render = scene.render
|
||||
res_x = render.resolution_x
|
||||
res_y = render.resolution_y
|
||||
if cam_data.sensor_fit == 'HORIZONTAL':
|
||||
sensor_width = cam_data.sensor_width
|
||||
sensor_height = sensor_width / (res_x / res_y)
|
||||
else:
|
||||
sensor_height = cam_data.sensor_height
|
||||
sensor_width = sensor_height * (res_x / res_y)
|
||||
focal = cam_data.lens
|
||||
frame_width = sensor_width / focal
|
||||
frame_height = sensor_height / focal
|
||||
if uv_layer is None:
|
||||
uv_layer = obj.data.uv_layers.active
|
||||
if uv_layer is None:
|
||||
raise ValueError("A UV layer is required")
|
||||
if patch_colors is None:
|
||||
patch_colors = precompute_cell_colors(nx, ny, r)
|
||||
if geometry is None:
|
||||
geometry = build_patch_geometry(
|
||||
obj, cam, scene, depsgraph, [uv_layer.name], nx, ny, edge_sample_grid
|
||||
)[uv_layer.name]
|
||||
|
||||
edge_sample_grid = max(2, int(edge_sample_grid))
|
||||
patch_uv_polygons = []
|
||||
patch_colors = np.zeros((ny, nx, 3), dtype=np.float32)
|
||||
skipped_patches = 0
|
||||
|
||||
print(f"Raycasting {nx*ny} patches...")
|
||||
for j in range(ny):
|
||||
v_start = j / ny
|
||||
v_end = (j+1) / ny
|
||||
for i in range(nx):
|
||||
u_start = i / nx
|
||||
u_end = (i+1) / nx
|
||||
uv_poly = sample_patch_uv_polygon(
|
||||
scene, depsgraph, cam, obj,
|
||||
u_start, u_end, v_start, v_end,
|
||||
frame_width, frame_height,
|
||||
edge_sample_grid=edge_sample_grid, uv_layer=uv_layer
|
||||
)
|
||||
if uv_poly is None:
|
||||
patch_uv_polygons.append(None)
|
||||
skipped_patches += 1
|
||||
continue
|
||||
patch_uv_polygons.append(uv_poly)
|
||||
|
||||
neighbors = []
|
||||
if j > 0: neighbors.append(patch_colors[j-1, i])
|
||||
if i > 0: neighbors.append(patch_colors[j, i-1])
|
||||
color = np.random.rand(3) if len(neighbors)==0 else generate_valid_color(neighbors, r)
|
||||
patch_colors[j, i] = color
|
||||
print(f"Skipped {skipped_patches} patches with fewer than 3 mesh samples.")
|
||||
def pixel_polygon(uv_polygon):
|
||||
# Authored per-loop UVs are authoritative, including wide/mirrored islands.
|
||||
# Clip to this output tile; do not infer a periodic seam or draw copies.
|
||||
return [(u * (texture_width - 1), v * (texture_height - 1))
|
||||
for u, v in clip_rect(uv_polygon, 0.0, 1.0, 0.0, 1.0)]
|
||||
|
||||
# Create image – start with black background
|
||||
img_name = image_name
|
||||
@@ -913,9 +904,8 @@ def generate_patch_texture(
|
||||
|
||||
# Rasterize all mesh UVs in white (visible + non-visible parts). Retain
|
||||
# the exact same polygons for native SVG/PDF output.
|
||||
mesh_polygons = mesh_uv_pixel_polygons(
|
||||
obj, texture_width, texture_height, uv_layer=uv_layer
|
||||
)
|
||||
mesh_polygons = [pixel_polygon(poly) for poly in geometry['mesh']]
|
||||
mesh_polygons = [poly for poly in mesh_polygons if len(poly) >= 3]
|
||||
for pixel_poly in mesh_polygons:
|
||||
fill_polygon(
|
||||
None, pixels, texture_width, texture_height,
|
||||
@@ -924,16 +914,14 @@ def generate_patch_texture(
|
||||
|
||||
# Draw the visible patches and retain each editable vector shape.
|
||||
patch_shapes = []
|
||||
idx = 0
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
uv_poly = patch_uv_polygons[idx]; idx += 1
|
||||
if uv_poly is None: continue
|
||||
color = patch_colors[j,i]
|
||||
rgba = (color[0], color[1], color[2], 1.0)
|
||||
for pixel_poly in uv_polygon_to_pixel_polygons(uv_poly, texture_width, texture_height):
|
||||
patch_shapes.append((pixel_poly, rgba))
|
||||
fill_polygon(image, pixels, texture_width, texture_height, pixel_poly, rgba)
|
||||
for i, j, uv_poly in geometry['patches']:
|
||||
color = patch_colors[j, i]
|
||||
rgba = (color[0], color[1], color[2], 1.0)
|
||||
pixel_poly = pixel_polygon(uv_poly)
|
||||
if len(pixel_poly) < 3:
|
||||
continue
|
||||
patch_shapes.append((pixel_poly, rgba))
|
||||
fill_polygon(image, pixels, texture_width, texture_height, pixel_poly, rgba)
|
||||
|
||||
cross_shapes = []
|
||||
if uv_centre is not None:
|
||||
@@ -1608,13 +1596,17 @@ def main():
|
||||
if config["seed"] is not None:
|
||||
np.random.seed(config["seed"])
|
||||
print(f"Using colour seed {config['seed']}")
|
||||
# One RNG snapshot is replayed for every target, so the same physical patch
|
||||
# keeps the same colour in each UV layout (and reruns reproduce when seeded).
|
||||
rng_state = np.random.get_state()
|
||||
|
||||
# Both targets share the very same cell table and visibility-clipped fragments.
|
||||
patch_colors = precompute_cell_colors(config['nx'], config['ny'], config['color_r'])
|
||||
start_time = time.time()
|
||||
try:
|
||||
geometry = build_patch_geometry(
|
||||
obj, cam, scene, depsgraph, [name for name, _, _ in targets],
|
||||
config['nx'], config['ny'], config['edge_sample_grid'])
|
||||
except Exception as exc:
|
||||
print(f'ERROR: patch geometry failed: {exc}', file=sys.stderr)
|
||||
return 1
|
||||
for uv_map, artifacts, uv_layer in targets:
|
||||
np.random.set_state(rng_state)
|
||||
uv_centre = _uv_centre(uv_layer) if config["cross_enabled"] else None
|
||||
print(f"--- UV map {uv_map!r}")
|
||||
try:
|
||||
@@ -1634,6 +1626,7 @@ def main():
|
||||
edge_sample_grid=config["edge_sample_grid"],
|
||||
uv_layer=uv_layer,
|
||||
image_name=f"PatchTexture_{uv_map}",
|
||||
patch_colors=patch_colors, geometry=geometry[uv_map],
|
||||
)
|
||||
except Exception as exc:
|
||||
print(
|
||||
|
||||
Reference in New Issue
Block a user