diff --git a/Export/exportKMZ.py b/Export/exportKMZ.py new file mode 100644 index 0000000..8f21acd --- /dev/null +++ b/Export/exportKMZ.py @@ -0,0 +1,278 @@ +# -*- 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 +