# -*- coding: utf-8 -*- import os import sys import zipfile import tempfile import shutil import xml.etree.ElementTree as ET from PySide2 import QtWidgets, QtCore, QtGui import FreeCAD import Mesh import Part import Import import pyproj import simplekml import os from datetime import datetime if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP from DraftTools import translate else: def translate(ctxt, txt): return txt try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s from PVPlantResources import DirIcons as DirIcons # Verificación de dependencias try: from pyproj import Transformer except ImportError: QtWidgets.QMessageBox.critical(None, "Error", "pyproj no está instalado.") sys.exit(1) try: import simplekml except ImportError: QtWidgets.QMessageBox.critical(None, "Error", "simplekml no está instalado.") sys.exit(1) class ExportKMZDialog(QtWidgets.QDialog): def __init__(self): super(ExportKMZDialog, self).__init__() self.setWindowTitle("Exportar a KMZ") self.layout = QtWidgets.QVBoxLayout(self) # Selección de archivo FCStd self.file_layout = QtWidgets.QHBoxLayout() self.fcstd_line = QtWidgets.QLineEdit() self.browse_fcstd_btn = QtWidgets.QPushButton("Examinar...") self.browse_fcstd_btn.clicked.connect(self.browse_fcstd) self.file_layout.addWidget(self.fcstd_line) self.file_layout.addWidget(self.browse_fcstd_btn) self.layout.addLayout(self.file_layout) # Sistema de coordenadas self.crs_label = QtWidgets.QLabel("Sistema de coordenadas origen:") self.crs_display = QtWidgets.QLabel("No encontrado") self.layout.addWidget(self.crs_label) self.layout.addWidget(self.crs_display) # Archivo de salida KMZ self.output_layout = QtWidgets.QHBoxLayout() self.kmz_line = QtWidgets.QLineEdit() self.browse_kmz_btn = QtWidgets.QPushButton("Examinar...") self.browse_kmz_btn.clicked.connect(self.browse_kmz) self.output_layout.addWidget(self.kmz_line) self.output_layout.addWidget(self.browse_kmz_btn) self.layout.addWidget(QtWidgets.QLabel("Archivo KMZ de salida:")) self.layout.addLayout(self.output_layout) # Progreso y logs self.progress = QtWidgets.QProgressBar() self.log = QtWidgets.QTextEdit() self.log.setReadOnly(True) self.layout.addWidget(self.progress) self.layout.addWidget(self.log) # Botones self.buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.layout.addWidget(self.buttons) path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "kmz") if not os.path.exists(path): os.makedirs(path) name = datetime.now().strftime("%Y%m%d%H%M%S") + "-" + FreeCAD.ActiveDocument.Name self.filename = os.path.join(path, name) + ".kmz" def browse_fcstd(self): path, _ = QtWidgets.QFileDialog.getOpenFileName( self, "Seleccionar archivo FreeCAD", "", "*.FCStd") if path: self.fcstd_line.setText(path) crs = self.get_crs_from_fcstd(path) self.crs_display.setText(crs if crs else "No encontrado") def get_crs_from_fcstd(self, path): try: with zipfile.ZipFile(path, 'r') as z: doc_xml = z.read('Document.xml') root = ET.fromstring(doc_xml) for prop in root.findall('.//Property[@name="CoordinateSystem"]'): if prop.get('type') == 'App::PropertyString': return prop.find('String').get('value') except Exception as e: self.log.append(f"Error leyendo CRS: {str(e)}") return None def browse_kmz(self): path, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Guardar KMZ", "", "*.kmz") if path: self.kmz_line.setText(path) class ExportKMZ(QtCore.QObject): progress_updated = QtCore.Signal(int) log_message = QtCore.Signal(str) def __init__(self, input_path, output_path, crs): super().__init__() self.input_path = input_path self.output_path = output_path self.crs = crs self.doc = None self.transformer = pyproj.Transformer.from_crs(crs, 'EPSG:4326', always_xy=True) self.temp_dir = tempfile.mkdtemp() self.kml = simplekml.Kml() self.model_folder = self.kml.newfolder(name='Modelos 3D') self.drawing_folder = self.kml.newfolder(name='Dibujos 2D') def process(self): try: self.doc = FreeCAD.openDocument(self.input_path) FreeCAD.setActiveDocument(self.doc.Name) self.process_objects() self.save_kmz() self.log_message.emit("Exportación completada exitosamente.") return True except Exception as e: self.log_message.emit(f"Error: {str(e)}") return False finally: if self.doc: FreeCAD.closeDocument(self.doc.Name) shutil.rmtree(self.temp_dir, ignore_errors=True) def process_objects(self): total = len(self.doc.Objects) for i, obj in enumerate(self.doc.Objects): try: if hasattr(obj, 'Shape') and obj.Shape.Volume > 0: self.process_3d(obj) elif obj.TypeId == 'Sketcher::SketchObject': self.process_2d(obj) except Exception as e: self.log_message.emit(f"Error procesando {obj.Name}: {str(e)}") self.progress_updated.emit(int((i + 1) / total * 100)) def process_3d(self, obj): placement = obj.getGlobalPlacement() x, y, z = placement.Base.x, placement.Base.y, placement.Base.z lon, lat = self.transformer.transform(x, y) temp_doc = FreeCAD.newDocument("TempExport", hidden=True) temp_obj = temp_doc.addObject('Part::Feature', 'TempObj') temp_obj.Shape = obj.Shape.copy() temp_obj.Placement = FreeCAD.Placement() model_path = os.path.join(self.temp_dir, f"{obj.Name}.dae") Import.export(temp_obj, model_path) FreeCAD.closeDocument(temp_doc.Name) color = obj.ViewObject.ShapeColor kml_color = simplekml.Color.rgb( int(color[0] * 255), int(color[1] * 255), int(color[2] * 255)) model = self.model_folder.newmodel(name=obj.Name) model.altitudemode = simplekml.AltitudeMode.relativetoground model.longitude = lon model.latitude = lat model.altitude = z model.model = simplekml.Model() model.model.link.href = f"models/{obj.Name}.dae" model.style = simplekml.Style() model.style.polystyle.color = kml_color def process_2d(self, obj): coords = [] placement = obj.getGlobalPlacement() for geom in obj.Geometry: for point in geom.StartPoint, geom.EndPoint: global_point = placement.multVec(point) lon, lat = self.transformer.transform(global_point.x, global_point.y) coords.append((lon, lat, global_point.z)) if len(coords) < 3: return poly = self.drawing_folder.newpolygon(name=obj.Name) poly.outerboundaryis = coords poly.altitudemode = simplekml.AltitudeMode.relativetoground poly.style.polystyle.color = simplekml.Color.rgb(255, 0, 0, 128) def save_kmz(self): kml_path = os.path.join(self.temp_dir, "doc.kml") self.kml.save(kml_path) with zipfile.ZipFile(self.output_path, 'w') as zipf: zipf.write(kml_path, arcname='doc.kml') for root, _, files in os.walk(self.temp_dir): for file in files: if file.endswith('.dae'): zipf.write( os.path.join(root, file), arcname=os.path.join('models', file)) ''' def main(): app = QtWidgets.QApplication([]) if not FreeCAD.GuiUp else QtWidgets.QApplication.instance() dialog = ExportKMZDialog() if dialog.exec_() == QtWidgets.QDialog.Accepted: input_path = dialog.fcstd_line.text() output_path = dialog.kmz_line.text() crs = dialog.crs_display.text() if not crs: QtWidgets.QMessageBox.critical(None, "Error", "Sistema de coordenadas no definido.") return exporter = ExportKMZ(input_path, output_path, crs) progress_dialog = QtWidgets.QProgressDialog("Exportando...", "Cancelar", 0, 100) exporter.progress_updated.connect(progress_dialog.setValue) exporter.log_message.connect(lambda msg: dialog.log.append(msg)) progress_dialog.show() QtCore.QTimer.singleShot(100, exporter.process) app.exec_() if __name__ == "__main__": main() ''' class CommandExportKMZ: def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "googleearth.svg")), 'Accel': "E, G", 'MenuText': "Export to KMZ", 'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to kmz file")} def Activated(self): taskd = ExportKMZDialog() taskd.setParent(FreeCADGui.getMainWindow()) taskd.setWindowFlags(QtCore.Qt.Dialog or QtCore.Qt.Dialog) taskd.setWindowModality(QtCore.Qt.WindowModal) taskd.show() def IsActive(self): if FreeCAD.ActiveDocument: return True else: return False