You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

475 lines
20 KiB
Python

# -*- coding: utf-8 -*-
"""
/***************************************************************************
GemeindeMessdatenImport
A QGIS plugin
Importiert Messdaten im TXT-Format in die Gemeindeeigene Messdaten-Datenbank.
-------------------
begin : 2024-02-01
git sha : $Format:%H$
copyright : (C) 2024 by Sebastian Pertl
email : sebastian.pertl@fridolfing.bayern.de
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QComboBox
from qgis.PyQt import uic
from qgis.PyQt import QtWidgets
from qgis.PyQt.QtWidgets import QFileDialog, QMessageBox, QInputDialog, QLineEdit
#from qgis.core import QgsVectorLayer, QgsVectorFileWriter, QgsProviderRegistry
#from qgis.core import QgsProject, QgsFeature
from qgis.core import *
from qgis.utils import iface
import qgis.core
import toml
# Initialize Qt resources from file resources.py
# from .resources import *
# Import the code for the dialog
#from .gdeMessdatenImport_dialog import GemeindeMessdatenImportDialog
import os
import os.path
import ezdxf
import pandas as pd
import numpy as np
import networkx as nx
import math
import scipy
import sqlite3
import subprocess
import win32com.client
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
hostName = "localhost"
serverPort = 8080
file_path = "D:/Daten/nc/Ingenieurbüro/98-CAD-Daten/Tools-QGIS/ibpTools/"
with open(file_path + 'config.toml', 'r') as f:
CFG = toml.load(f)
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
self.wfile.write(bytes("</body></html>", "utf-8"))
basepath = os.path.dirname(os.path.realpath(__file__))
TEIGHA_PATH = basepath.replace("\\","/") + "/Teigha File Converter 4.3.2/TeighaFileConverter.exe"
tmp_folder = r"C:/tmp"
class ibpTools:
"""QGIS Plugin Implementation."""
def __init__(self, iface):
"""Constructor.
:param iface: An interface instance that will be passed to this class
which provides the hook by which you can manipulate the QGIS
application at run time.
:type iface: QgsInterface
"""
# Save reference to the QGIS interface
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
# Declare instance attributes
# Eintrag Menüband Oben
self.actions = []
self.menu = u'&IBP: Kanalplanung-Tools'
# Check if plugin was started the first time in current QGIS session
# Must be set in initGui() to survive plugin reloads
self.first_start = None
inp_file = 'D:/Daten/nc/Ingenieurbüro/20 Projekte/Kanalplanung/kanalplanung.gpkg'
self.md = QgsProviderRegistry.instance().providerMetadata("ogr")
self.gpkg_con = self.md.createConnection( inp_file, {})
self.doc = False
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
#
self.toolBar = self.iface.addToolBar("IBP: Kanalplanung-Tools")
self.actList = []
icon_path = os.path.join(self.plugin_dir, 'icon_filter.png')
act = QAction(QIcon(icon_path), u'Setze Projektfilter', self.iface.mainWindow())
act.triggered.connect(self.run_projectFilter)
self.actList.append(act)
icon_path = os.path.join(self.plugin_dir, 'icon_setup.png')
act = QAction(QIcon(icon_path), u'Initialisiere BricsCAD', self.iface.mainWindow())
act.triggered.connect(self.run_setup)
self.actList.append(act)
icon_path = os.path.join(self.plugin_dir, 'icon_import.png')
act = QAction(QIcon(icon_path), u'Importiere von BricsCAD', self.iface.mainWindow())
act.triggered.connect(self.run_import_from_bricscad)
self.actList.append(act)
icon_path = os.path.join(self.plugin_dir, 'icon_export.png')
act = QAction(QIcon(icon_path), u'Übertrag in BricsCAD', self.iface.mainWindow())
act.triggered.connect(self.run_export_to_bricscad)
self.actList.append(act)
for i in self.actList:
self.toolBar.addAction(i)
self.iface.addPluginToMenu(self.menu, i)
self.first_start = True
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
for action in self.actions:
self.iface.removePluginMenu(u'&IBP: Kanalplanung-Tools', action)
self.iface.removeToolBarIcon(action)
def run_projectFilter(self):
qid = QInputDialog()
filter, ok = QInputDialog.getText( qid, "Projektfilter", "Projektnummmer", QLineEdit.Normal, "")
self.gpkg_con.executeSql("UPDATE projekt SET aktiv = 0;")
if filter != "":
self.gpkg_con.executeSql("UPDATE projekt SET aktiv = 1 WHERE nr='{}';".format(filter))
#for i in self.iface.mapCanvas().layers():
for i in QgsProject.instance().mapLayers().values():
if type(i) == type(QgsVectorLayer()):
idx = i.fields().indexFromName('ibpprj')
if idx >= 0:
i.setSubsetString("ibpprj LIKE '%"+filter+"%'")
else:
row.setSubsetString("")
def run_setup(self):
self.acad = win32com.client.Dispatch("BricscadApp.AcadApplication")
self.doc = self.acad.ActiveDocument
self.model = self.doc.ModelSpace
QMessageBox.information(None, "Information", "BricsCAD gestartet und Datei gewählt: "+self.doc.name)
def run_import_from_bricscad(self):
"""
Lädt alle Layer von BricsCAD in QGIS die mit "_IBP_QIN_" beginnen.
"""
if self.doc == False:
QMessageBox.warning(None, "Warnung", "Verbindung noch nicht initialisiert!")
else:
self.doc.save()
ibp_dwg_to_dxf(self.doc.name, self.doc.path.replace("\\","/"))
file_path = self.doc.path.replace("\\","/")+self.doc.name.replace(".dwg",".dxf")
print(file_path)
s_doc = ezdxf.readfile(file_path)
s_msp = s_doc.modelspace()
# QGIS-Daten
laylist = {}
laylist['cad_punkt'] = {"qgis_handle":[], "add_new":[], "qgis_lay": None}
laylist['cad_linie'] = {"qgis_handle":[], "add_new":[], "qgis_lay": None}
#@todo: Wenn Layer nicht existiert soll er neu angelegt werden
for key, val in laylist.items():
val['qgis_lay'] = QgsProject.instance().mapLayersByName(key)[0]
val['qgis_lay'].startEditing()
for i in val['qgis_lay'].getFeatures():
if i['doc'] == self.doc.name:
val['qgis_handle'].append(i['handle'])
# Übertrage alle Elemente des Bricscad Layer "_IBP_QIN_..." in QGIS
cad_qgis_obj = []
for i in s_msp.query('*[layer?"{}.*"]'.format(CFG['qgs_cad']['import_layer_prefix'])):
cad_qgis_obj.append(i)
#print(cad_qgis_obj)
for i in cad_qgis_obj:
layer = ""
if i.dxftype() in ["INSERT","POINT","CIRCLE"]:
layer = "cad_punkt"
elif i.dxftype() in ["LINE","LWPOLYLINE"]:
layer = "cad_linie"
else:
continue
new_feat = False
if i.dxf.handle not in laylist[layer]['qgis_handle']:
new_feat = True
feature = QgsFeature(laylist[layer]['qgis_lay'].fields())
feature['layer'] = i.dxf.layer
feature['handle'] = i.dxf.handle
feature['doc'] = self.doc.name
feature['type'] = i.dxftype()
else:
for j in laylist[layer]['qgis_lay'].getFeatures():
if j['handle'] == i.dxf.handle and j['doc'] == self.doc.name:
feature = j
if i.dxftype() == "LINE":
geom = QgsGeometry.fromPolylineXY([QgsPointXY(i.dxf.start[0],i.dxf.start[1]),
QgsPointXY(i.dxf.end[0],i.dxf.end[1])])
elif i.dxftype() == "INSERT":
geom = QgsGeometry.fromPointXY(QgsPointXY(i.dxf.insert[0],i.dxf.insert[1]))
feature['block_name'] = i.dxf.name
elif i.dxftype() == "LWPOLYLINE":
pts = []
for j in i.vertices():
pts.append(QgsPointXY(j[0],j[1]))
##Kontrolle ob geschlossen
if i.dxf.flags == 1:
geom = QgsGeometry.fromPolygonXY([pts])
else:
geom = QgsGeometry.fromPolylineXY(pts)
elif i.dxftype() == "POINT":
geom = QgsGeometry.fromPointXY(QgsPointXY(i.dxf.location[0],i.dxf.location[1]))
elif i.dxftype() == "CIRCLE":
geom = QgsGeometry.fromPointXY(QgsPointXY(i.dxf.center[0],i.dxf.center[1]))
if new_feat:
feature.setGeometry(geom)
laylist[layer]['add_new'].append(feature)
else:
laylist[layer]['qgis_lay'].updateFeature(feature)
laylist[layer]['qgis_lay'].changeGeometry(feature.id(), geom)
for key, val in laylist.items():
val['qgis_lay'].commitChanges()
val['qgis_lay'].dataProvider().addFeatures(val['add_new'])
self.iface.mapCanvas().refreshAllLayers()
def run_export_to_bricscad(self):
if self.doc == False:
QMessageBox.warning(None, "Warnung", "Verbindung noch nicht initialisiert!")
else:
lgp_file = self.doc.path.replace("\\","/")+self.doc.name.replace(".dwg","_Lageplan.dxf")
try:
ibp_dwg_to_dxf(lgp_file.split("/")[-1].replace(".dxf",".dwg"),lgp_file.replace(lgp_file.split("/")[-1],""))
lgp_doc = ezdxf.readfile(lgp_file)
except:
lgp_doc = ezdxf.readfile('C:/Daten/nc/Ingenieurbüro/99-Vorlagen/ezdxf_vorl.dxf')
lgp_msp = lgp_doc.modelspace()
lay = QgsProject.instance().mapLayersByName('schacht')[0]
lay.startEditing()
for i in lay.getFeatures():
if i['schacht_dn'] > 0:
rad = i['schacht_dn']/1000/2
else:
if i['fallrohr'] > 0:
rad = i['fallrohr']/1000/2
else:
rad = 0.05
if len(lgp_msp.query().filter(lambda e: "QGIS:schacht:symb:{}".format(i['fid']) in e.get_hyperlink())) == 1:
# Element vorhanden
#ent_symb = lgp_msp.query('*[handle=="'+i['cad_out_handle_symb']+'"]')[0]
ent_symb = lgp_msp.query().filter(lambda e: "QGIS:schacht:symb:{}".format(i['fid']) in e.get_hyperlink())[0]
else:
# Element anlegen
ent_symb = lgp_msp.add_circle((0,0), 1)
ent_symb.dxf.layer = '_IBP_QO_EW_{}_SCH'.format(i['art'])
ent_symb.dxf.center = (i.geometry().asPoint().x(), i.geometry().asPoint().y())
ent_symb.dxf.radius = rad
if len(lgp_msp.query().filter(lambda e: "QGIS:schacht:txt1:{}".format(i['fid']) in e.get_hyperlink())) == 1:
# Element vorhanden
# ent_txt = lgp_msp.query('*[handle=="{}"]'.format(i['cad_out_handle_txt']))[0]
ent_txt = lgp_msp.query().filter(lambda e: "QGIS:schacht:txt1:{}".format(i['fid']) in e.get_hyperlink())[0]
else:
# Element anlegen
ent_txt = lgp_msp.add_mtext("abc")
ent_txt.dxf.insert = ent_symb.dxf.center
ent_txt.dxf.layer = '_IBP_QO_EW_{}_SCH_Txt'.format(i['art'])
ent_txt.dxf.char_height = 0.25
ent_txt.dxf.style = 'IBP_Norm'
ent_txt.text = "{}".format(i['name'])
if i['schacht_dn'] != NULL:
ent_txt.text+="\nDN{:.0f}".format(i['schacht_dn'])
if i['dh'] != NULL:
ent_txt.text+="\nDH: {:.2f}".format(i['dh'])
if i['sh'] != NULL:
ent_txt.text+="\nSH: {:.2f}".format(i['sh'])
#i['cad_out_handle_symb'] = ent_symb.dxf.handle
#i['cad_out_handle_txt'] = ent_txt.dxf.handle
ent_symb.set_hyperlink("QGIS:schacht:symb:{}".format(i['fid']))
ent_txt.set_hyperlink("QGIS:schacht:txt1:{}".format(i['fid']))
#lay.commitChanges()
lay = QgsProject.instance().mapLayersByName('haltung')[0]
lay.startEditing()
for i in lay.getFeatures():
points = []
for j in i.geometry().vertices():
points.append((j.x(),j.y()))
qry = lgp_msp.query().filter(lambda e: "QGIS:haltung:symb:{}".format(i['fid']) in e.get_hyperlink())
if len(qry) == 1:
# Element vorhanden
#ent_symb = lgp_msp.query('*[handle=="{}"]'.format(i['cad_out_handle_symb']))[0]
ent_symb = qry[0]
else:
# Element anlegen
ent_symb = lgp_msp.add_lwpolyline(points)
ent_symb.dxf.layer = '_IBP_QO_EW_{}_Lei'.format(i['art'])
#i['cad_out_handle_symb'] = ent_symb.dxf.handle
ent_symb.set_hyperlink("QGIS:haltung:symb:{}".format(i['fid']))
mid_point = i.geometry().interpolate(i.geometry().length()/2)
mid_point_c = [mid_point.asPoint().x(), mid_point.asPoint().y()]
mid_angle = i.geometry().interpolateAngle(i.geometry().length()/2)
mid_angle_deg = 90-mid_angle/math.pi*180
if mid_angle_deg < 0:
mid_angle_deg+= 360
if mid_angle_deg > 90 and mid_angle_deg < 270:
mid_angle_deg-= 180
qry = lgp_msp.query().filter(lambda e: "QGIS:haltung:txt1:{}".format(i['fid']) in e.get_hyperlink())
if len(qry) == 1:
# Element vorhanden
#ent_txt = lgp_msp.query('*[handle=="{}"]'.format(i['cad_out_handle_txt']))[0]
ent_txt = qry[0]
else:
# Element anlegen
ent_txt = lgp_msp.add_mtext("abc")
ent_txt.dxf.insert = [mid_point_c[0]+math.cos((mid_angle_deg+90)/180*math.pi)*.1,
mid_point_c[1]+math.sin((mid_angle_deg+90)/180*math.pi)*.1]
ent_txt.dxf.layer = '_IBP_QO_EW_{}_Lei_Txt'.format(i['art'])
ent_txt.dxf.rotation = mid_angle_deg
ent_txt.dxf.attachment_point = ezdxf.lldxf.const.MTEXT_BOTTOM_CENTER
ent_txt.dxf.char_height = 0.25
ent_txt.dxf.style = 'IBP_Norm'
ent_txt.text = "{}".format(i['mat'])
#i['cad_out_handle_txt'] = ent_txt.dxf.handle
ent_txt.set_hyperlink("QGIS:haltung:txt1:{}".format(i['fid']))
qry = lgp_msp.query().filter(lambda e: "QGIS:haltung:txt2:{}".format(i['fid']) in e.get_hyperlink())
if len(qry) == 1:
# Element vorhanden
#ent_txt = lgp_msp.query('*[handle=="{}"]'.format(i['cad_out_handle_txt2']))[0]
ent_txt = qry[0]
else:
# Element anlegen
ent_txt = lgp_msp.add_mtext("abc")
ent_txt.dxf.insert = [mid_point_c[0]-math.cos((mid_angle_deg+90)/180*math.pi)*.1,
mid_point_c[1]-math.sin((mid_angle_deg+90)/180*math.pi)*.1]
ent_txt.dxf.layer = '_IBP_QO_EW_{}_Lei_Txt'.format(i['art'])
ent_txt.dxf.rotation = mid_angle_deg
ent_txt.dxf.attachment_point = ezdxf.lldxf.const.MTEXT_TOP_CENTER
ent_txt.dxf.char_height = 0.25
ent_txt.dxf.style = 'IBP_Norm'
if i['gefaelle'] == qgis.core.NULL:
ent_txt.text = '{:.2f}m'.format(i['c_laenge'])
else:
ent_txt.text = '{:.2f}‰; {:.2f}m'.format(i['gefaelle'], i['c_laenge'])
#i['cad_out_handle_txt2'] = ent_txt.dxf.handle
ent_txt.set_hyperlink("QGIS:haltung:txt2:{}".format(i['fid']))
#lay.updateFeature(i) #Update-QGIS-Layer
lay.commitChanges()
lgp_doc.saveas(lgp_file)
ibp_dxf_to_dwg(lgp_file.split("/")[-1],lgp_file.replace(lgp_file.split("/")[-1],""))
def run_init(self):
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
self.dlg = ibpToolsDialog()
# PARAMS:
# Input folder
# Output folder
# Output version: ACAD9, ACAD10, ACAD12, ACAD14, ACAD2000, ACAD2004, ACAD2007, ACAD20010, ACAD2013, ACAD2018
# Output file type: DWG, DXF, DXB
# Recurse Input Folder: 0, 1
# Audit each file: 0, 1
# (Optional) Input files filter: *.DWG, *.DXF
def ibp_dwg_to_dxf(file, INPUT_FOLDER = tmp_folder, OUTPUT_FOLDER = tmp_folder, RECURSIVE = "0", AUDIT = "1"):
if INPUT_FOLDER != tmp_folder and OUTPUT_FOLDER == tmp_folder:
OUTPUT_FOLDER = INPUT_FOLDER
OUTVER = "ACAD2018"
OUTFORMAT = "DXF"
# Command to run
cmd = [TEIGHA_PATH, INPUT_FOLDER, OUTPUT_FOLDER, OUTVER, OUTFORMAT, RECURSIVE, AUDIT, file]
# Run
subprocess.run(cmd, shell=True)
def ibp_dxf_to_dwg(file, INPUT_FOLDER = tmp_folder, OUTPUT_FOLDER = tmp_folder, RECURSIVE = "0", AUDIT = "1"):
if INPUT_FOLDER != tmp_folder and OUTPUT_FOLDER == tmp_folder:
OUTPUT_FOLDER = INPUT_FOLDER
OUTVER = "ACAD2018"
OUTFORMAT = "DWG"
# Command to run
cmd = [TEIGHA_PATH, INPUT_FOLDER, OUTPUT_FOLDER, OUTVER, OUTFORMAT, RECURSIVE, AUDIT, file]
# Run
subprocess.run(cmd, shell=True)