perf
This commit is contained in:
parent
a726825eed
commit
45d47161f8
3 changed files with 235 additions and 74 deletions
|
|
@ -86,3 +86,78 @@ EXPORT void align_path_seam_sharpest_c(const double* in_pts, int M, int stride,
|
||||||
memcpy(out_pts, in_pts + (best_idx * stride), split * stride * sizeof(double));
|
memcpy(out_pts, in_pts + (best_idx * stride), split * stride * sizeof(double));
|
||||||
memcpy(out_pts + (split * stride), in_pts, (best_idx + 1) * stride * sizeof(double));
|
memcpy(out_pts + (split * stride), in_pts, (best_idx + 1) * stride * sizeof(double));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast 64-bit hash for 3 float32 values (12 bytes)
|
||||||
|
static inline uint64_t hash_v3(const float* v) {
|
||||||
|
const uint32_t* u = (const uint32_t*)v;
|
||||||
|
uint64_t h = (uint64_t)u[0] * 0x9e3779b97f4a7c15ULL;
|
||||||
|
h ^= (uint64_t)u[1] * 0xbf58476d1ce4e5b9ULL;
|
||||||
|
h = (h ^ (h >> 30)) * 0x94d049bb133111ebULL;
|
||||||
|
h ^= (uint64_t)u[2] * 0x4bfb2f35a09a447fULL;
|
||||||
|
h = (h ^ (h >> 27)) * 0x94d049bb133111ebULL;
|
||||||
|
return h ^ (h >> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int v3_eq(const float* a, const float* b) {
|
||||||
|
const uint32_t* ua = (const uint32_t*)a;
|
||||||
|
const uint32_t* ub = (const uint32_t*)b;
|
||||||
|
return (ua[0] == ub[0]) && (ua[1] == ub[1]) && (ua[2] == ub[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT int weld_vertices_c(
|
||||||
|
const float* in_verts,
|
||||||
|
int num_in_verts,
|
||||||
|
float* out_unique_verts,
|
||||||
|
uint32_t* out_inv_indices
|
||||||
|
) {
|
||||||
|
if (num_in_verts <= 0) return 0;
|
||||||
|
|
||||||
|
uint32_t table_size = 1024;
|
||||||
|
while (table_size < (uint32_t)num_in_verts * 2) {
|
||||||
|
table_size <<= 1;
|
||||||
|
}
|
||||||
|
uint32_t mask = table_size - 1;
|
||||||
|
|
||||||
|
uint32_t* table = (uint32_t*)calloc(table_size, sizeof(uint32_t));
|
||||||
|
if (!table) return -1;
|
||||||
|
|
||||||
|
int unique_count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_in_verts; ++i) {
|
||||||
|
const float* v = in_verts + (i * 3);
|
||||||
|
uint64_t h = hash_v3(v);
|
||||||
|
uint32_t slot = (uint32_t)(h & mask);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
uint32_t entry = table[slot];
|
||||||
|
if (entry == 0) {
|
||||||
|
int uid = unique_count++;
|
||||||
|
out_unique_verts[uid * 3 + 0] = v[0];
|
||||||
|
out_unique_verts[uid * 3 + 1] = v[1];
|
||||||
|
out_unique_verts[uid * 3 + 2] = v[2];
|
||||||
|
table[slot] = (uint32_t)(uid + 1);
|
||||||
|
out_inv_indices[i] = (uint32_t)uid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t uid = entry - 1;
|
||||||
|
const float* existing_v = out_unique_verts + (uid * 3);
|
||||||
|
if (v3_eq(v, existing_v)) {
|
||||||
|
out_inv_indices[i] = (uint32_t)uid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
slot = (slot + 1) & mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(table);
|
||||||
|
return unique_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT double polygon_signed_area_2d_c(const double* pts, int N, int stride) {
|
||||||
|
if (N < 3) return 0.0;
|
||||||
|
double area = pts[(N - 1) * stride + 0] * pts[1] - pts[0] * pts[(N - 1) * stride + 1];
|
||||||
|
for (int i = 0; i < N - 1; ++i) {
|
||||||
|
area += pts[i * stride + 0] * pts[(i + 1) * stride + 1] - pts[(i + 1) * stride + 0] * pts[i * stride + 1];
|
||||||
|
}
|
||||||
|
return area * 0.5;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,16 +118,17 @@ class SLICENODE_SliceShells(SliceNode):
|
||||||
if merged_object.mesh is None or merged_object.mesh.size == 0:
|
if merged_object.mesh is None or merged_object.mesh.size == 0:
|
||||||
return SlicePaths()
|
return SlicePaths()
|
||||||
|
|
||||||
if rotation and any(rotation):
|
has_rot = bool(rotation and any(rotation))
|
||||||
|
if has_rot:
|
||||||
from mathutils import Euler
|
from mathutils import Euler
|
||||||
mat = np.array(Euler(rotation).to_matrix().inverted())
|
mat = np.array(Euler(rotation).to_matrix().inverted())
|
||||||
merged_object.mesh[:, :3, :] = merged_object.mesh[:, :3, :] @ mat.T
|
merged_object.mesh[:, :3, :] = merged_object.mesh[:, :3, :] @ mat.T
|
||||||
merged_object.mesh[:, 3:4, :] = merged_object.mesh[:, 3:4, :] @ mat.T
|
merged_object.mesh[:, 3:4, :] = merged_object.mesh[:, 3:4, :] @ mat.T
|
||||||
|
|
||||||
min_z = float(merged_object.mesh[:, 0:3, 2:3].min())
|
min_z = float(merged_object.mesh[:, :3, 2].min())
|
||||||
if abs(min_z) < 1e-6:
|
if abs(min_z) < 1e-6:
|
||||||
min_z = 0.0
|
min_z = 0.0
|
||||||
max_z = float(merged_object.mesh[:, 0:3, 2:3].max())
|
max_z = float(merged_object.mesh[:, :3, 2].max())
|
||||||
|
|
||||||
h_first = fl_height if fl_height is not None else layer_height
|
h_first = fl_height if fl_height is not None else layer_height
|
||||||
|
|
||||||
|
|
@ -141,8 +142,8 @@ class SLICENODE_SliceShells(SliceNode):
|
||||||
|
|
||||||
if z_first_um <= max_z_um:
|
if z_first_um <= max_z_um:
|
||||||
if z_rest_start_um <= max_z_um and lh_um > 0:
|
if z_rest_start_um <= max_z_um and lh_um > 0:
|
||||||
z_um_values = list(range(z_rest_start_um, max_z_um + 1, lh_um))
|
z_um_values = np.arange(z_rest_start_um, max_z_um + 1, lh_um, dtype=np.int64)
|
||||||
z_values = np.array([z_first_um] + z_um_values, dtype=np.float64) / 1000.0
|
z_values = np.concatenate(([z_first_um], z_um_values)) / 1000.0
|
||||||
else:
|
else:
|
||||||
z_values = np.array([z_first_um], dtype=np.float64) / 1000.0
|
z_values = np.array([z_first_um], dtype=np.float64) / 1000.0
|
||||||
else:
|
else:
|
||||||
|
|
@ -197,13 +198,14 @@ class SLICENODE_SliceShells(SliceNode):
|
||||||
for i, p in enumerate(slices):
|
for i, p in enumerate(slices):
|
||||||
if len(p) > 0 and int(round(p[0, 2] * 1000)) == z_first_um:
|
if len(p) > 0 and int(round(p[0, 2] * 1000)) == z_first_um:
|
||||||
slices.extrusion_rate[i] = slices.extrusion_rate[i] * mult
|
slices.extrusion_rate[i] = slices.extrusion_rate[i] * mult
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
if rotation and any(rotation):
|
if has_rot:
|
||||||
from mathutils import Euler
|
from mathutils import Euler
|
||||||
mat_inv = np.array(Euler(rotation).to_matrix())
|
mat_inv = np.array(Euler(rotation).to_matrix())
|
||||||
slices = slices.transform(mat_inv)
|
slices = slices.transform(mat_inv)
|
||||||
|
slices = slices.align_seams(mode="sharpest")
|
||||||
slices = slices.align_seams(mode="sharpest")
|
|
||||||
|
|
||||||
return slices
|
return slices
|
||||||
|
|
||||||
|
|
|
||||||
216
core/slice.py
216
core/slice.py
|
|
@ -113,7 +113,11 @@ try:
|
||||||
_so_dir = os.path.dirname(os.path.abspath(__file__))
|
_so_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
_so_path = os.path.join(_so_dir, "libseam_opt.so")
|
_so_path = os.path.join(_so_dir, "libseam_opt.so")
|
||||||
_c_src = os.path.join(_so_dir, "c_seam.c")
|
_c_src = os.path.join(_so_dir, "c_seam.c")
|
||||||
if not os.path.exists(_so_path) and os.path.exists(_c_src):
|
rebuild = False
|
||||||
|
if os.path.exists(_c_src):
|
||||||
|
if not os.path.exists(_so_path) or os.path.getmtime(_so_path) < os.path.getmtime(_c_src):
|
||||||
|
rebuild = True
|
||||||
|
if rebuild:
|
||||||
import subprocess
|
import subprocess
|
||||||
for compiler in ("gcc", "clang"):
|
for compiler in ("gcc", "clang"):
|
||||||
try:
|
try:
|
||||||
|
|
@ -131,10 +135,61 @@ try:
|
||||||
ctypes.c_double
|
ctypes.c_double
|
||||||
]
|
]
|
||||||
_c_seam_lib.find_sharpest_seam_c.restype = ctypes.c_int
|
_c_seam_lib.find_sharpest_seam_c.restype = ctypes.c_int
|
||||||
|
|
||||||
|
if hasattr(_c_seam_lib, "weld_vertices_c"):
|
||||||
|
_c_seam_lib.weld_vertices_c.argtypes = [
|
||||||
|
ctypes.POINTER(ctypes.c_float),
|
||||||
|
ctypes.c_int,
|
||||||
|
ctypes.POINTER(ctypes.c_float),
|
||||||
|
ctypes.POINTER(ctypes.c_uint32)
|
||||||
|
]
|
||||||
|
_c_seam_lib.weld_vertices_c.restype = ctypes.c_int
|
||||||
|
|
||||||
|
if hasattr(_c_seam_lib, "polygon_signed_area_2d_c"):
|
||||||
|
_c_seam_lib.polygon_signed_area_2d_c.argtypes = [
|
||||||
|
ctypes.POINTER(ctypes.c_double),
|
||||||
|
ctypes.c_int,
|
||||||
|
ctypes.c_int
|
||||||
|
]
|
||||||
|
_c_seam_lib.polygon_signed_area_2d_c.restype = ctypes.c_double
|
||||||
except Exception:
|
except Exception:
|
||||||
_c_seam_lib = None
|
_c_seam_lib = None
|
||||||
|
|
||||||
|
|
||||||
|
def weld_vertices(tris: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||||
|
"""
|
||||||
|
Given an array of triangles of shape (F, 3, 3) or (F, 4, 3), welds identical vertices.
|
||||||
|
Returns (unique_verts, faces) where unique_verts is (V, 3) float32 and faces is (F, 3) uint32.
|
||||||
|
"""
|
||||||
|
flat = np.ascontiguousarray(tris[:, :3, :].reshape(-1, 3), dtype=np.float32)
|
||||||
|
num_verts = len(flat)
|
||||||
|
if num_verts == 0:
|
||||||
|
return np.empty((0, 3), dtype=np.float32), np.empty((0, 3), dtype=np.uint32)
|
||||||
|
|
||||||
|
if _c_seam_lib is not None and hasattr(_c_seam_lib, "weld_vertices_c"):
|
||||||
|
try:
|
||||||
|
out_unique = np.empty((num_verts, 3), dtype=np.float32)
|
||||||
|
out_inv = np.empty(num_verts, dtype=np.uint32)
|
||||||
|
unique_count = int(_c_seam_lib.weld_vertices_c(
|
||||||
|
flat.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
||||||
|
num_verts,
|
||||||
|
out_unique.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
||||||
|
out_inv.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))
|
||||||
|
))
|
||||||
|
if unique_count >= 0:
|
||||||
|
return out_unique[:unique_count], out_inv.reshape(-1, 3)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# NumPy fallback
|
||||||
|
void_dtype = np.dtype((np.void, flat.dtype.itemsize * flat.shape[1]))
|
||||||
|
flat_void = flat.view(void_dtype).ravel()
|
||||||
|
unique_voids, inv = np.unique(flat_void, return_inverse=True)
|
||||||
|
unique_verts = unique_voids.view(flat.dtype).reshape(-1, 3)
|
||||||
|
faces = inv.reshape(-1, 3).astype(np.uint32)
|
||||||
|
return unique_verts, faces
|
||||||
|
|
||||||
|
|
||||||
def align_path_seam_sharpest(path: np.ndarray) -> np.ndarray:
|
def align_path_seam_sharpest(path: np.ndarray) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Reorders a closed 2D/3D polygon path (N, 3) or (N, 2) so that its starting vertex (seam)
|
Reorders a closed 2D/3D polygon path (N, 3) or (N, 2) so that its starting vertex (seam)
|
||||||
|
|
@ -657,44 +712,53 @@ class SlicePaths(list[np.ndarray]):
|
||||||
new_extra[k].append(self.extra_attributes[k][i])
|
new_extra[k].append(self.extra_attributes[k][i])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pts_um = np.round(pts[:, :2] * SCALE).astype(np.int64)
|
if _c_seam_lib is not None:
|
||||||
x = pts_um[:, 0]
|
try:
|
||||||
y = pts_um[:, 1]
|
c_pts = np.ascontiguousarray(pts, dtype=np.float64)
|
||||||
x_next = np.roll(x, -1)
|
stride = int(c_pts.shape[1])
|
||||||
y_next = np.roll(y, -1)
|
ptr = c_pts.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||||
signed_area_um = np.sum(x * y_next - x_next * y)
|
best_idx = int(_c_seam_lib.find_sharpest_seam_c(ptr, M, stride, float(SCALE)))
|
||||||
|
except Exception:
|
||||||
prev_pts = np.roll(pts_um, 1, axis=0)
|
best_idx = 0
|
||||||
next_pts = np.roll(pts_um, -1, axis=0)
|
|
||||||
|
|
||||||
v_in = pts_um - prev_pts
|
|
||||||
v_out = next_pts - pts_um
|
|
||||||
|
|
||||||
l2_in = np.sum(v_in**2, axis=1)
|
|
||||||
l2_out = np.sum(v_out**2, axis=1)
|
|
||||||
|
|
||||||
valid = (l2_in > 0) & (l2_out > 0)
|
|
||||||
if not np.any(valid) or signed_area_um == 0:
|
|
||||||
best_idx = 0
|
|
||||||
else:
|
else:
|
||||||
cross_2d = v_in[:, 0] * v_out[:, 1] - v_in[:, 1] * v_out[:, 0]
|
pts_um = np.round(pts[:, :2] * SCALE).astype(np.int64)
|
||||||
if signed_area_um > 0:
|
x = pts_um[:, 0]
|
||||||
concave_mask = valid & (cross_2d < 0)
|
y = pts_um[:, 1]
|
||||||
|
x_next = np.roll(x, -1)
|
||||||
|
y_next = np.roll(y, -1)
|
||||||
|
signed_area_um = np.sum(x * y_next - x_next * y)
|
||||||
|
|
||||||
|
prev_pts = np.roll(pts_um, 1, axis=0)
|
||||||
|
next_pts = np.roll(pts_um, -1, axis=0)
|
||||||
|
|
||||||
|
v_in = pts_um - prev_pts
|
||||||
|
v_out = next_pts - pts_um
|
||||||
|
|
||||||
|
l2_in = np.sum(v_in**2, axis=1)
|
||||||
|
l2_out = np.sum(v_out**2, axis=1)
|
||||||
|
|
||||||
|
valid = (l2_in > 0) & (l2_out > 0)
|
||||||
|
if not np.any(valid) or signed_area_um == 0:
|
||||||
|
best_idx = 0
|
||||||
else:
|
else:
|
||||||
concave_mask = valid & (cross_2d > 0)
|
cross_2d = v_in[:, 0] * v_out[:, 1] - v_in[:, 1] * v_out[:, 0]
|
||||||
|
if signed_area_um > 0:
|
||||||
|
concave_mask = valid & (cross_2d < 0)
|
||||||
|
else:
|
||||||
|
concave_mask = valid & (cross_2d > 0)
|
||||||
|
|
||||||
l_in = np.sqrt(l2_in.astype(np.float64))
|
l_in = np.sqrt(l2_in.astype(np.float64))
|
||||||
l_out = np.sqrt(l2_out.astype(np.float64))
|
l_out = np.sqrt(l2_out.astype(np.float64))
|
||||||
|
|
||||||
cos_angles = np.full(M, 1.0)
|
cos_angles = np.full(M, 1.0)
|
||||||
dot_prods = np.sum(v_in[valid] * v_out[valid], axis=1).astype(np.float64)
|
dot_prods = np.sum(v_in[valid] * v_out[valid], axis=1).astype(np.float64)
|
||||||
cos_angles[valid] = dot_prods / (l_in[valid] * l_out[valid])
|
cos_angles[valid] = dot_prods / (l_in[valid] * l_out[valid])
|
||||||
|
|
||||||
if np.any(concave_mask):
|
if np.any(concave_mask):
|
||||||
concave_indices = np.where(concave_mask)[0]
|
concave_indices = np.where(concave_mask)[0]
|
||||||
best_idx = int(concave_indices[np.argmin(cos_angles[concave_mask])])
|
best_idx = int(concave_indices[np.argmin(cos_angles[concave_mask])])
|
||||||
else:
|
else:
|
||||||
best_idx = int(np.argmin(cos_angles))
|
best_idx = int(np.argmin(cos_angles))
|
||||||
|
|
||||||
if best_idx == 0:
|
if best_idx == 0:
|
||||||
new_paths.append(p)
|
new_paths.append(p)
|
||||||
|
|
@ -703,25 +767,37 @@ class SlicePaths(list[np.ndarray]):
|
||||||
for k in self.extra_attributes:
|
for k in self.extra_attributes:
|
||||||
new_extra[k].append(self.extra_attributes[k][i])
|
new_extra[k].append(self.extra_attributes[k][i])
|
||||||
else:
|
else:
|
||||||
new_p = np.vstack([p[best_idx:-1], p[:best_idx + 1]])
|
new_p = np.empty_like(p)
|
||||||
|
split = M - best_idx
|
||||||
|
new_p[:split] = p[best_idx:-1]
|
||||||
|
new_p[split:] = p[:best_idx + 1]
|
||||||
new_paths.append(new_p)
|
new_paths.append(new_p)
|
||||||
|
|
||||||
ext = self.extrusion_rate[i]
|
ext = self.extrusion_rate[i]
|
||||||
if len(ext) == len(p):
|
if len(ext) == len(p):
|
||||||
new_ext.append(np.concatenate([ext[best_idx:-1], ext[:best_idx + 1]]))
|
new_e = np.empty_like(ext)
|
||||||
|
new_e[:split] = ext[best_idx:-1]
|
||||||
|
new_e[split:] = ext[:best_idx + 1]
|
||||||
|
new_ext.append(new_e)
|
||||||
else:
|
else:
|
||||||
new_ext.append(ext)
|
new_ext.append(ext)
|
||||||
|
|
||||||
feed = self.feedrate[i]
|
feed = self.feedrate[i]
|
||||||
if len(feed) == len(p):
|
if len(feed) == len(p):
|
||||||
new_feed.append(np.concatenate([feed[best_idx:-1], feed[:best_idx + 1]]))
|
new_f = np.empty_like(feed)
|
||||||
|
new_f[:split] = feed[best_idx:-1]
|
||||||
|
new_f[split:] = feed[:best_idx + 1]
|
||||||
|
new_feed.append(new_f)
|
||||||
else:
|
else:
|
||||||
new_feed.append(feed)
|
new_feed.append(feed)
|
||||||
|
|
||||||
for k in self.extra_attributes:
|
for k in self.extra_attributes:
|
||||||
attr = self.extra_attributes[k][i]
|
attr = self.extra_attributes[k][i]
|
||||||
if len(attr) == len(p):
|
if len(attr) == len(p):
|
||||||
new_extra[k].append(np.concatenate([attr[best_idx:-1], attr[:best_idx + 1]]))
|
new_a = np.empty_like(attr)
|
||||||
|
new_a[:split] = attr[best_idx:-1]
|
||||||
|
new_a[split:] = attr[:best_idx + 1]
|
||||||
|
new_extra[k].append(new_a)
|
||||||
else:
|
else:
|
||||||
new_extra[k].append(attr)
|
new_extra[k].append(attr)
|
||||||
|
|
||||||
|
|
@ -740,13 +816,9 @@ def _slice_single_mesh(mesh_array: np.ndarray, z_values: np.ndarray | list[float
|
||||||
if len(z_arr) == 0:
|
if len(z_arr) == 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
tris = mesh_array[:, :3, :] # Shape (F, 3, 3)
|
unique_verts, faces = weld_vertices(mesh_array)
|
||||||
flat = np.ascontiguousarray(tris.reshape(-1, 3), dtype=np.float32)
|
if len(unique_verts) == 0:
|
||||||
void_dtype = np.dtype((np.void, flat.dtype.itemsize * flat.shape[1]))
|
return []
|
||||||
flat_void = flat.view(void_dtype).ravel()
|
|
||||||
unique_voids, inv = np.unique(flat_void, return_inverse=True)
|
|
||||||
unique_verts = unique_voids.view(flat.dtype).reshape(-1, 3)
|
|
||||||
faces = inv.reshape(-1, 3).astype(np.uint32)
|
|
||||||
|
|
||||||
all_paths: list[np.ndarray] = []
|
all_paths: list[np.ndarray] = []
|
||||||
|
|
||||||
|
|
@ -783,11 +855,17 @@ def _slice_single_mesh(mesh_array: np.ndarray, z_values: np.ndarray | list[float
|
||||||
if N < 3:
|
if N < 3:
|
||||||
continue
|
continue
|
||||||
if ignore_holes:
|
if ignore_holes:
|
||||||
p_x = poly[:, 0]
|
if _c_seam_lib is not None and hasattr(_c_seam_lib, "polygon_signed_area_2d_c"):
|
||||||
p_y = poly[:, 1]
|
poly_c = np.ascontiguousarray(poly, dtype=np.float64)
|
||||||
signed_area = np.sum(p_x[:-1] * p_y[1:] - p_x[1:] * p_y[:-1]) + (p_x[-1] * p_y[0] - p_x[0] * p_y[-1])
|
area = _c_seam_lib.polygon_signed_area_2d_c(poly_c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), N, 2)
|
||||||
if signed_area <= 0:
|
if area <= 0:
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
p_x = poly[:, 0]
|
||||||
|
p_y = poly[:, 1]
|
||||||
|
signed_area = np.sum(p_x[:-1] * p_y[1:] - p_x[1:] * p_y[:-1]) + (p_x[-1] * p_y[0] - p_x[0] * p_y[-1])
|
||||||
|
if signed_area <= 0:
|
||||||
|
continue
|
||||||
p3d = np.empty((N + 1, 3), dtype=np.float64)
|
p3d = np.empty((N + 1, 3), dtype=np.float64)
|
||||||
p3d[:N, :2] = poly
|
p3d[:N, :2] = poly
|
||||||
p3d[:N, 2] = zf
|
p3d[:N, 2] = zf
|
||||||
|
|
@ -849,13 +927,15 @@ def link_segments(segments: np.ndarray) -> list[np.ndarray]:
|
||||||
z = segments[0, 0, 2]
|
z = segments[0, 0, 2]
|
||||||
pts = segments[:, :, :2]
|
pts = segments[:, :, :2]
|
||||||
|
|
||||||
# Quantize to integer micron tuples: (x_um, y_um)
|
# Quantize to integer micron: (x_um, y_um) packed into 64-bit scalar key
|
||||||
pts_um = np.round(pts * SCALE).astype(np.int64)
|
pts_um = np.round(pts * SCALE).astype(np.int64)
|
||||||
|
k1_arr = (pts_um[:, 0, 0] << 32) | (pts_um[:, 0, 1] & 0xFFFFFFFF)
|
||||||
adj: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = {}
|
k2_arr = (pts_um[:, 1, 0] << 32) | (pts_um[:, 1, 1] & 0xFFFFFFFF)
|
||||||
|
|
||||||
|
adj: dict[int, list[tuple[int, int]]] = {}
|
||||||
for i in range(len(segments)):
|
for i in range(len(segments)):
|
||||||
k1 = (int(pts_um[i, 0, 0]), int(pts_um[i, 0, 1]))
|
k1 = int(k1_arr[i])
|
||||||
k2 = (int(pts_um[i, 1, 0]), int(pts_um[i, 1, 1]))
|
k2 = int(k2_arr[i])
|
||||||
if k1 == k2:
|
if k1 == k2:
|
||||||
continue
|
continue
|
||||||
adj.setdefault(k1, []).append((k2, i))
|
adj.setdefault(k1, []).append((k2, i))
|
||||||
|
|
@ -866,12 +946,13 @@ def link_segments(segments: np.ndarray) -> list[np.ndarray]:
|
||||||
for i in range(len(segments)):
|
for i in range(len(segments)):
|
||||||
if i in used:
|
if i in used:
|
||||||
continue
|
continue
|
||||||
k1 = (int(pts_um[i, 0, 0]), int(pts_um[i, 0, 1]))
|
k1 = int(k1_arr[i])
|
||||||
k2 = (int(pts_um[i, 1, 0]), int(pts_um[i, 1, 1]))
|
k2 = int(k2_arr[i])
|
||||||
if k1 == k2:
|
if k1 == k2:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
path_pts = [pts[i, 0], pts[i, 1]]
|
fwd_pts = [pts[i, 0], pts[i, 1]]
|
||||||
|
bwd_pts = []
|
||||||
used.add(i)
|
used.add(i)
|
||||||
|
|
||||||
# Forward
|
# Forward
|
||||||
|
|
@ -880,9 +961,9 @@ def link_segments(segments: np.ndarray) -> list[np.ndarray]:
|
||||||
found = False
|
found = False
|
||||||
for next_k, idx in adj.get(curr_k, []):
|
for next_k, idx in adj.get(curr_k, []):
|
||||||
if idx not in used:
|
if idx not in used:
|
||||||
pk1 = (int(pts_um[idx, 0, 0]), int(pts_um[idx, 0, 1]))
|
pk1 = int(k1_arr[idx])
|
||||||
other_pt = pts[idx, 1] if pk1 == curr_k else pts[idx, 0]
|
other_pt = pts[idx, 1] if pk1 == curr_k else pts[idx, 0]
|
||||||
path_pts.append(other_pt)
|
fwd_pts.append(other_pt)
|
||||||
used.add(idx)
|
used.add(idx)
|
||||||
curr_k = next_k
|
curr_k = next_k
|
||||||
found = True
|
found = True
|
||||||
|
|
@ -890,15 +971,15 @@ def link_segments(segments: np.ndarray) -> list[np.ndarray]:
|
||||||
if not found:
|
if not found:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Backward
|
# Backward (O(1) append to bwd_pts)
|
||||||
curr_k = k1
|
curr_k = k1
|
||||||
while True:
|
while True:
|
||||||
found = False
|
found = False
|
||||||
for next_k, idx in adj.get(curr_k, []):
|
for next_k, idx in adj.get(curr_k, []):
|
||||||
if idx not in used:
|
if idx not in used:
|
||||||
pk1 = (int(pts_um[idx, 0, 0]), int(pts_um[idx, 0, 1]))
|
pk1 = int(k1_arr[idx])
|
||||||
other_pt = pts[idx, 1] if pk1 == curr_k else pts[idx, 0]
|
other_pt = pts[idx, 1] if pk1 == curr_k else pts[idx, 0]
|
||||||
path_pts.insert(0, other_pt)
|
bwd_pts.append(other_pt)
|
||||||
used.add(idx)
|
used.add(idx)
|
||||||
curr_k = next_k
|
curr_k = next_k
|
||||||
found = True
|
found = True
|
||||||
|
|
@ -906,12 +987,15 @@ def link_segments(segments: np.ndarray) -> list[np.ndarray]:
|
||||||
if not found:
|
if not found:
|
||||||
break
|
break
|
||||||
|
|
||||||
p_arr = np.array(path_pts)
|
all_pts = bwd_pts[::-1] + fwd_pts if bwd_pts else fwd_pts
|
||||||
|
p_arr = np.empty((len(all_pts), 3), dtype=np.float64)
|
||||||
|
p_arr[:, :2] = all_pts
|
||||||
|
p_arr[:, 2] = z
|
||||||
|
|
||||||
if len(p_arr) > 2 and is_closed_path(p_arr):
|
if len(p_arr) > 2 and is_closed_path(p_arr):
|
||||||
p_arr[-1] = p_arr[0]
|
p_arr[-1] = p_arr[0]
|
||||||
|
|
||||||
res = np.column_stack([p_arr, np.full(len(p_arr), z)])
|
paths.append(p_arr)
|
||||||
paths.append(res)
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
@ndarray_lru_cache(maxsize=128)
|
@ndarray_lru_cache(maxsize=128)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue