seams on the back
|
|
@ -12,67 +12,60 @@
|
||||||
EXPORT int find_sharpest_seam_c(const double* pts, int M, int stride, double scale) {
|
EXPORT int find_sharpest_seam_c(const double* pts, int M, int stride, double scale) {
|
||||||
if (M < 3) return 0;
|
if (M < 3) return 0;
|
||||||
|
|
||||||
int64_t x_buf[1024];
|
// Find Y bounding range
|
||||||
int64_t y_buf[1024];
|
double min_y = pts[1];
|
||||||
int64_t* x = (M <= 1024) ? x_buf : (int64_t*)malloc(M * sizeof(int64_t));
|
double max_y = pts[1];
|
||||||
int64_t* y = (M <= 1024) ? y_buf : (int64_t*)malloc(M * sizeof(int64_t));
|
for (int i = 1; i < M; ++i) {
|
||||||
|
double y = pts[i * stride + 1];
|
||||||
for (int i = 0; i < M; ++i) {
|
if (y < min_y) min_y = y;
|
||||||
x[i] = (int64_t)llround(pts[i * stride + 0] * scale);
|
if (y > max_y) max_y = y;
|
||||||
y[i] = (int64_t)llround(pts[i * stride + 1] * scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t signed_area = (x[M - 1] * y[0] - x[0] * y[M - 1]);
|
|
||||||
for (int i = 0; i < M - 1; ++i) {
|
|
||||||
signed_area += (x[i] * y[i + 1] - x[i + 1] * y[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signed_area == 0) {
|
|
||||||
if (M > 1024) { free(x); free(y); }
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
double y_range = max_y - min_y;
|
||||||
|
double corner_bonus = (y_range > 0.0) ? (0.1 * y_range < 2.0 ? 0.1 * y_range : 2.0) : 2.0;
|
||||||
|
|
||||||
int best_idx = 0;
|
int best_idx = 0;
|
||||||
double min_cos_concave = 2.0;
|
double best_score = -1e30;
|
||||||
int best_concave_idx = -1;
|
double best_x = 1e30;
|
||||||
double min_cos_overall = 2.0;
|
|
||||||
|
|
||||||
for (int i = 0; i < M; ++i) {
|
for (int i = 0; i < M; ++i) {
|
||||||
int prev = (i == 0) ? M - 1 : i - 1;
|
int prev = (i == 0) ? M - 1 : i - 1;
|
||||||
int next = (i == M - 1) ? 0 : i + 1;
|
int next = (i == M - 1) ? 0 : i + 1;
|
||||||
|
|
||||||
int64_t vin_x = x[i] - x[prev];
|
double xi = pts[i * stride + 0];
|
||||||
int64_t vin_y = y[i] - y[prev];
|
double yi = pts[i * stride + 1];
|
||||||
int64_t vout_x = x[next] - x[i];
|
|
||||||
int64_t vout_y = y[next] - y[i];
|
|
||||||
|
|
||||||
int64_t l2_in = vin_x * vin_x + vin_y * vin_y;
|
double vin_x = xi - pts[prev * stride + 0];
|
||||||
int64_t l2_out = vout_x * vout_x + vout_y * vout_y;
|
double vin_y = yi - pts[prev * stride + 1];
|
||||||
|
double vout_x = pts[next * stride + 0] - xi;
|
||||||
|
double vout_y = pts[next * stride + 1] - yi;
|
||||||
|
|
||||||
if (l2_in == 0 || l2_out == 0) continue;
|
double l_in = sqrt(vin_x * vin_x + vin_y * vin_y);
|
||||||
|
double l_out = sqrt(vout_x * vout_x + vout_y * vout_y);
|
||||||
|
|
||||||
int64_t cross = vin_x * vout_y - vin_y * vout_x;
|
double sharpness = 0.0;
|
||||||
int is_concave = (signed_area > 0) ? (cross < 0) : (cross > 0);
|
if (l_in > 1e-7 && l_out > 1e-7) {
|
||||||
|
double dot = vin_x * vout_x + vin_y * vout_y;
|
||||||
double dot = (double)(vin_x * vout_x + vin_y * vout_y);
|
double cos_ang = dot / (l_in * l_out);
|
||||||
double denom = sqrt((double)l2_in * (double)l2_out);
|
if (cos_ang > 1.0) cos_ang = 1.0;
|
||||||
double cos_ang = dot / denom;
|
if (cos_ang < -1.0) cos_ang = -1.0;
|
||||||
|
sharpness = 1.0 - cos_ang; // 0 for straight line, 1 for 90 deg corner, 2 for 180 deg turnaround
|
||||||
if (is_concave) {
|
|
||||||
if (cos_ang < min_cos_concave) {
|
|
||||||
min_cos_concave = cos_ang;
|
|
||||||
best_concave_idx = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cos_ang < min_cos_overall) {
|
|
||||||
min_cos_overall = cos_ang;
|
double score = yi + sharpness * corner_bonus;
|
||||||
|
|
||||||
|
// Highest score wins (back of the object). Tie-break: smallest X for deterministic consistency across layers
|
||||||
|
if (score > best_score + 1e-6) {
|
||||||
|
best_score = score;
|
||||||
|
best_x = xi;
|
||||||
best_idx = i;
|
best_idx = i;
|
||||||
|
} else if (fabs(score - best_score) <= 1e-6) {
|
||||||
|
if (xi < best_x) {
|
||||||
|
best_x = xi;
|
||||||
|
best_idx = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (M > 1024) { free(x); free(y); }
|
|
||||||
|
|
||||||
if (best_concave_idx >= 0) return best_concave_idx;
|
|
||||||
return best_idx;
|
return best_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
179
core/slice.py
|
|
@ -190,89 +190,68 @@ def weld_vertices(tris: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||||
return unique_verts, faces
|
return unique_verts, faces
|
||||||
|
|
||||||
|
|
||||||
def align_path_seam_sharpest(path: np.ndarray) -> np.ndarray:
|
def find_path_seam(path: np.ndarray) -> int:
|
||||||
"""
|
"""
|
||||||
Reorders a closed 2D/3D polygon path (N, 3) or (N, 2) so that its starting vertex (seam)
|
Finds the optimal starting vertex index (seam) positioned at the back of the object (+Y).
|
||||||
is located at the sharpest concave angle (inner corner). If no concave angles exist,
|
Uses a weighted heuristic prioritizing maximum Y with a sharpness bonus for corners,
|
||||||
falls back to the sharpest angle overall.
|
with deterministic tie-breaking for consistent vertical layer alignment.
|
||||||
|
|
||||||
If path is open or has fewer than 3 distinct vertices, returns path unchanged.
|
|
||||||
Uses exact 1-micron integer arithmetic for orientation and angle selection.
|
|
||||||
"""
|
"""
|
||||||
if len(path) < 4:
|
if len(path) < 4 or not is_closed_path(path):
|
||||||
return path
|
return 0
|
||||||
|
|
||||||
if not is_closed_path(path):
|
|
||||||
return path
|
|
||||||
|
|
||||||
pts = path[:-1]
|
pts = path[:-1]
|
||||||
M = len(pts)
|
M = len(pts)
|
||||||
if M < 3:
|
if M < 3:
|
||||||
return path
|
return 0
|
||||||
|
|
||||||
if _c_seam_lib is not None:
|
if _c_seam_lib is not None:
|
||||||
try:
|
try:
|
||||||
c_pts = np.ascontiguousarray(pts, dtype=np.float64)
|
c_pts = np.ascontiguousarray(pts, dtype=np.float64)
|
||||||
stride = int(c_pts.shape[1])
|
stride = int(c_pts.shape[1])
|
||||||
ptr = c_pts.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
ptr = c_pts.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||||
best_idx = int(_c_seam_lib.find_sharpest_seam_c(ptr, M, stride, float(SCALE)))
|
return int(_c_seam_lib.find_sharpest_seam_c(ptr, M, stride, float(SCALE)))
|
||||||
if best_idx == 0:
|
|
||||||
return path
|
|
||||||
res = np.empty_like(path)
|
|
||||||
split = M - best_idx
|
|
||||||
res[:split] = path[best_idx:-1]
|
|
||||||
res[split:] = path[:best_idx + 1]
|
|
||||||
return res
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pts_um = np.rint(pts[:, :2] * SCALE).astype(np.int64)
|
# NumPy fallback
|
||||||
x = pts_um[:, 0]
|
x = pts[:, 0]
|
||||||
y = pts_um[:, 1]
|
y = pts[:, 1]
|
||||||
signed_area_um = np.sum(x[:-1] * y[1:] - x[1:] * y[:-1]) + (x[-1] * y[0] - x[0] * y[-1])
|
y_min, y_max = float(np.min(y)), float(np.max(y))
|
||||||
if signed_area_um == 0:
|
y_range = y_max - y_min
|
||||||
|
bonus = min(2.0, 0.1 * y_range) if y_range > 0 else 2.0
|
||||||
|
|
||||||
|
prev_pts = np.roll(pts[:, :2], 1, axis=0)
|
||||||
|
next_pts = np.roll(pts[:, :2], -1, axis=0)
|
||||||
|
|
||||||
|
v_in = pts[:, :2] - prev_pts
|
||||||
|
v_out = next_pts - pts[:, :2]
|
||||||
|
|
||||||
|
l_in = np.linalg.norm(v_in, axis=1)
|
||||||
|
l_out = np.linalg.norm(v_out, axis=1)
|
||||||
|
|
||||||
|
valid = (l_in > 1e-7) & (l_out > 1e-7)
|
||||||
|
cos_ang = np.ones(M, dtype=np.float64)
|
||||||
|
cos_ang[valid] = np.sum(v_in[valid] * v_out[valid], axis=1) / (l_in[valid] * l_out[valid])
|
||||||
|
cos_ang = np.clip(cos_ang, -1.0, 1.0)
|
||||||
|
sharpness = 1.0 - cos_ang
|
||||||
|
|
||||||
|
scores = y + sharpness * bonus
|
||||||
|
# Deterministic tie-break: highest score, smallest x
|
||||||
|
return int(np.lexsort((x, -scores))[0])
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
is located at the back of the object (highest Y with corner preference).
|
||||||
|
|
||||||
|
If path is open or has fewer than 3 distinct vertices, returns path unchanged.
|
||||||
|
"""
|
||||||
|
if len(path) < 4 or not is_closed_path(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
v_in = np.empty((M, 2), dtype=np.int64)
|
M = len(path) - 1
|
||||||
v_in[0, 0] = x[0] - x[-1]
|
best_idx = find_path_seam(path)
|
||||||
v_in[0, 1] = y[0] - y[-1]
|
|
||||||
v_in[1:, 0] = x[1:] - x[:-1]
|
|
||||||
v_in[1:, 1] = y[1:] - y[:-1]
|
|
||||||
|
|
||||||
l2_in = v_in[:, 0]**2 + v_in[:, 1]**2
|
|
||||||
valid_in = l2_in > 0
|
|
||||||
valid_out = np.empty_like(valid_in)
|
|
||||||
valid_out[:-1] = valid_in[1:]
|
|
||||||
valid_out[-1] = valid_in[0]
|
|
||||||
valid = valid_in & valid_out
|
|
||||||
if not np.any(valid):
|
|
||||||
return path
|
|
||||||
|
|
||||||
cross_2d = np.empty(M, dtype=np.int64)
|
|
||||||
cross_2d[:-1] = v_in[:-1, 0] * v_in[1:, 1] - v_in[:-1, 1] * v_in[1:, 0]
|
|
||||||
cross_2d[-1] = v_in[-1, 0] * v_in[0, 1] - v_in[-1, 1] * v_in[0, 0]
|
|
||||||
|
|
||||||
concave_mask = valid & (cross_2d < 0) if signed_area_um > 0 else valid & (cross_2d > 0)
|
|
||||||
|
|
||||||
l_in = np.sqrt(l2_in, dtype=np.float64)
|
|
||||||
l_out = np.empty_like(l_in)
|
|
||||||
l_out[:-1] = l_in[1:]
|
|
||||||
l_out[-1] = l_in[0]
|
|
||||||
|
|
||||||
dot_prods = np.empty(M, dtype=np.float64)
|
|
||||||
dot_prods[:-1] = v_in[:-1, 0] * v_in[1:, 0] + v_in[:-1, 1] * v_in[1:, 1]
|
|
||||||
dot_prods[-1] = v_in[-1, 0] * v_in[0, 0] + v_in[-1, 1] * v_in[0, 1]
|
|
||||||
|
|
||||||
cos_angles = np.full(M, 1.0)
|
|
||||||
denom = l_in[valid] * l_out[valid]
|
|
||||||
cos_angles[valid] = dot_prods[valid] / denom
|
|
||||||
|
|
||||||
if np.any(concave_mask):
|
|
||||||
concave_indices = np.where(concave_mask)[0]
|
|
||||||
best_idx = int(concave_indices[np.argmin(cos_angles[concave_mask])])
|
|
||||||
else:
|
|
||||||
best_idx = int(np.argmin(cos_angles))
|
|
||||||
|
|
||||||
if best_idx == 0:
|
if best_idx == 0:
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
@ -283,6 +262,7 @@ def align_path_seam_sharpest(path: np.ndarray) -> np.ndarray:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SlicePaths(list[np.ndarray]):
|
class SlicePaths(list[np.ndarray]):
|
||||||
groups: list[int]
|
groups: list[int]
|
||||||
extrusion_rate: list[np.ndarray]
|
extrusion_rate: list[np.ndarray]
|
||||||
|
|
@ -681,11 +661,10 @@ class SlicePaths(list[np.ndarray]):
|
||||||
|
|
||||||
def align_seams(self, mode: str = "sharpest") -> SlicePaths:
|
def align_seams(self, mode: str = "sharpest") -> SlicePaths:
|
||||||
"""
|
"""
|
||||||
Aligns the seams (starting point of closed paths) in SlicePaths.
|
Aligns the seams (starting point of closed paths) in SlicePaths to the back (+Y).
|
||||||
Supported modes: 'sharpest'.
|
Preserves all per-path and per-vertex attributes.
|
||||||
Uses 1-micron integer math.
|
|
||||||
"""
|
"""
|
||||||
if not self or mode != "sharpest":
|
if not self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
new_paths: list[np.ndarray] = []
|
new_paths: list[np.ndarray] = []
|
||||||
|
|
@ -702,9 +681,9 @@ 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 = p[:-1]
|
M = len(p) - 1
|
||||||
M = len(pts)
|
best_idx = find_path_seam(p)
|
||||||
if M < 3:
|
if best_idx == 0:
|
||||||
new_paths.append(p)
|
new_paths.append(p)
|
||||||
new_ext.append(self.extrusion_rate[i])
|
new_ext.append(self.extrusion_rate[i])
|
||||||
new_feed.append(self.feedrate[i])
|
new_feed.append(self.feedrate[i])
|
||||||
|
|
@ -712,60 +691,6 @@ 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
|
||||||
|
|
||||||
if _c_seam_lib is not None:
|
|
||||||
try:
|
|
||||||
c_pts = np.ascontiguousarray(pts, dtype=np.float64)
|
|
||||||
stride = int(c_pts.shape[1])
|
|
||||||
ptr = c_pts.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
|
||||||
best_idx = int(_c_seam_lib.find_sharpest_seam_c(ptr, M, stride, float(SCALE)))
|
|
||||||
except Exception:
|
|
||||||
best_idx = 0
|
|
||||||
else:
|
|
||||||
pts_um = np.round(pts[:, :2] * SCALE).astype(np.int64)
|
|
||||||
x = pts_um[:, 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:
|
|
||||||
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_out = np.sqrt(l2_out.astype(np.float64))
|
|
||||||
|
|
||||||
cos_angles = np.full(M, 1.0)
|
|
||||||
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])
|
|
||||||
|
|
||||||
if np.any(concave_mask):
|
|
||||||
concave_indices = np.where(concave_mask)[0]
|
|
||||||
best_idx = int(concave_indices[np.argmin(cos_angles[concave_mask])])
|
|
||||||
else:
|
|
||||||
best_idx = int(np.argmin(cos_angles))
|
|
||||||
|
|
||||||
if best_idx == 0:
|
|
||||||
new_paths.append(p)
|
|
||||||
new_ext.append(self.extrusion_rate[i])
|
|
||||||
new_feed.append(self.feedrate[i])
|
|
||||||
for k in self.extra_attributes:
|
|
||||||
new_extra[k].append(self.extra_attributes[k][i])
|
|
||||||
else:
|
else:
|
||||||
new_p = np.empty_like(p)
|
new_p = np.empty_like(p)
|
||||||
split = M - best_idx
|
split = M - best_idx
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ This file provides a mapping of modules to their primary responsibilities and ke
|
||||||
- `slice.py`:
|
- `slice.py`:
|
||||||
- `slice_at_range()`: Top-level slicing function that iterates through Z-heights using `manifold3d` for guaranteed watertight solid slicing (resolving coplanar and floating-point degeneracies) with a fallback to `trimesh` multiplane slicing for open sheets (supports `ignore_holes` option).
|
- `slice_at_range()`: Top-level slicing function that iterates through Z-heights using `manifold3d` for guaranteed watertight solid slicing (resolving coplanar and floating-point degeneracies) with a fallback to `trimesh` multiplane slicing for open sheets (supports `ignore_holes` option).
|
||||||
- `_slice_single_mesh()`: Mesh slicer implementing `manifold3d` solid slicing and `trimesh` multiplane intersection with micro-epsilon perturbation at boundary faces.
|
- `_slice_single_mesh()`: Mesh slicer implementing `manifold3d` solid slicing and `trimesh` multiplane intersection with micro-epsilon perturbation at boundary faces.
|
||||||
- `align_path_seam_sharpest()`: High-performance vectorized heuristic that positions the start vertex (seam) of closed polygon loops at the sharpest concave angle (inner corner), with fallback to sharpest angle overall.
|
- `align_path_seam_sharpest()`: High-performance vectorized heuristic that positions the start vertex (seam) of closed polygon loops at the back of the object (+Y) with corner sharpness preference.
|
||||||
- `SlicePaths.align_seams()`: Method on `SlicePaths` to align path seams to sharpest concave angles while preserving per-vertex attributes and metadata.
|
- `SlicePaths.align_seams()`: Method on `SlicePaths` to align path seams to the back (+Y) while preserving per-vertex attributes and metadata.
|
||||||
- `Pather`: Class that converts toolpaths into G-code strings. Supports per-segment extrusion rates, feedrates, and custom extra attributes.
|
- `Pather`: Class that converts toolpaths into G-code strings. Supports per-segment extrusion rates, feedrates, and custom extra attributes.
|
||||||
- `segments_to_slice()`: Utility to convert raw intersection segments into continuous paths (closed polygons are processed via Pyclipper, supports `ignore_holes`, open paths remain open).
|
- `segments_to_slice()`: Utility to convert raw intersection segments into continuous paths (closed polygons are processed via Pyclipper, supports `ignore_holes`, open paths remain open).
|
||||||
- `link_segments()`: Logic for joining loose segments into continuous paths.
|
- `link_segments()`: Logic for joining loose segments into continuous paths.
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **Extrusion Multiplier Attribute**: Mesh attribute to use for per-segment extrusion rates.
|
- **Extrusion Multiplier Attribute**: Mesh attribute to use for per-segment extrusion rates.
|
||||||
- **Feedrate Attribute**: Mesh attribute to use for per-segment feedrates.
|
- **Feedrate Attribute**: Mesh attribute to use for per-segment feedrates.
|
||||||
- **Extra Attributes**: Dictionary mapping mesh attribute names to custom G-code prefixes (e.g., `opening_size` -> `S`).
|
- **Extra Attributes**: Dictionary mapping mesh attribute names to custom G-code prefixes (e.g., `opening_size` -> `S`).
|
||||||
- **Slice Shells**: Slices a `SlicingObject` into 2D `SlicePaths` at a given layer height. Automatically aligns path seams to the sharpest concave angle (inner corner) of each layer. Supports rotation for slicing at an angle.
|
- **Slice Shells**: Slices a `SlicingObject` into 2D `SlicePaths` at a given layer height. Automatically aligns path seams to the back (+Y) of each layer. Supports rotation for slicing at an angle.
|
||||||
- **Ignore Holes**: Boolean option to omit interior hole perimeters and generate only the outer shell boundaries.
|
- **Ignore Holes**: Boolean option to omit interior hole perimeters and generate only the outer shell boundaries.
|
||||||
- **Buffer Shells**: Offsets/buffers 2D `SlicePaths`. Supports rotated offsetting and multiple miter types.
|
- **Buffer Shells**: Offsets/buffers 2D `SlicePaths`. Supports rotated offsetting and multiple miter types.
|
||||||
- **Buffer Shells Multiple**: Offsets 2D `SlicePaths` with multiple buffer passes.
|
- **Buffer Shells Multiple**: Offsets 2D `SlicePaths` with multiple buffer passes.
|
||||||
|
|
|
||||||
|
|
@ -725,9 +725,7 @@ class TestSlice(unittest.TestCase):
|
||||||
|
|
||||||
def test_align_seam_sharpest_triangle(self):
|
def test_align_seam_sharpest_triangle(self):
|
||||||
from NodeSlicer.core.slice import align_path_seam_sharpest
|
from NodeSlicer.core.slice import align_path_seam_sharpest
|
||||||
# Right triangle with acute 30-degree vertex at (10, 0)
|
# Right triangle with acute vertex at (10,0) and back vertex at (0, 5.7735)
|
||||||
# Vertices: 90 deg at (0,0), 60 deg at (0, 5.7735), 30 deg at (10,0)
|
|
||||||
# Start at 90 deg vertex (0,0)
|
|
||||||
path = np.array([
|
path = np.array([
|
||||||
[0.0, 0.0, 0.0],
|
[0.0, 0.0, 0.0],
|
||||||
[0.0, 5.7735, 0.0],
|
[0.0, 5.7735, 0.0],
|
||||||
|
|
@ -736,8 +734,8 @@ class TestSlice(unittest.TestCase):
|
||||||
])
|
])
|
||||||
aligned = align_path_seam_sharpest(path)
|
aligned = align_path_seam_sharpest(path)
|
||||||
self.assertTrue(np.allclose(aligned[0], aligned[-1]))
|
self.assertTrue(np.allclose(aligned[0], aligned[-1]))
|
||||||
# Sharpest angle is 30 degrees at (10.0, 0.0)
|
# Back seam should be placed at the rear-most vertex (+Y) at (0.0, 5.7735)
|
||||||
np.testing.assert_allclose(aligned[0, :2], [10.0, 0.0], atol=1e-3)
|
np.testing.assert_allclose(aligned[0, :2], [0.0, 5.7735], atol=1e-3)
|
||||||
|
|
||||||
def test_slicepaths_align_seams(self):
|
def test_slicepaths_align_seams(self):
|
||||||
from NodeSlicer.core.slice import SlicePaths
|
from NodeSlicer.core.slice import SlicePaths
|
||||||
|
|
@ -777,8 +775,9 @@ class TestSlice(unittest.TestCase):
|
||||||
aligned = align_path_seam_sharpest(path)
|
aligned = align_path_seam_sharpest(path)
|
||||||
self.assertEqual(len(aligned), len(path))
|
self.assertEqual(len(aligned), len(path))
|
||||||
self.assertTrue(np.allclose(aligned[0], aligned[-1]))
|
self.assertTrue(np.allclose(aligned[0], aligned[-1]))
|
||||||
# Seam should be placed at the concave inner corner (5.0, 5.0)
|
# Back seam should be placed on the rear corner (+Y) at (0.0, 10.0)
|
||||||
np.testing.assert_allclose(aligned[0, :2], [5.0, 5.0])
|
np.testing.assert_allclose(aligned[0, :2], [0.0, 10.0])
|
||||||
|
|
||||||
|
|
||||||
def test_intrinsics_retraction_feedrate(self):
|
def test_intrinsics_retraction_feedrate(self):
|
||||||
from NodeSlicer.core.nodeslicer.nodes.base import SliceIntrinsics
|
from NodeSlicer.core.nodeslicer.nodes.base import SliceIntrinsics
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |