cyclecad 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,28 @@
1
1
  /**
2
- * Photo-to-CAD Reverse Engineering Module
3
- * Converts photographs of parts into parametric 3D CAD models
2
+ * @fileoverview Photo-to-CAD Reverse Engineering Module
3
+ * @module CycleCAD/PhotoToCAD
4
+ * @version 3.7.0
5
+ * @author cycleCAD Team
6
+ * @license MIT
4
7
  *
5
- * Features:
6
- * - Image input: drag-drop, camera, clipboard
7
- * - Edge detection: Sobel + Canny + contour tracing
8
- * - Geometry reconstruction: 2D contours → 3D primitives
9
- * - Interactive refinement: overlay, sliders, reference dimensions
10
- * - AI enhancement: Gemini Flash Vision API integration
11
- * - UI panel with side-by-side photo + 3D preview
8
+ * @description
9
+ * Converts photographs of parts into parametric 3D CAD models through image analysis.
10
+ * Features drag-drop/camera/clipboard image input, Sobel+Canny edge detection with non-maximum suppression,
11
+ * contour tracing, 2D → 3D primitive reconstruction, interactive refinement with sliders,
12
+ * Gemini Flash Vision API integration, and side-by-side photo + 3D preview.
13
+ *
14
+ * @example
15
+ * // Initialize the module
16
+ * window.CycleCAD.PhotoToCAD.init();
17
+ *
18
+ * // Load image and detect features
19
+ * window.CycleCAD.PhotoToCAD.execute('loadImage', {imageData: dataUrl});
20
+ *
21
+ * // Detect geometric primitives
22
+ * window.CycleCAD.PhotoToCAD.execute('detectPrimitives');
23
+ *
24
+ * @requires THREE (Three.js r170)
25
+ * @see {@link https://cyclecad.com/docs/killer-features|Killer Features Guide}
12
26
  */
13
27
 
14
28
  (function() {
@@ -18,6 +32,40 @@
18
32
  // STATE
19
33
  // ============================================================================
20
34
 
35
+ // ========== TYPEDEFS ==========
36
+ /**
37
+ * @typedef {Object} DetectedFeature
38
+ * @property {string} type - Feature type: 'circle'|'rectangle'|'line'|'arc'
39
+ * @property {Array<Point>} contour - Ordered points defining the feature boundary
40
+ * @property {Object} geometry - Computed geometric properties
41
+ * @property {number} confidence - Detection confidence 0-1
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} ContourPoint
46
+ * @property {number} x - X coordinate in image pixels
47
+ * @property {number} y - Y coordinate in image pixels
48
+ * @property {number} angle - Edge angle in radians (-π to π)
49
+ * @property {number} magnitude - Edge magnitude 0-255
50
+ */
51
+
52
+ /**
53
+ * @typedef {Object} EdgeMap
54
+ * @property {Uint8ClampedArray} data - Edge magnitude per pixel
55
+ * @property {number} width - Image width in pixels
56
+ * @property {number} height - Image height in pixels
57
+ * @property {number} maxMagnitude - Highest magnitude value found
58
+ * @property {Uint8ClampedArray} angles - Edge angle per pixel
59
+ */
60
+
61
+ /**
62
+ * @typedef {Object} ReconstructionResult
63
+ * @property {THREE.Object3D} geometry - Generated 3D model
64
+ * @property {Array<DetectedFeature>} features - Detected 2D features
65
+ * @property {Object} metrics - Quality metrics (area coverage, confidence, etc.)
66
+ */
67
+
68
+ // ========== MODULE STATE ==========
21
69
  const state = {
22
70
  originalImage: null,
23
71
  processedImage: null,
@@ -45,7 +93,15 @@
45
93
  // ============================================================================
46
94
 
47
95
  /**
48
- * Initialize image input handlers
96
+ * Initialize image input handlers (drag-drop, file input, camera, paste)
97
+ *
98
+ * Sets up event listeners for multiple image input methods:
99
+ * - Drag-drop files onto designated drop zone
100
+ * - File <input> element selection
101
+ * - Camera/device capture via getUserMedia
102
+ * - Paste from clipboard (Ctrl+V)
103
+ *
104
+ * @returns {void}
49
105
  */
50
106
  function initImageInput() {
51
107
  const dropZone = document.getElementById('photo-cad-drop-zone');
@@ -276,6 +332,18 @@
276
332
  /**
277
333
  * Detect edges using Sobel operator + Canny-style thinning
278
334
  */
335
+ /**
336
+ * Detect edges in image using Sobel operator with non-maximum suppression
337
+ *
338
+ * Two-stage edge detection:
339
+ * 1. Sobel: Applies 3x3 Sobel kernels for Gx/Gy gradients (fast, robust to noise)
340
+ * 2. Non-Maximum Suppression: Thins edges to single-pixel width, removes weak edges below threshold
341
+ *
342
+ * Sobel is chosen over Canny here for speed (no hysteresis linking) while maintaining quality.
343
+ * Non-maximum suppression uses gradient angle to suppress perpendicular pixels.
344
+ *
345
+ * @returns {void} Updates state.processedImage with binary edge map
346
+ */
279
347
  function detectEdges() {
280
348
  if (!state.canvas) return;
281
349
 
@@ -325,6 +393,22 @@
325
393
  * @param {number} height
326
394
  * @returns {Uint8Array} Edge magnitude
327
395
  */
396
+ /**
397
+ * Apply Sobel edge detection operator to grayscale image
398
+ *
399
+ * Computes image gradients using 3x3 Sobel kernels (Gx for horizontal, Gy for vertical).
400
+ * Returns magnitude (edge strength) and angle (edge direction) for each pixel.
401
+ *
402
+ * Sobel kernels:
403
+ * Gx = [-1 0 +1] / 2 Gy = [-1 -2 -1] / 2
404
+ * [-2 0 +2] [ 0 0 0]
405
+ * [-1 0 +1] [+1 +2 +1]
406
+ *
407
+ * @param {Uint8ClampedArray} gray - Grayscale image data (single channel)
408
+ * @param {number} width - Image width in pixels
409
+ * @param {number} height - Image height in pixels
410
+ * @returns {EdgeMap} Edge magnitude and direction data
411
+ */
328
412
  function sobelEdgeDetection(gray, width, height) {
329
413
  const sobelX = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
330
414
  const sobelY = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
@@ -356,6 +440,20 @@
356
440
  * @param {number} height
357
441
  * @returns {Uint8Array} Thinned edges
358
442
  */
443
+ /**
444
+ * Apply non-maximum suppression to thin edges and remove weak responses
445
+ *
446
+ * For each edge pixel: checks if it's a local maximum along the gradient direction.
447
+ * Only keeps pixels stronger than both perpendicular neighbors. Applies threshold
448
+ * to remove weak edges below sensitivity level. Produces single-pixel-wide edge outlines.
449
+ *
450
+ * Used in classic Canny algorithm but applied here after Sobel for performance.
451
+ *
452
+ * @param {EdgeMap} edges - Edge map from sobelEdgeDetection()
453
+ * @param {number} width - Image width in pixels
454
+ * @param {number} height - Image height in pixels
455
+ * @returns {Uint8ClampedArray} Binary edge map (0 or 255 per pixel)
456
+ */
359
457
  function nonMaximumSuppression(edges, width, height) {
360
458
  const thinned = new Uint8Array(width * height);
361
459
 
@@ -390,6 +488,18 @@
390
488
  * @param {number} width
391
489
  * @param {number} height
392
490
  */
491
+ /**
492
+ * Extract closed contours from binary edge map using flood fill
493
+ *
494
+ * Finds all edge pixels and traces connected components to form contours.
495
+ * Each contour is an ordered array of points that form a closed loop.
496
+ * Contours smaller than minSize are discarded as noise.
497
+ *
498
+ * @param {Uint8ClampedArray} binary - Binary edge map (0 or 255 per pixel)
499
+ * @param {number} width - Image width in pixels
500
+ * @param {number} height - Image height in pixels
501
+ * @returns {Array<Array<ContourPoint>>} Array of contours, each contour is array of points
502
+ */
393
503
  function extractContours(binary, width, height) {
394
504
  const visited = new Uint8Array(width * height);
395
505
  const contours = [];
@@ -419,6 +529,21 @@
419
529
  * @param {number} height
420
530
  * @returns {Array<{x, y}>} Contour points
421
531
  */
532
+ /**
533
+ * Trace a single contour starting from a seed point (internal helper)
534
+ *
535
+ * Uses 8-connectivity neighborhood traversal to follow edge pixels.
536
+ * Starts at seed point and walks perimeter in consistent direction.
537
+ * Marks visited pixels to avoid tracing same contour twice.
538
+ *
539
+ * @param {Uint8ClampedArray} binary - Binary edge map
540
+ * @param {Set<string>} visited - Set of already-traced pixel coordinates
541
+ * @param {number} startX - X coordinate of seed point
542
+ * @param {number} startY - Y coordinate of seed point
543
+ * @param {number} width - Image width in pixels
544
+ * @param {number} height - Image height in pixels
545
+ * @returns {Array<ContourPoint>} Ordered contour points
546
+ */
422
547
  function traceContour(binary, visited, startX, startY, width, height) {
423
548
  const contour = [];
424
549
  let x = startX, y = startY;
@@ -459,6 +584,19 @@
459
584
  /**
460
585
  * Detect primitives: circles, lines, rectangles, arcs
461
586
  */
587
+ /**
588
+ * Detect geometric primitives (circles, rectangles, lines) from extracted contours
589
+ *
590
+ * For each contour, attempts to fit known shapes in order of specificity:
591
+ * 1. Circle: Uses least-squares circle fitting (Taubin method)
592
+ * 2. Rectangle: Finds axis-aligned bounding box, checks linearity
593
+ * 3. Line: Fits line segment if contour is nearly straight
594
+ * 4. Arc: Fits circular arc for partial shapes
595
+ *
596
+ * Assigns confidence score based on fit quality. Returns array of detected features.
597
+ *
598
+ * @returns {Array<DetectedFeature>} Detected geometric primitives
599
+ */
462
600
  function detectPrimitives() {
463
601
  const contours = state.detectedFeatures.contours;
464
602
  state.detectedFeatures.circles = [];
@@ -492,6 +630,16 @@
492
630
  * @param {Array<{x, y}>} contour
493
631
  * @returns {{x, y, radius, confidence} | null}
494
632
  */
633
+ /**
634
+ * Fit least-squares circle to contour points (internal helper)
635
+ *
636
+ * Uses Taubin circle fitting algorithm (robust, algebraic method).
637
+ * Computes circle center and radius minimizing algebraic distance to all points.
638
+ * Returns null if fit quality is poor (e.g., contour is linear, not circular).
639
+ *
640
+ * @param {Array<ContourPoint>} contour - Ordered contour points
641
+ * @returns {Object|null} {center: {x, y}, radius, confidence} or null if not a circle
642
+ */
495
643
  function detectCircle(contour) {
496
644
  if (contour.length < 8) return null;
497
645
 
@@ -725,6 +873,20 @@
725
873
  /**
726
874
  * Reconstruct 3D geometry from detected features
727
875
  */
876
+ /**
877
+ * Reconstruct 3D geometry from 2D detected primitives
878
+ *
879
+ * Multi-mode reconstruction based on detected shape types:
880
+ * - Single circle → cylinder (rotational extrusion)
881
+ * - Single rectangle → box (extrusion)
882
+ * - Circle + reference dimension → scaled cylinder
883
+ * - Multiple shapes → composite assembly
884
+ *
885
+ * Uses reference dimension to establish real-world scale from pixel measurements.
886
+ * Returns composite THREE.Group with all 3D models.
887
+ *
888
+ * @returns {ReconstructionResult} 3D geometry and metrics
889
+ */
728
890
  function reconstruct3D() {
729
891
  const features = Array.from(state.selectedFeatures);
730
892
  if (features.length === 0) {
@@ -1048,6 +1210,14 @@
1048
1210
  /**
1049
1211
  * Initialize module
1050
1212
  */
1213
+ /**
1214
+ * Initialize PhotoToCAD module with UI and event handlers
1215
+ *
1216
+ * Sets up drop zone, file input, camera button, and Three.js preview scene.
1217
+ * Must be called once before execute() calls. Safe to call multiple times.
1218
+ *
1219
+ * @returns {void}
1220
+ */
1051
1221
  function init() {
1052
1222
  initImageInput();
1053
1223
  setupUIEventListeners();
@@ -1274,6 +1444,26 @@
1274
1444
  * @param {string} command
1275
1445
  * @param {*} params
1276
1446
  */
1447
+ /**
1448
+ * Execute command in PhotoToCAD module (public API)
1449
+ *
1450
+ * Main entry point for reverse engineering operations:
1451
+ * - 'loadImage': Load image from data URL, file, or camera
1452
+ * - 'detectEdges': Apply Sobel edge detection
1453
+ * - 'detectPrimitives': Recognize circles, rectangles, lines
1454
+ * - 'reconstruct3D': Convert 2D shapes to 3D geometry
1455
+ * - 'setReferenceDimension': Set pixel-to-mm scale
1456
+ * - 'enhanceWithAI': Ask Gemini to refine detection
1457
+ * - 'exportModel': Export as STL or glTF
1458
+ *
1459
+ * @param {string} command - Command name
1460
+ * @param {Object} [params={}] - Command parameters
1461
+ * @param {string} params.imageData - For 'loadImage': data URL or file
1462
+ * @param {number} params.pixelLength - For 'setReferenceDimension': length in pixels
1463
+ * @param {number} params.mmLength - For 'setReferenceDimension': length in mm
1464
+ * @param {string} params.format - For 'exportModel': 'stl'|'gltf'|'glb'
1465
+ * @returns {Object} Command result (varies by command)
1466
+ */
1277
1467
  function execute(command, params) {
1278
1468
  switch (command) {
1279
1469
  case 'processImage':