diff --git a/app.py b/app.py index 2c49557..414169f 100644 --- a/app.py +++ b/app.py @@ -173,6 +173,57 @@ def get_footprints_in_library(library_path): return sorted(footprints) +def extract_embedded_model(footprint_path, footprint_name): + """Extract embedded 3D model from a KiCad footprint file. + + Returns: + dict with 'name', 'data' (base64), 'type' if found, None otherwise + """ + try: + footprint_file = os.path.join(footprint_path, f"{footprint_name}.kicad_mod") + if not os.path.exists(footprint_file): + return None + + with open(footprint_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Look for embedded_files section + embedded_pattern = r'\(embedded_files\s+(.*?)\n\t\)\s*\n\t\(model' + match = re.search(embedded_pattern, content, re.DOTALL) + + if not match: + return None + + embedded_section = match.group(1) + + # Extract file name + name_match = re.search(r'\(name\s+"([^"]+)"\)', embedded_section) + if not name_match: + return None + model_name = name_match.group(1) + + # Extract type + type_match = re.search(r'\(type\s+(\w+)\)', embedded_section) + model_type = type_match.group(1) if type_match else 'model' + + # Extract base64 data (multiline, starts with |) + data_match = re.search(r'\(data\s+\|(.*?)\n\t\t\t\)', embedded_section, re.DOTALL) + if not data_match: + return None + + # Clean up the base64 data (remove whitespace and newlines) + base64_data = re.sub(r'\s+', '', data_match.group(1)) + + return { + 'name': model_name, + 'type': model_type, + 'data': base64_data + } + + except Exception as e: + print(f"Error extracting embedded model: {e}") + return None + def extract_symbol_graphics(file_path, symbol_name): """Extract graphical elements from a symbol for rendering.""" try: @@ -2082,6 +2133,58 @@ def handle_render_footprint(data): import traceback emit('library_error', {'error': f'{str(e)}\n\nTraceback:\n{traceback.format_exc()}'}) +@socketio.on('get_footprint_model') +def handle_get_footprint_model(data): + """Extract and return embedded 3D model from a footprint.""" + try: + library_path = data.get('library_path', '') + footprint_name = data.get('footprint_name', '') + + if not library_path or not footprint_name: + emit('model_result', {'has_model': False}) + return + + if not os.path.exists(library_path): + emit('model_result', {'has_model': False}) + return + + # Extract embedded model + model_data = extract_embedded_model(library_path, footprint_name) + + if model_data: + # Decode base64 to get the actual file data + import base64 + import zstandard as zstd + + try: + # Decode base64 + compressed_data = base64.b64decode(model_data['data']) + # Decompress (KiCad uses zstd compression) + dctx = zstd.ZstdDecompressor() + decompressed_data = dctx.decompress(compressed_data) + # Re-encode as base64 for transmission + model_base64 = base64.b64encode(decompressed_data).decode('ascii') + + emit('model_result', { + 'has_model': True, + 'name': model_data['name'], + 'type': model_data['type'], + 'data': model_base64, + 'format': 'step' # Assuming STEP format + }) + except Exception as e: + print(f"Error decoding model data: {e}") + import traceback + traceback.print_exc() + emit('model_result', {'has_model': False}) + else: + emit('model_result', {'has_model': False}) + + except Exception as e: + import traceback + print(f"Error extracting model: {traceback.format_exc()}") + emit('model_result', {'has_model': False}) + # --------------------------------------------------------------------------- # BOM Generation # --------------------------------------------------------------------------- diff --git a/templates/index.html b/templates/index.html index a86ecfd..8cf3314 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,6 +5,8 @@