소스 검색

Quick fixes (#72)

* fix Windows path problem

* add buttons to calibrate

* fix idle effect
Tuan Nguyen 5 달 전
부모
커밋
1750ebef86
3개의 변경된 파일119개의 추가작업 그리고 2개의 파일을 삭제
  1. 5 0
      modules/core/cache_manager.py
  2. 10 1
      modules/core/pattern_manager.py
  3. 104 1
      templates/table_control.html

+ 5 - 0
modules/core/cache_manager.py

@@ -56,10 +56,15 @@ def ensure_cache_dir():
 
 def get_cache_path(pattern_file):
     """Get the cache path for a pattern file."""
+    # Normalize path separators to handle both forward slashes and backslashes
+    pattern_file = pattern_file.replace('\\', '/')
+    
     # Create subdirectories in cache to match the pattern file structure
     cache_subpath = os.path.dirname(pattern_file)
     if cache_subpath:
         # Create the same subdirectory structure in cache (including custom_patterns)
+        # Convert forward slashes back to platform-specific separator for os.path.join
+        cache_subpath = cache_subpath.replace('/', os.sep)
         cache_dir = os.path.join(CACHE_DIR, cache_subpath)
     else:
         # For files in root pattern directory

+ 10 - 1
modules/core/pattern_manager.py

@@ -98,6 +98,8 @@ def list_theta_rho_files():
     for root, _, filenames in os.walk(THETA_RHO_DIR):
         for file in filenames:
             relative_path = os.path.relpath(os.path.join(root, file), THETA_RHO_DIR)
+            # Normalize path separators to always use forward slashes for consistency across platforms
+            relative_path = relative_path.replace(os.sep, '/')
             files.append(relative_path)
     logger.debug(f"Found {len(files)} theta-rho files")
     return [file for file in files if file.endswith('.thr')]
@@ -290,6 +292,11 @@ async def run_theta_rho_file(file_path, is_playlist=False):
             
         connection_manager.check_idle()
         
+        # Set LED back to idle when pattern completes normally (not stopped early)
+        if state.led_controller and not state.stop_requested:
+            effect_idle(state.led_controller)
+            logger.debug("LED effect set to idle after pattern completion")
+        
         # Only clear state if not part of a playlist
         if not is_playlist:
             state.current_playing_file = None
@@ -545,7 +552,9 @@ def get_status():
         "speed": state.speed,
         "pause_time_remaining": state.pause_time_remaining,
         "original_pause_time": getattr(state, 'original_pause_time', None),
-        "connection_status": state.conn.is_connected() if state.conn else False
+        "connection_status": state.conn.is_connected() if state.conn else False,
+        "current_theta": state.current_theta,
+        "current_rho": state.current_rho
     }
     
     # Add playlist information if available

+ 104 - 1
templates/table_control.html

@@ -192,7 +192,7 @@
         </li>
         <li class="flex items-start gap-3">
           <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">2.</span>
-          <span><strong>Manually</strong> move the radial arm to point 90° to the right of where you want the pattern bottom to be.</span>
+          <span><strong>Manually</strong> move the radial arm or <strong>use the rotation buttons below</strong> to point 90° to the right of where you want the pattern bottom to be.</span>
         </li>
         <li class="flex items-start gap-3">
           <span class="font-semibold text-blue-600 dark:text-blue-400 min-w-[20px]">3.</span>
@@ -210,6 +210,34 @@
         </p>
       </div>
       
+      <!-- Fine Adjustment Controls -->
+      <div class="border-t border-gray-200 dark:border-gray-600 pt-4 mb-4">
+        <p class="text-gray-700 dark:text-gray-300 text-sm mb-3 text-center">
+          <strong>Fine Adjustment:</strong> Use these buttons to rotate the ball precisely
+        </p>
+        <div class="flex justify-center gap-3">
+          <button
+            id="rotateCCW"
+            class="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
+            title="Rotate Counter-Clockwise 10°"
+          >
+            <span class="material-icons text-lg">rotate_left</span>
+            <span class="text-sm">CCW 10°</span>
+          </button>
+          <button
+            id="rotateCW"
+            class="flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
+            title="Rotate Clockwise 10°"
+          >
+            <span class="text-sm">CW 10°</span>
+            <span class="material-icons text-lg">rotate_right</span>
+          </button>
+        </div>
+        <p class="text-gray-500 dark:text-gray-400 text-xs text-center mt-2">
+          Each click rotates 10 degrees for fine adjustment
+        </p>
+      </div>
+      
       <div class="flex justify-center">
         <button
           id="closeOrientationHelpModal"
@@ -231,6 +259,12 @@ document.addEventListener('DOMContentLoaded', function() {
   const helpButton = document.getElementById('orientationHelpButton');
   const modal = document.getElementById('orientationHelpModal');
   const closeButton = document.getElementById('closeOrientationHelpModal');
+  const rotateCWButton = document.getElementById('rotateCW');
+  const rotateCCWButton = document.getElementById('rotateCCW');
+  
+  // Track current position (theta, rho)
+  let currentTheta = 0;
+  let currentRho = 1; // Always at perimeter for rotation adjustments
   
   helpButton.addEventListener('click', () => {
     modal.classList.remove('hidden');
@@ -246,6 +280,75 @@ document.addEventListener('DOMContentLoaded', function() {
       modal.classList.add('hidden');
     }
   });
+  
+  // Rotation button handlers
+  async function rotateByDegrees(degrees) {
+    try {
+      // Convert degrees to radians
+      const radians = degrees * (Math.PI / 180);
+      
+      // Calculate new theta position
+      const newTheta = currentTheta + radians;
+      
+      // Send coordinate to move to new position (always at perimeter, rho = 1)
+      const response = await fetch('/send_coordinate', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          theta: newTheta,
+          rho: 1
+        })
+      });
+      
+      if (response.ok) {
+        // Update tracked position
+        currentTheta = newTheta;
+        console.log(`Rotated ${degrees}°. New theta: ${(currentTheta * 180 / Math.PI).toFixed(1)}°`);
+      } else {
+        throw new Error('Failed to send coordinate');
+      }
+    } catch (error) {
+      console.error('Error rotating:', error);
+      alert('Failed to rotate. Please check connection.');
+    }
+  }
+  
+  // Clockwise rotation (positive angle)
+  rotateCWButton.addEventListener('click', async () => {
+    await rotateByDegrees(10);
+  });
+  
+  // Counter-clockwise rotation (negative angle)  
+  rotateCCWButton.addEventListener('click', async () => {
+    await rotateByDegrees(-10);
+  });
+  
+  // Try to get current position from WebSocket status
+  // This assumes you have a WebSocket connection for status updates
+  if (typeof ws !== 'undefined' && ws) {
+    const originalOnMessage = ws.onmessage;
+    ws.onmessage = function(event) {
+      try {
+        const data = JSON.parse(event.data);
+        if (data.type === 'status_update' && data.data) {
+          // Update current theta position if available in status
+          if (data.data.current_theta !== undefined) {
+            currentTheta = data.data.current_theta;
+          }
+          // Keep rho at 1 for rotation adjustments
+          currentRho = 1;
+        }
+      } catch (error) {
+        // Ignore parsing errors
+      }
+      // Call original handler if it exists
+      if (originalOnMessage) {
+        originalOnMessage.call(ws, event);
+      }
+    };
+  }
 });
 </script>
 {% endblock %}