fix: prevent pcbnew stdout noise from causing sync_schematic_to_board timeouts
The TS<->Python communication channel uses stdout for JSON responses. pcbnew's C++ SWIG layer can write warnings and diagnostics directly to C-level stdout (fd 1), corrupting the JSON framing. The TS parser then never sees valid JSON and the command times out after 30 seconds. Three changes fix this: 1. Python stdout redirect: In main(), save the original stdout fd for exclusive JSON response use, then redirect fd 1 to stderr so all pcbnew C++ output goes to logs instead of the response pipe. 2. Robust TS JSON parser: tryParseResponse() now uses newline-delimited parsing as a fallback. The Python side writes single-line JSON terminated by \n; the parser uses this as the completion signal instead of brace-matching, which prevents premature resolution of truncated chunked responses. Non-JSON preamble lines are logged and stripped. 3. Fix stray print() calls: Converted print() to logger in component_schematic.py and library_schematic.py so they don't leak to stdout during normal operations. Also adds sync_schematic_to_board to the longRunningCommands list for an appropriate timeout value.
This commit is contained in:
@@ -278,13 +278,13 @@ class ComponentManager:
|
||||
|
||||
if symbol_to_remove:
|
||||
schematic.symbol._elements.remove(symbol_to_remove)
|
||||
print(f"Removed component {component_ref} from schematic.")
|
||||
logger.info(f"Removed component {component_ref} from schematic.")
|
||||
return True
|
||||
else:
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error removing component {component_ref}: {e}")
|
||||
logger.error(f"Error removing component {component_ref}: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@@ -302,15 +302,14 @@ class ComponentManager:
|
||||
if key in symbol_to_update.property:
|
||||
symbol_to_update.property[key].value = value
|
||||
else:
|
||||
# Add as a new property if it doesn't exist
|
||||
symbol_to_update.property.append(key, value)
|
||||
print(f"Updated properties for component {component_ref}.")
|
||||
logger.info(f"Updated properties for component {component_ref}.")
|
||||
return True
|
||||
else:
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error updating component {component_ref}: {e}")
|
||||
logger.error(f"Error updating component {component_ref}: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@@ -318,9 +317,9 @@ class ComponentManager:
|
||||
"""Get a component by reference designator"""
|
||||
for symbol in schematic.symbol:
|
||||
if symbol.reference == component_ref:
|
||||
print(f"Found component with reference {component_ref}.")
|
||||
logger.debug(f"Found component with reference {component_ref}.")
|
||||
return symbol
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -339,13 +338,13 @@ class ComponentManager:
|
||||
)
|
||||
):
|
||||
matching_components.append(symbol)
|
||||
print(f"Found {len(matching_components)} components matching query '{query}'.")
|
||||
logger.debug(f"Found {len(matching_components)} components matching query '{query}'.")
|
||||
return matching_components
|
||||
|
||||
@staticmethod
|
||||
def get_all_components(schematic: Schematic):
|
||||
"""Get all components in schematic"""
|
||||
print(f"Retrieving all {len(schematic.symbol)} components.")
|
||||
logger.debug(f"Retrieving all {len(schematic.symbol)} components.")
|
||||
return list(schematic.symbol)
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import glob
|
||||
import logging
|
||||
|
||||
# Symbol class might not be directly importable in the current version
|
||||
import os
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LibraryManager:
|
||||
"""Manage symbol libraries"""
|
||||
@@ -31,11 +34,11 @@ class LibraryManager:
|
||||
matching_libs = glob.glob(path_pattern, recursive=True)
|
||||
libraries.extend(matching_libs)
|
||||
except Exception as e:
|
||||
print(f"Error searching for libraries at {path_pattern}: {e}")
|
||||
logger.error(f"Error searching for libraries at {path_pattern}: {e}")
|
||||
|
||||
# Extract library names from paths
|
||||
library_names = [os.path.splitext(os.path.basename(lib))[0] for lib in libraries]
|
||||
print(
|
||||
logger.info(
|
||||
f"Found {len(library_names)} libraries: {', '.join(library_names[:10])}{'...' if len(library_names) > 10 else ''}"
|
||||
)
|
||||
|
||||
@@ -54,12 +57,12 @@ class LibraryManager:
|
||||
# A potential approach would be to load the library file using KiCAD's Python API
|
||||
# or by parsing the library file format.
|
||||
# KiCAD symbol libraries are .kicad_sym files which are S-expression format
|
||||
print(
|
||||
logger.warning(
|
||||
f"Attempted to list symbols in library {library_path}. This requires advanced implementation."
|
||||
)
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"Error listing symbols in library {library_path}: {e}")
|
||||
logger.error(f"Error listing symbols in library {library_path}: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
@@ -68,12 +71,12 @@ class LibraryManager:
|
||||
try:
|
||||
# Similar to list_library_symbols, this might require a more direct approach
|
||||
# using KiCAD's Python API or by parsing the symbol library.
|
||||
print(
|
||||
logger.warning(
|
||||
f"Attempted to get details for symbol {symbol_name} in library {library_path}. This requires advanced implementation."
|
||||
)
|
||||
return {}
|
||||
except Exception as e:
|
||||
print(f"Error getting symbol details for {symbol_name} in {library_path}: {e}")
|
||||
logger.error(f"Error getting symbol details for {symbol_name} in {library_path}: {e}")
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
@@ -89,12 +92,12 @@ class LibraryManager:
|
||||
libraries = LibraryManager.list_available_libraries(search_paths)
|
||||
|
||||
results = []
|
||||
print(
|
||||
logger.warning(
|
||||
f"Searched for symbols matching '{query}'. This requires advanced implementation."
|
||||
)
|
||||
return results
|
||||
except Exception as e:
|
||||
print(f"Error searching for symbols matching '{query}': {e}")
|
||||
logger.error(f"Error searching for symbols matching '{query}': {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4024,8 +4024,29 @@ print("ok")
|
||||
}
|
||||
|
||||
|
||||
def _write_response(response_fd, response):
|
||||
"""Write a JSON response to the original stdout fd.
|
||||
|
||||
All response output goes through this function so that stray C-level
|
||||
writes from pcbnew (warnings, diagnostics) never corrupt the JSON
|
||||
framing seen by the TypeScript host.
|
||||
"""
|
||||
payload = json.dumps(response) + "\n"
|
||||
os.write(response_fd, payload.encode("utf-8"))
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
# --- Redirect stdout so pcbnew C++ noise never reaches the TS host ---
|
||||
# Save the real stdout fd for our exclusive JSON response channel.
|
||||
_response_fd = os.dup(1)
|
||||
# Point fd 1 (C-level stdout) at stderr so that any printf / std::cout
|
||||
# output from pcbnew or other C extensions is visible in logs but does
|
||||
# NOT corrupt the JSON stream the TypeScript side is parsing.
|
||||
os.dup2(2, 1)
|
||||
# Also redirect Python-level stdout to stderr for the same reason.
|
||||
sys.stdout = sys.stderr
|
||||
|
||||
logger.info("Starting KiCAD interface...")
|
||||
interface = KiCADInterface()
|
||||
|
||||
@@ -4167,10 +4188,9 @@ def main():
|
||||
# Handle command
|
||||
response = interface.handle_command(command, params)
|
||||
|
||||
# Send response
|
||||
# Send response via the clean fd (immune to pcbnew stdout noise)
|
||||
logger.debug(f"Sending response: {response}")
|
||||
print(json.dumps(response))
|
||||
sys.stdout.flush()
|
||||
_write_response(_response_fd, response)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Invalid JSON input: {str(e)}")
|
||||
@@ -4179,8 +4199,7 @@ def main():
|
||||
"message": "Invalid JSON input",
|
||||
"errorDetails": str(e),
|
||||
}
|
||||
print(json.dumps(response))
|
||||
sys.stdout.flush()
|
||||
_write_response(_response_fd, response)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("KiCAD interface stopped")
|
||||
|
||||
Reference in New Issue
Block a user