Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -270,23 +270,95 @@ def extract_outlines(binary_image: np.ndarray) -> (np.ndarray, list):
|
|
270 |
# ---------------------
|
271 |
# Functions for Finger Cut Clearance
|
272 |
# ---------------------
|
273 |
-
def union_tool_and_circle(tool_polygon: Polygon, center_inch, circle_diameter=1.0):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
radius = circle_diameter / 2.0
|
275 |
-
|
276 |
-
|
277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
278 |
|
279 |
def build_tool_polygon(points_inch):
|
280 |
return Polygon(points_inch)
|
281 |
|
282 |
-
def polygon_to_exterior_coords(poly: Polygon): # works fine
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
return []
|
288 |
-
return list(poly.exterior.coords)
|
289 |
-
|
290 |
|
291 |
|
292 |
|
@@ -413,42 +485,215 @@ from shapely.geometry import Point
|
|
413 |
# return None, None
|
414 |
|
415 |
|
416 |
-
def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=100):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
import random
|
418 |
needed_center_distance = circle_diameter + min_gap
|
419 |
radius = circle_diameter / 2.0
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
434 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
435 |
|
436 |
-
|
437 |
overlap = False
|
438 |
-
# Check against other tool polygons for overlap or proximity issues
|
439 |
for poly in all_polygons:
|
440 |
if poly == tool_polygon:
|
441 |
-
continue
|
442 |
-
if
|
443 |
overlap = True
|
444 |
break
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
|
|
452 |
return None, None
|
453 |
# ---------------------
|
454 |
# DXF Spline and Boundary Functions
|
@@ -484,6 +729,59 @@ def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=
|
|
484 |
print(f"Skipping contour: {e}")
|
485 |
return doc, final_polygons_inch
|
486 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
487 |
|
488 |
|
489 |
def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, offset_unit, annotation_text="", image_height_in=None, image_width_in=None):
|
@@ -658,8 +956,8 @@ def predict(
|
|
658 |
print(f"Error calculating scaling factor: {e}")
|
659 |
|
660 |
if scaling_factor is None or scaling_factor == 0:
|
661 |
-
scaling_factor = 0.
|
662 |
-
print("Using default scaling factor of 0.
|
663 |
gc.collect()
|
664 |
print("Scaling factor determined: {}".format(scaling_factor))
|
665 |
|
@@ -891,10 +1189,10 @@ if __name__ == "__main__":
|
|
891 |
gr.Image(label="Input Image"),
|
892 |
gr.Number(label="Offset value for Mask", value=0.075),
|
893 |
gr.Dropdown(label="Offset Unit", choices=["mm", "inches"], value="inches"),
|
894 |
-
gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="
|
895 |
-
gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="
|
896 |
-
gr.Number(label="Boundary Length", value=
|
897 |
-
gr.Number(label="Boundary Width", value=
|
898 |
gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
|
899 |
],
|
900 |
outputs=[
|
@@ -905,8 +1203,8 @@ if __name__ == "__main__":
|
|
905 |
gr.Textbox(label="Scaling Factor (inches/pixel)")
|
906 |
],
|
907 |
examples=[
|
908 |
-
["./Test20.jpg", 0.075, "inches", "
|
909 |
-
["./Test21.jpg", 0.075, "inches", "Yes", "Yes",
|
910 |
]
|
911 |
)
|
912 |
iface.launch(share=True)
|
|
|
270 |
# ---------------------
|
271 |
# Functions for Finger Cut Clearance
|
272 |
# ---------------------
|
273 |
+
# def union_tool_and_circle(tool_polygon: Polygon, center_inch, circle_diameter=1.0): #original
|
274 |
+
# radius = circle_diameter / 2.0
|
275 |
+
# circle_poly = Point(center_inch).buffer(radius, resolution=64)
|
276 |
+
# union_poly = tool_polygon.union(circle_poly)
|
277 |
+
# return union_poly
|
278 |
+
|
279 |
+
def union_tool_and_circle(tool_polygon, center_inch, circle_diameter=1.0):
|
280 |
+
"""Union a tool polygon with a circle with extensive validation."""
|
281 |
+
if tool_polygon is None or not isinstance(tool_polygon, Polygon):
|
282 |
+
print("Invalid tool polygon provided")
|
283 |
+
return None
|
284 |
+
|
285 |
radius = circle_diameter / 2.0
|
286 |
+
try:
|
287 |
+
# Create the circle
|
288 |
+
circle_poly = Point(center_inch).buffer(radius, resolution=64)
|
289 |
+
|
290 |
+
# Make sure both geometries are valid
|
291 |
+
if not tool_polygon.is_valid:
|
292 |
+
tool_polygon = tool_polygon.buffer(0)
|
293 |
+
if not circle_poly.is_valid:
|
294 |
+
print("Invalid circle geometry")
|
295 |
+
return tool_polygon
|
296 |
+
|
297 |
+
# Perform union
|
298 |
+
result = tool_polygon.union(circle_poly)
|
299 |
+
|
300 |
+
# Validate result
|
301 |
+
if result.is_empty:
|
302 |
+
print(f"Union resulted in empty geometry at {center_inch}")
|
303 |
+
return tool_polygon
|
304 |
+
|
305 |
+
# Handle multi-polygon results
|
306 |
+
if result.geom_type == "MultiPolygon":
|
307 |
+
# Take the largest piece to avoid fragmentation
|
308 |
+
result = max(result.geoms, key=lambda g: g.area)
|
309 |
+
|
310 |
+
# Final validation
|
311 |
+
if not result.is_valid:
|
312 |
+
print("Union produced invalid geometry, returning original polygon")
|
313 |
+
return tool_polygon
|
314 |
+
|
315 |
+
# Check exterior points
|
316 |
+
if not result.exterior or len(list(result.exterior.coords)) < 4:
|
317 |
+
print(f"Union resulted in degenerate polygon with insufficient points")
|
318 |
+
return tool_polygon
|
319 |
+
|
320 |
+
return result
|
321 |
+
except Exception as e:
|
322 |
+
print(f"Exception during union operation: {e}")
|
323 |
+
return tool_polygon
|
324 |
|
325 |
def build_tool_polygon(points_inch):
|
326 |
return Polygon(points_inch)
|
327 |
|
328 |
+
# def polygon_to_exterior_coords(poly: Polygon): # works fine original
|
329 |
+
# if poly.geom_type == "MultiPolygon":
|
330 |
+
# biggest = max(poly.geoms, key=lambda g: g.area)
|
331 |
+
# poly = biggest
|
332 |
+
# if not poly.exterior:
|
333 |
+
# return []
|
334 |
+
# return list(poly.exterior.coords)
|
335 |
+
|
336 |
+
def polygon_to_exterior_coords(poly):
|
337 |
+
"""Extract exterior coordinates with robust error handling."""
|
338 |
+
if poly is None:
|
339 |
+
print("Warning: Null polygon provided")
|
340 |
+
return []
|
341 |
+
|
342 |
+
try:
|
343 |
+
if poly.geom_type == "MultiPolygon":
|
344 |
+
if len(poly.geoms) == 0:
|
345 |
+
print("Warning: Empty MultiPolygon")
|
346 |
+
return []
|
347 |
+
biggest = max(poly.geoms, key=lambda g: g.area)
|
348 |
+
poly = biggest
|
349 |
+
|
350 |
+
if not poly.exterior:
|
351 |
+
print("Warning: Polygon has no exterior")
|
352 |
+
return []
|
353 |
+
|
354 |
+
coords = list(poly.exterior.coords)
|
355 |
+
if len(coords) < 4:
|
356 |
+
print(f"Warning: Polygon has insufficient coordinates: {len(coords)}")
|
357 |
+
|
358 |
+
return coords
|
359 |
+
except Exception as e:
|
360 |
+
print(f"Error extracting polygon coordinates: {e}")
|
361 |
return []
|
|
|
|
|
362 |
|
363 |
|
364 |
|
|
|
485 |
# return None, None
|
486 |
|
487 |
|
488 |
+
# def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=100): #working best
|
489 |
+
# import random
|
490 |
+
# needed_center_distance = circle_diameter + min_gap
|
491 |
+
# radius = circle_diameter / 2.0
|
492 |
+
# attempts = 0
|
493 |
+
# indices = list(range(len(points_inch)))
|
494 |
+
# random.shuffle(indices) # Shuffle indices for randomness
|
495 |
+
|
496 |
+
# for i in indices:
|
497 |
+
# if attempts >= max_attempts:
|
498 |
+
# break
|
499 |
+
# cx, cy = points_inch[i]
|
500 |
+
# # Try small adjustments around the chosen candidate
|
501 |
+
# for dx in np.linspace(-0.1, 0.1, 10):
|
502 |
+
# for dy in np.linspace(-0.1, 0.1, 10):
|
503 |
+
# candidate_center = (cx + dx, cy + dy)
|
504 |
+
# # Check distance from already placed centers
|
505 |
+
# if any(np.hypot(candidate_center[0] - ex, candidate_center[1] - ey) < needed_center_distance for ex, ey in existing_centers):
|
506 |
+
# continue
|
507 |
+
|
508 |
+
# union_poly= union_tool_and_circle(tool_polygon,candidate_center)
|
509 |
+
# overlap = False
|
510 |
+
# # Check against other tool polygons for overlap or proximity issues
|
511 |
+
# for poly in all_polygons:
|
512 |
+
# if poly == tool_polygon:
|
513 |
+
# continue
|
514 |
+
# if union_poly.intersects(poly) or union_poly.buffer(min_gap).intersects(poly):
|
515 |
+
# overlap = True
|
516 |
+
# break
|
517 |
+
# if overlap:
|
518 |
+
# continue
|
519 |
+
# # If candidate passes, accept it
|
520 |
+
# existing_centers.append(candidate_center)
|
521 |
+
# return union_poly, candidate_center
|
522 |
+
# attempts += 1
|
523 |
+
# print("Warning: Could not place a finger cut circle meeting all spacing requirements.")
|
524 |
+
# return None, None
|
525 |
+
|
526 |
+
|
527 |
+
# def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=20):
|
528 |
+
# """Place a finger cut with strategic positioning based on tool shape."""
|
529 |
+
# if tool_polygon is None or len(points_inch) < 4:
|
530 |
+
# return None, None
|
531 |
+
|
532 |
+
# import random
|
533 |
+
# needed_center_distance = circle_diameter + min_gap
|
534 |
+
# radius = circle_diameter / 2.0
|
535 |
+
|
536 |
+
# # Calculate the tool's bounding box and find its center
|
537 |
+
# minx, miny, maxx, maxy = tool_polygon.bounds
|
538 |
+
# bbox_center = ((minx + maxx) / 2, (miny + maxy) / 2)
|
539 |
+
|
540 |
+
# # Strategy 1: Try handle areas first (typically thinner parts)
|
541 |
+
# # Find narrow regions by looking at distance to boundary
|
542 |
+
# narrow_candidates = []
|
543 |
+
# for i, pt in enumerate(points_inch):
|
544 |
+
# # Calculate distance to the opposite side of the polygon
|
545 |
+
# point = Point(pt)
|
546 |
+
# if tool_polygon.boundary.distance(point) < radius * 1.5: # Points near boundary
|
547 |
+
# narrow_candidates.append(i)
|
548 |
+
|
549 |
+
# # Strategy 2: Try areas far from existing cuts
|
550 |
+
# far_candidates = []
|
551 |
+
# if existing_centers:
|
552 |
+
# for i, pt in enumerate(points_inch):
|
553 |
+
# min_dist = min(np.hypot(pt[0] - cx, pt[1] - cy) for cx, cy in existing_centers)
|
554 |
+
# if min_dist > needed_center_distance * 1.5: # Points far from existing cuts
|
555 |
+
# far_candidates.append(i)
|
556 |
+
|
557 |
+
# # Strategy 3: Try concave regions (often good for finger grips)
|
558 |
+
# # This is more complex but could identify good grip points
|
559 |
+
|
560 |
+
# # Combine strategies, prioritizing certain candidates
|
561 |
+
# candidate_indices = (narrow_candidates + far_candidates +
|
562 |
+
# list(set(range(len(points_inch))) -
|
563 |
+
# set(narrow_candidates) -
|
564 |
+
# set(far_candidates)))
|
565 |
+
# random.shuffle(candidate_indices) # Add randomness
|
566 |
+
|
567 |
+
# # Try candidates
|
568 |
+
# for idx in candidate_indices:
|
569 |
+
# base_pt = points_inch[idx]
|
570 |
+
|
571 |
+
# # Try at different distances from the point
|
572 |
+
# for dist_factor in [0.0, 0.1, 0.2, 0.3, 0.4]:
|
573 |
+
# # Try different angles
|
574 |
+
# for angle in np.linspace(0, 2*np.pi, 12):
|
575 |
+
# dx = dist_factor * radius * np.cos(angle)
|
576 |
+
# dy = dist_factor * radius * np.sin(angle)
|
577 |
+
# candidate = (base_pt[0] + dx, base_pt[1] + dy)
|
578 |
+
|
579 |
+
# # Skip if point isn't inside the polygon
|
580 |
+
# if not tool_polygon.contains(Point(candidate)):
|
581 |
+
# continue
|
582 |
+
|
583 |
+
# # Skip if too close to existing centers
|
584 |
+
# if any(np.hypot(candidate[0] - cx, candidate[1] - cy) < needed_center_distance
|
585 |
+
# for cx, cy in existing_centers):
|
586 |
+
# continue
|
587 |
+
|
588 |
+
# # Try creating the union
|
589 |
+
# new_polygon = union_tool_and_circle(tool_polygon, candidate, circle_diameter)
|
590 |
+
# if new_polygon is None or new_polygon == tool_polygon:
|
591 |
+
# continue # Union failed
|
592 |
+
|
593 |
+
# # Check for overlaps with other tools
|
594 |
+
# overlap = False
|
595 |
+
# for poly in all_polygons:
|
596 |
+
|
597 |
+
# if poly == tool_polygon:
|
598 |
+
# continue
|
599 |
+
# # if new_polygon.intersects(poly):
|
600 |
+
# # overlap = True
|
601 |
+
# # break
|
602 |
+
# if new_polygon.buffer(0.25).intersects(poly):
|
603 |
+
# overlap = True
|
604 |
+
# break
|
605 |
+
|
606 |
+
# if not overlap:
|
607 |
+
# # Success! This is a good spot for a finger cut
|
608 |
+
# existing_centers.append(candidate)
|
609 |
+
# return new_polygon, candidate
|
610 |
+
|
611 |
+
# # If we get here, we couldn't find a good spot
|
612 |
+
# print("Could not find suitable location for finger cut")
|
613 |
+
# return None, None
|
614 |
+
|
615 |
+
def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=50):
|
616 |
+
"""Place a finger cut with strategic positioning based on tool shape."""
|
617 |
+
if tool_polygon is None or len(points_inch) < 4:
|
618 |
+
return None, None
|
619 |
+
|
620 |
import random
|
621 |
needed_center_distance = circle_diameter + min_gap
|
622 |
radius = circle_diameter / 2.0
|
623 |
+
|
624 |
+
# Calculate the tool's bounding box and find its center
|
625 |
+
minx, miny, maxx, maxy = tool_polygon.bounds
|
626 |
+
bbox_center = ((minx + maxx) / 2, (miny + maxy) / 2)
|
627 |
+
|
628 |
+
# Strategy 1: Try handle areas first (typically thinner parts)
|
629 |
+
# Find narrow regions by looking at distance to boundary
|
630 |
+
narrow_candidates = []
|
631 |
+
for i, pt in enumerate(points_inch):
|
632 |
+
# Calculate distance to the opposite side of the polygon
|
633 |
+
point = Point(pt)
|
634 |
+
if tool_polygon.boundary.distance(point) < radius * 1.5: # Points near boundary
|
635 |
+
narrow_candidates.append(i)
|
636 |
+
|
637 |
+
# Strategy 2: Try areas far from existing cuts
|
638 |
+
far_candidates = []
|
639 |
+
if existing_centers:
|
640 |
+
for i, pt in enumerate(points_inch):
|
641 |
+
min_dist = min(np.hypot(pt[0] - cx, pt[1] - cy) for cx, cy in existing_centers)
|
642 |
+
if min_dist > needed_center_distance * 1.5: # Points far from existing cuts
|
643 |
+
far_candidates.append(i)
|
644 |
+
|
645 |
+
# Strategy 3: Try concave regions (often good for finger grips)
|
646 |
+
# This is more complex but could identify good grip points
|
647 |
+
|
648 |
+
# Combine strategies, prioritizing certain candidates
|
649 |
+
candidate_indices = (narrow_candidates + far_candidates +
|
650 |
+
list(set(range(len(points_inch))) -
|
651 |
+
set(narrow_candidates) -
|
652 |
+
set(far_candidates)))
|
653 |
+
random.shuffle(candidate_indices) # Add randomness
|
654 |
+
|
655 |
+
# Try candidates
|
656 |
+
for idx in candidate_indices:
|
657 |
+
base_pt = points_inch[idx]
|
658 |
+
|
659 |
+
# Try at different distances from the point
|
660 |
+
for dist_factor in [0.0, 0.1, 0.2, 0.3, 0.4]:
|
661 |
+
# Try different angles
|
662 |
+
for angle in np.linspace(0, 2*np.pi, 12):
|
663 |
+
dx = dist_factor * radius * np.cos(angle)
|
664 |
+
dy = dist_factor * radius * np.sin(angle)
|
665 |
+
candidate = (base_pt[0] + dx, base_pt[1] + dy)
|
666 |
+
|
667 |
+
# Skip if point isn't inside the polygon
|
668 |
+
if not tool_polygon.contains(Point(candidate)):
|
669 |
continue
|
670 |
+
|
671 |
+
# Skip if too close to existing centers
|
672 |
+
if any(np.hypot(candidate[0] - cx, candidate[1] - cy) < needed_center_distance
|
673 |
+
for cx, cy in existing_centers):
|
674 |
+
continue
|
675 |
+
|
676 |
+
# Try creating the union
|
677 |
+
new_polygon = union_tool_and_circle(tool_polygon, candidate, circle_diameter)
|
678 |
+
if new_polygon is None or new_polygon == tool_polygon:
|
679 |
+
continue # Union failed
|
680 |
|
681 |
+
# Check for overlaps with other tools
|
682 |
overlap = False
|
|
|
683 |
for poly in all_polygons:
|
684 |
if poly == tool_polygon:
|
685 |
+
continue
|
686 |
+
if new_polygon.buffer(0.1).intersects(poly):
|
687 |
overlap = True
|
688 |
break
|
689 |
+
|
690 |
+
if not overlap:
|
691 |
+
# Success! This is a good spot for a finger cut
|
692 |
+
existing_centers.append(candidate)
|
693 |
+
return new_polygon, candidate
|
694 |
+
|
695 |
+
# If we get here, we couldn't find a good spot
|
696 |
+
print("Could not find suitable location for finger cut")
|
697 |
return None, None
|
698 |
# ---------------------
|
699 |
# DXF Spline and Boundary Functions
|
|
|
729 |
print(f"Skipping contour: {e}")
|
730 |
return doc, final_polygons_inch
|
731 |
|
732 |
+
# def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=True):
|
733 |
+
# degree = 3
|
734 |
+
# closed = True
|
735 |
+
# doc = ezdxf.new(units=0)
|
736 |
+
# doc.units = ezdxf.units.IN
|
737 |
+
# doc.header["$INSUNITS"] = ezdxf.units.IN
|
738 |
+
# msp = doc.modelspace()
|
739 |
+
# finger_cut_centers = []
|
740 |
+
# final_polygons_inch = []
|
741 |
+
|
742 |
+
# for contour in inflated_contours:
|
743 |
+
# try:
|
744 |
+
# resampled_contour = resample_contour(contour)
|
745 |
+
# points_inch = [(x * scaling_factor, (height - y) * scaling_factor) for x, y in resampled_contour]
|
746 |
+
|
747 |
+
# # Ensure minimum number of points
|
748 |
+
# if len(points_inch) < 4: # Need at least 4 for a valid polygon and spline
|
749 |
+
# print(f"Skipping contour with only {len(points_inch)} points")
|
750 |
+
# continue
|
751 |
+
|
752 |
+
# # Ensure first and last points match for closed polygon
|
753 |
+
# if np.linalg.norm(np.array(points_inch[0]) - np.array(points_inch[-1])) > 1e-2:
|
754 |
+
# points_inch.append(points_inch[0])
|
755 |
+
|
756 |
+
# # Create tool polygon with validation
|
757 |
+
# tool_polygon = build_tool_polygon(points_inch)
|
758 |
+
# if not tool_polygon.is_valid:
|
759 |
+
# tool_polygon = tool_polygon.buffer(0) # Fix invalid geometries
|
760 |
+
|
761 |
+
# # Add finger clearance if requested
|
762 |
+
# if finger_clearance:
|
763 |
+
# for attempt in range(3): # Try multiple finger cuts per tool
|
764 |
+
# union_poly, center = place_finger_cut_adjusted(
|
765 |
+
# tool_polygon, points_inch, finger_cut_centers, final_polygons_inch)
|
766 |
+
# if union_poly is not None:
|
767 |
+
# tool_polygon = union_poly
|
768 |
+
# else:
|
769 |
+
# break # Stop if we can't place more cuts
|
770 |
+
|
771 |
+
# # Get exterior coordinates with validation
|
772 |
+
# exterior_coords = polygon_to_exterior_coords(tool_polygon)
|
773 |
+
# if len(exterior_coords) < 4:
|
774 |
+
# print(f"Warning: Insufficient exterior points ({len(exterior_coords)})")
|
775 |
+
# continue
|
776 |
+
|
777 |
+
# # Add to DXF
|
778 |
+
# msp.add_spline(exterior_coords, degree=degree, dxfattribs={"layer": "TOOLS"})
|
779 |
+
# final_polygons_inch.append(tool_polygon)
|
780 |
+
|
781 |
+
# except Exception as e:
|
782 |
+
# print(f"Error processing contour: {e}")
|
783 |
+
|
784 |
+
# return doc, final_polygons_inch
|
785 |
|
786 |
|
787 |
def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, offset_unit, annotation_text="", image_height_in=None, image_width_in=None):
|
|
|
956 |
print(f"Error calculating scaling factor: {e}")
|
957 |
|
958 |
if scaling_factor is None or scaling_factor == 0:
|
959 |
+
scaling_factor = 0.05
|
960 |
+
print("Using default scaling factor of 0.05 due to calculation error")
|
961 |
gc.collect()
|
962 |
print("Scaling factor determined: {}".format(scaling_factor))
|
963 |
|
|
|
1189 |
gr.Image(label="Input Image"),
|
1190 |
gr.Number(label="Offset value for Mask", value=0.075),
|
1191 |
gr.Dropdown(label="Offset Unit", choices=["mm", "inches"], value="inches"),
|
1192 |
+
gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="Yes"),
|
1193 |
+
gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="Yes"),
|
1194 |
+
gr.Number(label="Boundary Length", value=30.0, precision=2),
|
1195 |
+
gr.Number(label="Boundary Width", value=30.0, precision=2),
|
1196 |
gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
|
1197 |
],
|
1198 |
outputs=[
|
|
|
1203 |
gr.Textbox(label="Scaling Factor (inches/pixel)")
|
1204 |
],
|
1205 |
examples=[
|
1206 |
+
["./Test20.jpg", 0.075, "inches", "Yes", "No", 30.0, 30.0, "MyTool"],
|
1207 |
+
["./Test21.jpg", 0.075, "inches", "Yes", "Yes", 30.0, 30.0, "Tool2"]
|
1208 |
]
|
1209 |
)
|
1210 |
iface.launch(share=True)
|