Add cutout shell and volume node assets for depth volume processing
- Implemented `cutout_shell.py` to build front normals and shell positions for balloon geometry. - Created `cutout_volume.py` to establish smooth center fields and manage balloon surface attributes. - Introduced `pisa_sr_export.py` for exporting PiSA-SR components with dependencies and asset finalization.
This commit is contained in:
@@ -563,6 +563,11 @@ def triangulate(contours, spine, path_edges, free, spacing):
|
||||
edges.extend(((boundary_count + a, boundary_count + b) for a, b in path_edges))
|
||||
result = delaunay_2d_cdt([Vector(v) for v in p], edges, [], 0, 1e-07, True)
|
||||
points = np.asarray([tuple(v) for v in result[0]])
|
||||
# Restore input precision before testing area and containment. Float32 CDT
|
||||
# coordinates can turn collinear outline samples into artificial slivers.
|
||||
for vertex, originals in enumerate(result[3]):
|
||||
if originals:
|
||||
points[vertex] = p[originals].mean(axis=0)
|
||||
faces = np.asarray(result[2], dtype=np.int32)
|
||||
faces = faces[g.points_inside_contours(points[faces].mean(axis=1), contours)]
|
||||
area = np.abs(
|
||||
|
||||
@@ -1238,6 +1238,35 @@ class BaseShape:
|
||||
balloon: object
|
||||
|
||||
|
||||
def refine_balloon_triangles(points, faces, boundary):
|
||||
"""Add interior samples to boundary ears and interior boundary chords."""
|
||||
from collections import Counter
|
||||
import numpy as np
|
||||
|
||||
edges = Counter(tuple(sorted((face[i], face[(i + 1) % 3]))) for face in faces for i in range(3))
|
||||
values, midpoints = list(points), {}
|
||||
for edge, count in edges.items():
|
||||
if count == 2 and all(boundary[index] for index in edge):
|
||||
midpoints[edge] = len(values)
|
||||
values.append(points[list(edge)].mean(axis=0))
|
||||
refined = []
|
||||
for face in faces:
|
||||
ring = []
|
||||
for index, vertex in enumerate(face):
|
||||
ring.append(vertex)
|
||||
midpoint = midpoints.get(tuple(sorted((vertex, face[(index + 1) % 3]))))
|
||||
if midpoint is not None:
|
||||
ring.append(midpoint)
|
||||
if len(ring) == 3 and not all(boundary[vertex] for vertex in face):
|
||||
refined.append(tuple(face))
|
||||
continue
|
||||
center = len(values)
|
||||
values.append(points[list(face)].mean(axis=0))
|
||||
refined.extend((center, ring[i], ring[(i + 1) % len(ring)]) for i in range(len(ring)))
|
||||
points = np.asarray(values)
|
||||
return points, refined, boundary_vertices_from_faces(len(points), refined)
|
||||
|
||||
|
||||
def cutout_domain(
|
||||
selection_contours,
|
||||
bounds,
|
||||
@@ -1276,6 +1305,17 @@ def cutout_domain(
|
||||
(start, end, faces.tolist(), pixel_to_planar(contour))
|
||||
for start, end, faces, contour in pixel_components
|
||||
]
|
||||
refined_points, refined_faces, refined_components = [], [], []
|
||||
offset = 0
|
||||
for start, end, component_faces, contour in components:
|
||||
points, faces, _ = refine_balloon_triangles(planar[start:end], component_faces, boundary[start:end])
|
||||
refined_points.append(points)
|
||||
refined_faces.extend(tuple(offset + index for index in face) for face in faces)
|
||||
refined_components.append((offset, offset + len(points), faces, contour))
|
||||
offset += len(points)
|
||||
planar = np.concatenate(refined_points)
|
||||
planar_faces, components = refined_faces, refined_components
|
||||
boundary = boundary_vertices_from_faces(len(planar), planar_faces)
|
||||
profile = balloon_profile(
|
||||
balloon_algorithm, planar, planar_faces, boundary, components
|
||||
)
|
||||
|
||||
@@ -4,10 +4,10 @@ from ...properties import ai_ready
|
||||
from .shape import CUTOUT_SHAPE_LABELS
|
||||
|
||||
CUTOUT_SHAPE_BUTTONS = (
|
||||
("SURFACE", "MESH_GRID"),
|
||||
("BALLOON", "MESH_UVSPHERE"),
|
||||
("FLAT", "MESH_GRID"),
|
||||
("SOLID", "MESH_UVSPHERE"),
|
||||
("DEPTH_BALLOON", "META_BALL"),
|
||||
("DEPTH_SURFACE", "SURFACE_NCURVE"),
|
||||
("DEPTH_SOLID", "SURFACE_NCURVE"),
|
||||
)
|
||||
|
||||
CUTOUT_MESH_DETAIL_SPACING = {
|
||||
|
||||
@@ -73,15 +73,17 @@ _HIDDEN = {"HIDDEN", "SKIP_SAVE"}
|
||||
|
||||
|
||||
def normal_mode_for_shape(shape, generate_normal):
|
||||
if shape == "DEPTH_SOLID":
|
||||
return NormalMode.OBJECT
|
||||
if shape == "DEPTH_BALLOON":
|
||||
return NormalMode.TANGENT
|
||||
if not generate_normal:
|
||||
return NormalMode.NONE
|
||||
if shape == "DEPTH_SURFACE":
|
||||
return NormalMode.OBJECT
|
||||
return NormalMode.TANGENT
|
||||
|
||||
|
||||
def depth_mode_for_shape(shape):
|
||||
if shape == "DEPTH_SURFACE":
|
||||
if shape == "DEPTH_SOLID":
|
||||
return "VECTOR"
|
||||
if shape == "DEPTH_BALLOON":
|
||||
return "Z"
|
||||
@@ -200,7 +202,7 @@ class CutoutSelectionToShape(JobOperatorBase, bpy.types.Operator):
|
||||
source_object_name: bpy.props.StringProperty(options=_HIDDEN)
|
||||
selection_path_json: bpy.props.StringProperty(options=_HIDDEN)
|
||||
selection_bounds_json: bpy.props.StringProperty(options=_HIDDEN)
|
||||
shape: bpy.props.StringProperty(options=_HIDDEN)
|
||||
shape: bpy.props.StringProperty(default="SOLID", options=_HIDDEN)
|
||||
point_spacing: bpy.props.FloatProperty(
|
||||
default=32.0,
|
||||
min=0.01,
|
||||
|
||||
@@ -31,10 +31,9 @@ from .geometry import (
|
||||
)
|
||||
from .shape import (
|
||||
BALLOON_ATTRIBUTE_NAME,
|
||||
BALLOON_CUTOUT_SHAPES,
|
||||
NORMAL_SCALE_ATTRIBUTE_NAME,
|
||||
CUTOUT_X_TO_Z,
|
||||
CUTOUT_NODE_GROUP_NAME,
|
||||
CUTOUT_SHAPE_INDICES,
|
||||
CUTOUT_NODE_GROUP_NAMES,
|
||||
DEPTH_CUTOUT_SHAPES,
|
||||
)
|
||||
|
||||
@@ -131,11 +130,11 @@ def create_cropped_color_image(source_object, crop_bounds, source_rgba):
|
||||
return image
|
||||
|
||||
|
||||
def load_cutout_node_group():
|
||||
return load_node_group(CUTOUT_NODE_GROUP_NAME)
|
||||
def load_cutout_node_group(shape):
|
||||
return load_node_group(CUTOUT_NODE_GROUP_NAMES[shape], asset_revision=11)
|
||||
|
||||
|
||||
def _connect_balloon_normal_scale(material):
|
||||
def _connect_cutout_normal_scale(material):
|
||||
nodes = material.node_tree.nodes
|
||||
image_layer = next(
|
||||
node
|
||||
@@ -146,7 +145,7 @@ def _connect_balloon_normal_scale(material):
|
||||
)
|
||||
attribute = nodes.new("ShaderNodeAttribute")
|
||||
attribute.attribute_type = "GEOMETRY"
|
||||
attribute.attribute_name = BALLOON_ATTRIBUTE_NAME
|
||||
attribute.attribute_name = NORMAL_SCALE_ATTRIBUTE_NAME
|
||||
attribute.location = (-550.0, -250.0)
|
||||
material.node_tree.links.new(
|
||||
attribute.outputs["Fac"], image_layer.inputs["Normal Scale"]
|
||||
@@ -206,8 +205,7 @@ def create_shape_object(
|
||||
scene=context.scene,
|
||||
)
|
||||
depth_reference = None
|
||||
if shape in BALLOON_CUTOUT_SHAPES:
|
||||
_connect_balloon_normal_scale(material)
|
||||
_connect_cutout_normal_scale(material)
|
||||
uniform_scale = None
|
||||
depth_direction = None
|
||||
if shape in DEPTH_CUTOUT_SHAPES:
|
||||
@@ -227,7 +225,7 @@ def create_shape_object(
|
||||
depth_reference = DEFAULT_DEPTH_BASELINE
|
||||
if shape == "DEPTH_BALLOON":
|
||||
depth_direction = DEFAULT_DEPTH_DIRECTION
|
||||
elif shape == "DEPTH_SURFACE":
|
||||
elif shape == "DEPTH_SOLID":
|
||||
depth_reference = metric_depth_reference(
|
||||
camera_depth,
|
||||
calibration_valid,
|
||||
@@ -268,22 +266,19 @@ def create_shape_object(
|
||||
inward_axis,
|
||||
)
|
||||
mesh_object["anyimage_mesh_shape"] = shape
|
||||
node_group = load_cutout_node_group()
|
||||
node_group = load_cutout_node_group(shape)
|
||||
modifier = mesh_object.modifiers.new(name=node_group.name, type="NODES")
|
||||
modifier.node_group = node_group
|
||||
modifier.show_group_selector = False
|
||||
for name, value in (
|
||||
("Shape", CUTOUT_SHAPE_INDICES[shape]),
|
||||
(
|
||||
"Inward Axis",
|
||||
0 if inward_axis == CutoutInwardAxis.NEGATIVE_Z else 1,
|
||||
),
|
||||
("Cleanup", cleanup_value),
|
||||
("Smooth Iterations", 0),
|
||||
("Smooth", 1.0),
|
||||
("Cleanup Threshold", cleanup_value),
|
||||
(
|
||||
"Thickness",
|
||||
1.0 if shape in {"BALLOON", "DEPTH_BALLOON"} else 0.0,
|
||||
0.0 if shape == "FLAT" else 1.0,
|
||||
),
|
||||
):
|
||||
set_modifier_input(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Cutout Shape identifiers shared by runtime and node-asset tools."""
|
||||
|
||||
BALLOON_ATTRIBUTE_NAME = "o_balloon"
|
||||
CUTOUT_NODE_GROUP_NAME = "O Image Cutout"
|
||||
NORMAL_SCALE_ATTRIBUTE_NAME = "o_normal_scale"
|
||||
CUTOUT_X_TO_Z = (
|
||||
(0.0, -1.0, 0.0, 0.0),
|
||||
(0.0, 0.0, 1.0, 0.0),
|
||||
@@ -15,20 +15,16 @@ CUTOUT_Z_TO_X = (
|
||||
(0.0, 0.0, 0.0, 1.0),
|
||||
)
|
||||
CUTOUT_NODE_GROUP_NAMES = {
|
||||
"SURFACE": "O Image Surface",
|
||||
"BALLOON": "O Image Balloon",
|
||||
"FLAT": "O Image Cutout",
|
||||
"SOLID": "O Image Cutout",
|
||||
"DEPTH_BALLOON": "O Image Depth Balloon",
|
||||
"DEPTH_SURFACE": "O Image Depth Surface",
|
||||
"DEPTH_SOLID": "O Image Depth Cutout",
|
||||
}
|
||||
CUTOUT_SHAPE_LABELS = {
|
||||
"SURFACE": "Surface",
|
||||
"BALLOON": "Balloon",
|
||||
"FLAT": "Flat",
|
||||
"SOLID": "Solid",
|
||||
"DEPTH_BALLOON": "Depth Balloon",
|
||||
"DEPTH_SURFACE": "Depth Surface",
|
||||
}
|
||||
CUTOUT_SHAPE_INDICES = {
|
||||
shape: index for index, shape in enumerate(CUTOUT_NODE_GROUP_NAMES)
|
||||
"DEPTH_SOLID": "Depth Solid",
|
||||
}
|
||||
CUTOUT_SHAPES = frozenset(CUTOUT_NODE_GROUP_NAMES)
|
||||
DEPTH_CUTOUT_SHAPES = frozenset({"DEPTH_BALLOON", "DEPTH_SURFACE"})
|
||||
BALLOON_CUTOUT_SHAPES = frozenset({"BALLOON", "DEPTH_BALLOON"})
|
||||
DEPTH_CUTOUT_SHAPES = frozenset({"DEPTH_BALLOON", "DEPTH_SOLID"})
|
||||
|
||||
@@ -29,7 +29,7 @@ from ..common.image import (
|
||||
|
||||
_HIDDEN = {"HIDDEN", "SKIP_SAVE"}
|
||||
|
||||
UPSCALE_FACTOR = 4
|
||||
UPSCALE_FACTOR = 2
|
||||
|
||||
|
||||
def upscale_output_size(image):
|
||||
@@ -49,7 +49,7 @@ def upscale_input_size_allowed(image, max_input_size):
|
||||
class UpscaleImage(JobOperatorBase, bpy.types.Operator):
|
||||
bl_idname = "anyimage.upscale_image"
|
||||
bl_label = "Upscale"
|
||||
bl_description = "Upscale this Image Empty by 4x with an AI model"
|
||||
bl_description = "Upscale this Image Empty by 2x with an AI model"
|
||||
bl_options = {"UNDO"}
|
||||
job_type = "upscale-image"
|
||||
starting_message = "Starting Image Empty upscale"
|
||||
|
||||
Reference in New Issue
Block a user