Skip to main content
Glama
freecad_connection_launcher.py10.3 kB
#!/usr/bin/env python3 """ FreeCAD Launcher This script launches FreeCAD with our script to execute commands directly in FreeCAD's Python interpreter, avoiding module loading issues. Can use AppRun from an extracted AppImage for better compatibility. """ import json import os import subprocess import time from typing import Any, Dict, Optional class FreeCADLauncher: """Class to launch FreeCAD with scripts""" def __init__( self, freecad_path="/usr/bin/freecad", script_path=None, debug=False, use_apprun=False, ): """Initialize the launcher""" self.freecad_path = freecad_path self.debug = debug self.use_apprun = use_apprun # Use the provided script path or look in the same directory as this file if script_path: self.script_path = script_path else: self.script_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "freecad_launcher_script.py" ) # Check if the script exists if not os.path.exists(self.script_path): raise FileNotFoundError(f"FreeCAD script not found at {self.script_path}") # Handle AppRun path detection if use_apprun: # First, check if the path is directly to AppRun if os.path.basename(self.freecad_path) == "AppRun": self.apprun_path = self.freecad_path else: # Then check if AppRun is in the same directory potential_apprun = os.path.join( os.path.dirname(self.freecad_path), "AppRun" ) if os.path.exists(potential_apprun): self.apprun_path = potential_apprun else: # Finally, check if this is a directory containing AppRun potential_apprun = os.path.join(self.freecad_path, "AppRun") if os.path.exists(potential_apprun): self.apprun_path = potential_apprun else: raise FileNotFoundError( f"AppRun not found at or near {self.freecad_path}. Please specify the correct path." ) self.log(f"Using AppRun at: {self.apprun_path}") else: # Check if freecad executable exists if not os.path.exists(self.freecad_path): raise FileNotFoundError( f"FreeCAD executable not found at {self.freecad_path}" ) self.log(f"Using FreeCAD executable at: {self.freecad_path}") self.log(f"Using script: {self.script_path}") def log(self, message): """Log a message if debug is enabled""" if self.debug: print(f"[FreeCAD Launcher] {message}") def execute_command(self, command, params=None): """Execute a command in FreeCAD""" if params is None: params = {} # Convert params to JSON string params_json = json.dumps(params) # Build the command if self.use_apprun: # ---- REVERT TO ORIGINAL COMMAND ---- cmd = [ self.apprun_path, # Use the resolved AppRun path self.script_path, # Script path as direct argument "--", # Separate script arguments command, # Pass command name as arg to our script params_json, # Pass params JSON as arg to our script ] # ---- END REVERTED COMMAND ---- else: # Standard mode using FreeCAD executable # This mode likely needs refinement to work consistently cmd = [ self.freecad_path, # e.g., /usr/bin/freecad "--console", self.script_path, "--", # Separator might be needed depending on FreeCAD version command, params_json, ] self.log(f"Running command: {' '.join(map(str, cmd))}") try: # ---> REMOVE Environment Modification <--- # env = os.environ.copy() # env["QT_QPA_PLATFORM"] = "offscreen" # self.log(f"Setting QT_QPA_PLATFORM=offscreen") # -------------------------------------- # Run the command process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", errors="replace", # Handle encoding errors gracefully # env=env # ---> REMOVE Modified Environment <--- ) # Set a timeout for the process timeout = 90 # seconds try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log(f"Command execution timed out after {timeout} seconds") return { "success": False, "error": f"Command execution timed out after {timeout} seconds", } self.log(f"Command executed with return code: {process.returncode}") # If we got output on stderr, log it for debugging if stderr: self.log(f"Command stderr: {stderr}") # Check for errors if process.returncode != 0: self.log(f"Error executing command: {stderr}") return { "success": False, "error": f"FreeCAD execution failed with code {process.returncode}", "stderr": stderr, } # Parse the output try: # Look for JSON output in stdout output_lines = stdout.strip().split("\n") # Detailed logging for debugging if self.debug: self.log(f"Output lines: {len(output_lines)}") for i, line in enumerate(output_lines): self.log( f"Line {i}: {line[:100]}{'...' if len(line) > 100 else ''}" ) # Start from the end to find the last JSON for line in reversed(output_lines): line = line.strip() if line and line[0] == "{" and line[-1] == "}": try: result = json.loads(line) self.log(f"Found valid JSON result") return result except json.JSONDecodeError: continue # If we didn't find any valid JSON, return the error self.log(f"No valid JSON output found") self.log(f"Command stdout: {stdout}") return { "success": False, "error": "No valid JSON output found in FreeCAD response", "stdout": stdout, "stderr": stderr, } except json.JSONDecodeError as e: self.log(f"Error parsing JSON output: {e}") return { "success": False, "error": f"Error parsing output: {e}", "stdout": stdout, "stderr": stderr, } except Exception as e: self.log(f"Error executing command: {type(e).__name__}: {e}") return { "success": False, "error": f"Error executing command: {type(e).__name__}: {e}", } def get_version(self): """Get FreeCAD version""" return self.execute_command("get_version") def create_document(self, name="Unnamed"): """Create a new document""" return self.execute_command("create_document", {"name": name}) def create_box(self, length=10.0, width=10.0, height=10.0, doc_name=None): """Create a box""" params = {"length": length, "width": width, "height": height} if doc_name: params["document"] = doc_name return self.execute_command("create_box", params) def export_stl(self, obj_name, file_path, doc_name=None): """Export an object to STL""" params = {"object": obj_name, "path": file_path} if doc_name: params["document"] = doc_name return self.execute_command("export_stl", params) # Test function if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Test FreeCAD Launcher") parser.add_argument("--apprun", action="store_true", help="Use AppRun mode") parser.add_argument( "--path", default="/usr/bin/freecad", help="Path to FreeCAD or AppImage extraction directory", ) args = parser.parse_args() launcher = FreeCADLauncher( freecad_path=args.path, debug=True, use_apprun=args.apprun ) # Test get version # print("Testing get_version...") # version_info = launcher.get_version() # print(f"Version info: {version_info}") # Test create document print("\nTesting create_document...") doc_result = launcher.create_document("TestDoc") print(f"Create document result: {doc_result}") if doc_result.get("success"): doc_name = doc_result.get("document_name") # Test create box print("\nTesting create_box...") box_result = launcher.create_box(10, 20, 30, doc_name) print(f"Create box result: {box_result}") if box_result.get("success"): box_name = box_result.get("box_name") # Test export STL print("\nTesting export_stl...") export_result = launcher.export_stl( box_name, os.path.join(os.getcwd(), "test_export.stl"), doc_name ) print(f"Export result: {export_result}") else: print("Version check failed, skipping other tests")

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server