279 lines
9.7 KiB
Python
279 lines
9.7 KiB
Python
|
|
# -*- 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
|
||
|
|
|