import math import os.path from collections import OrderedDict import re import attrs import pandas as pd import xlwings as xw from Apyautocad import Apyautocad, APoint import os import numpy as np from time import sleep from attrs import define from typing import List import error @define class SEntry: tower_name: str = "" tower_height: float = 0 tower_type: str = "" mileage_in_s: int = 0 back_k: float = 0 # 杆塔后侧的k值 forth_k: float = 0 # 杆塔前侧的k值 altitude_off: float = 0 # 中心桩高差 foundation_low: float = 0 # 基降 fitting: str = "" # 金具 is_tension_tower: bool = False # 是否为耐张塔 back_representive_span: float = 0 # 后侧代表档距 forth_representive_span: float = 0 # 前侧代表档距 class SFile: def __init__(self) -> None: self.tower_dic = None def open(self, s_file_path): self.tower_dic = OrderedDict() tower_dic = self.tower_dic with open(s_file_path, encoding="gbk") as s_file_obj: new_k = 0 new_reprtv_span = 0 last_k = -1 last_reprtv_span = -1 last_tower_name = "" for line in s_file_obj: norm_line = line.strip() if norm_line == "": continue if "首端转角号" in norm_line: new_k = float(norm_line.split(":")[-1].replace("E", "e")) # 模板系数 new_reprtv_span = float( re.findall(r"代表档距 : {2}(\d+) {3}模板系数", norm_line)[0] ) # 代表档距 continue if "塔号" in norm_line: # next_is_tension_tower=True if last_tower_name != "": tower_dic[last_tower_name].is_tension_tower = True continue norm_entry = re.sub(r"\s+", ",", norm_line) sep_entry = norm_entry.split(",") tower_name = sep_entry[0] if tower_name in tower_dic: tower_dic[tower_name].forth_k = new_k # 更新耐张塔前侧k值。 tower_dic[ tower_name ].forth_representive_span = new_reprtv_span # 更新耐张塔前侧代表档距。 last_k = tower_dic[tower_name].forth_k last_reprtv_span = tower_dic[tower_name].forth_representive_span continue s_entry = SEntry() s_entry.tower_name = tower_name last_tower_name = tower_name s_entry.tower_type = sep_entry[6] s_entry.tower_height = float(sep_entry[7]) s_entry.mileage_in_s = float(sep_entry[1]) s_entry.back_k = last_k s_entry.back_representive_span = last_reprtv_span s_entry.forth_k = new_k s_entry.forth_representive_span = new_reprtv_span s_entry.altitude_off = float(sep_entry[3]) s_entry.foundation_low = float(sep_entry[4]) s_entry.fitting = sep_entry[8] last_k = s_entry.forth_k last_reprtv_span = s_entry.forth_representive_span tower_dic[tower_name] = s_entry tower_dic[list(tower_dic.keys())[-1]].forth_k = -1 tower_dic[list(tower_dic.keys())[0]].is_tension_tower = True tower_dic[list(tower_dic.keys())[-1]].is_tension_tower = True @define class Fitting: # 金具 fitting_length_dic = {} def __init__(self, fitting_file_path): content = [] with open(fitting_file_path) as fitting_file: for line in fitting_file: norm_line = line.strip() if norm_line == "": continue norm_entry = re.sub("\s+", ",", norm_line) content.append(norm_entry.split(",")) ite = iter(content[7:]) for fit in ite: # 跳过前面7行 fit_name = fit[0] fit_parameter = next(ite) self.fitting_length_dic[fit_name] = float(fit_parameter[2]) / 1000 @define class ColorEnume: wire_color_rgb = [122, 219, 245] tree_color_rgb = [240, 226, 81] ground_color_rgb = [82, 79, 254] span_text_color_rgb = [140, 245, 236] representive_span_text_color_rgb = [255, 172, 75] # 读取Z文件,找到Z断面第一个点的坐标 def plane_z_origin(z_file_path): with open(z_file_path) as zfile: content = zfile.read() norm_content = re.sub("\s+", ",", content) sep = norm_content.split(",") return np.array([float(sep[0]), float(sep[1])]) def deduce_zfile_from_cad_path(cad_file_path): dwg_file_name = os.path.split(cad_file_path) dwg_prefix = dwg_file_name[1].split(".")[0] return os.path.join(dwg_file_name[0], f"Z{dwg_prefix}") def deduce_fit_db_from_cad_path(cad_file_path): dwg_file_name = os.path.split(cad_file_path) dwg_prefix = dwg_file_name[1].split(".")[0] return os.path.join(dwg_file_name[0], "Fit.db") def curve_fun(x, span, k, gaocha): # 弧垂公式 return x * gaocha / span - x * (span - x) * k def np2d_to_array(np2d): # 把2维numpy数组转换成cad可以用的数组 t = np.hstack((np2d, np.zeros((np2d.shape[0], 1)))).reshape(1, np2d.shape[0] * 3) return t[0] @define class StringImpactExcel: def read(self, wb, gaocha, span, tension): pos代表档距 = "F13" pos档距 = "L13" pos高差 = "R13" pos张力 = "AB18" pos总串长 = "C8" sheet = wb.sheets["模板"] sheet.range(pos高差).value = gaocha sheet.range(pos档距).value = span sheet.range(pos张力).value = tension string_length = sheet.range(pos总串长).value x = np.linspace(string_length, span, int(span / 5), endpoint=True) x[0] = sheet.range("E23").value x[1] = sheet.range("E24").value sheet.range(f"E25:E{25+len(x)-3}").value = x[2:].reshape(len(x[2:]), 1) y = ( np.array(sheet.range(f"V23:V{23+len(x)-1}").value) / 2 ) # 表格是乘以了2的,为了和x保持一致,没有乘比例。 return (x, y) def set_true_color(object, r_or_rgb_list, g=0, b=0): true_color = object.TrueColor if type(r_or_rgb_list) == List or type(r_or_rgb_list) == list: true_color.SetRGB(*r_or_rgb_list) else: true_color.SetRGB(r_or_rgb_list, g, b) object.TrueColor = true_color @define class StringImpactExcelRecord: from_tower_name: str = "" fo_tower_name: str = "" representive_span: float = 0 # 代表档距 span: float = 0 tension: float = 0 gaocha: float = 0 @define class StringImpactPlate: _dwg_file_path: str _s_file_path: str _draw_start_tower_name: str _continouse_tension_excel: str _string_impact_curve_excel: str _cad: None excel_record_list: List = [] # 记录对excel的操作 def _find_target_tower_index(self, start_tower_name: str, tower_dict): # 从 start_tower_name开始,寻找一个耐张段 index = [] tower_key_list = list(tower_dict.keys()) index.append(tower_key_list.index(start_tower_name)) can_start_find = False for tower_key in tower_key_list: tower_info = tower_dict[tower_key] if tower_info.tower_name == start_tower_name: can_start_find = True continue if can_start_find and tower_info.is_tension_tower == True: found_tower_name = tower_info.tower_name index.append(tower_key_list.index(found_tower_name)) break return index def _plot(self, cad, plot_x, plot_y): plot_vector = np2d_to_array(np.hstack((plot_x, plot_y))) added_curve = cad.model.AddPolyLine(plot_vector) set_true_color(added_curve, *ColorEnume.wire_color_rgb) plot_ground_y = plot_y - 18 * 2 plot_ground_vector = np2d_to_array(np.hstack((plot_x, plot_ground_y))) added_ground_curve = cad.model.AddPolyLine(plot_ground_vector) set_true_color(added_ground_curve, *ColorEnume.ground_color_rgb) plot_tree_y = plot_y - 13.5 * 2 plot_tree_vector = np2d_to_array(np.hstack((plot_x, plot_tree_y))) added_tree_curve = cad.model.AddPolyLine(plot_tree_vector) set_true_color(added_tree_curve, *ColorEnume.tree_color_rgb) def _draw_action(self, excel_app, cad): # 计算代表档距 s_file = SFile() s_file.open(self._s_file_path) tower_dict = s_file.tower_dic tower_key_list = list(tower_dict.keys()) draw_tower_index = self._find_target_tower_index( self._draw_start_tower_name, tower_dict ) fitting_file_path = deduce_fit_db_from_cad_path(self._dwg_file_path) fitting = Fitting(fitting_file_path) z_file_path = deduce_zfile_from_cad_path(self._dwg_file_path) plate_origin = plane_z_origin(z_file_path) continouse_wb = excel_app.books.open(self._continouse_tension_excel) continouse_sheet = continouse_wb.sheets["模板"] wb_string_impact = excel_app.books.open(self._string_impact_curve_excel) stringImpactExcel = StringImpactExcel() sleep(1) draw_first_tower_key = tower_key_list[draw_tower_index[0]] first_tower_info: SEntry = tower_dict[draw_first_tower_key] forth_reprtv_span = first_tower_info.forth_representive_span continouse_sheet.range("B69").value = forth_reprtv_span high_temperature_tension = continouse_sheet.range("L69").value if first_tower_info.is_tension_tower: forth_tower_info: SEntry = tower_dict[ tower_key_list[draw_tower_index[0] + 1] ] if forth_tower_info.is_tension_tower: forth_tower_fitting_length = 0 else: forth_tower_fitting_length = fitting.fitting_length_dic[ forth_tower_info.fitting ] gaocha_of_first_tower = ( ( forth_tower_info.tower_height - forth_tower_info.foundation_low - forth_tower_fitting_length ) - (first_tower_info.tower_height - first_tower_info.foundation_low) + forth_tower_info.altitude_off ) span_of_first_tower = ( forth_tower_info.mileage_in_s - first_tower_info.mileage_in_s ) (x, y) = stringImpactExcel.read( wb_string_impact, gaocha_of_first_tower, span_of_first_tower, high_temperature_tension, ) # TODO: 没有考虑断面中间有耐张塔的情况 plot_x = (plate_origin[0] + x / 5).reshape(len(x), 1) plot_y = ( plate_origin[1] + (first_tower_info.tower_height - first_tower_info.foundation_low + y) * 2 ).reshape(len(x), 1) self._plot(cad, plot_x, plot_y) # 记录 record = StringImpactExcelRecord() record.from_tower_name = first_tower_info.tower_name record.fo_tower_name = forth_tower_info.tower_name record.span = span_of_first_tower record.representive_span = first_tower_info.forth_representive_span record.gaocha = gaocha_of_first_tower record.tension = high_temperature_tension self.excel_record_list.append(record) # 画右侧耐张塔的弧垂 draw_last_tower_key = tower_key_list[draw_tower_index[-1]] last_tower_info: SEntry = tower_dict[draw_last_tower_key] # 最后一个塔位 if last_tower_info.is_tension_tower: back_reprtv_span = last_tower_info.back_representive_span back_tower_info: SEntry = tower_dict[ tower_key_list[draw_tower_index[-1] - 1] ] if back_tower_info.is_tension_tower: back_tower_fitting_length = 0 else: back_tower_fitting_length = fitting.fitting_length_dic[ back_tower_info.fitting ] gaocha_of_last_tower = ( ( back_tower_info.tower_height - back_tower_info.foundation_low - back_tower_fitting_length ) - (last_tower_info.tower_height - last_tower_info.foundation_low) - last_tower_info.altitude_off ) span_of_last_tower = ( last_tower_info.mileage_in_s - back_tower_info.mileage_in_s ) (x, y) = stringImpactExcel.read( wb_string_impact, gaocha_of_last_tower, span_of_last_tower, high_temperature_tension, ) plot_last_tower_x = ( plate_origin[0] + ( tower_dict[tower_key_list[draw_tower_index[-1]]].mileage_in_s - tower_dict[tower_key_list[draw_tower_index[0]]].mileage_in_s ) / 5 - x / 5 # 从右往左画 ).reshape(len(x), 1) accumulate_altitude_off = np.sum( [ tower_dict[tower_key_list[bar]].altitude_off for bar in range(draw_tower_index[0] + 1, draw_tower_index[-1] + 1) ] ) plot_last_tower_y = ( plate_origin[1] + accumulate_altitude_off * 2 + (last_tower_info.tower_height - last_tower_info.foundation_low + y) * 2 ).reshape(len(x), 1) plot_last_tower_vector = np2d_to_array( np.hstack((plot_last_tower_x, plot_last_tower_y)) ) self._plot(cad, plot_last_tower_x, plot_last_tower_y) def draw(self): if self._cad: with xw.App(visible=False) as excel_app: self._draw_action(excel_app, self._cad) else: with xw.App(visible=False) as excel_app, Apyautocad( create_if_not_exists=True, visible=True, auto_close=True ) as cad: cad.app.Documents.Open(self._dwg_file_path) self._draw_action(excel_app, cad) def saveAs(self, save_to): self._cad.SaveAs(save_to) @define class ContinuousPlate: _dwg_file_path: str _s_file_path: str _from_tower_name: str _end_tower_name: str cad: object = None def draw(self): s_file = SFile() s_file_path = self._s_file_path s_file.open(s_file_path) dwg_file_path = self._dwg_file_path with Apyautocad( create_if_not_exists=True, visible=True, auto_close=False ) as cad: self.cad = cad doc = cad.app.Documents.Open(dwg_file_path) sleep(1) tower_dict = s_file.tower_dic z_file_path = deduce_zfile_from_cad_path(dwg_file_path) z_point = plane_z_origin(z_file_path) fitting_file_path = deduce_fit_db_from_cad_path(dwg_file_path) fitting = Fitting(fitting_file_path) fitting_length_dict = fitting.fitting_length_dic first_tower_point = z_point last_tower_info = None # 上一个塔位信息 accu_mileage = 0 # 累计档距 accu_altitude_off = 0 # 累计高差 is_first_tower = True can_start_draw = -1 start_tower_name = self._from_tower_name tower_key_list = list(tower_dict.keys()) draw_count_limit = ( tower_key_list.index(self._end_tower_name) - tower_key_list.index(self._from_tower_name) + 1 ) draw_count = 0 for tower in tower_dict: tower_info = tower_dict[tower] if tower_info.tower_name == start_tower_name: can_start_draw = 0 elif can_start_draw != 0: continue if not last_tower_info: last_tower_info = tower_info if draw_count > draw_count_limit - 1: break foundation_low = tower_info.foundation_low accu_mileage = ( accu_mileage + tower_info.mileage_in_s - last_tower_info.mileage_in_s ) if draw_count == draw_count_limit - 1: # 画代表档距 represented_span_text = f"{tower_info.back_representive_span:.0f}" represented_span_text_point = np.array( [(accu_mileage / 2) / 5 + 50, 1] ) added_represented_span_text = cad.model.AddText( represented_span_text, APoint(*represented_span_text_point.tolist()), 3, ) set_true_color( added_represented_span_text, ColorEnume.representive_span_text_color_rgb, ) if is_first_tower: # 是否是开始画的第一个塔。 accu_altitude_off = 0 else: accu_altitude_off = ( accu_altitude_off + tower_info.altitude_off ) # 中心桩高程 np_tower_start = first_tower_point + np.array( [ accu_mileage / 5, (accu_altitude_off - foundation_low) * 2, ] ) tower_height = tower_info.tower_height if tower_info.is_tension_tower: np_tower_end = first_tower_point + np.array( [ accu_mileage / 5, (accu_altitude_off + tower_height - foundation_low) * 2, ] ) else: np_tower_end = first_tower_point + np.array( [ accu_mileage / 5, ( accu_altitude_off + tower_height - foundation_low - fitting_length_dict[tower_info.fitting] + 3 ) * 2, ] ) # 直线塔杆高只比悬垂挂点高3米 # 画杆高 tower_pole = cad.model.AddPolyLine( np2d_to_array(np.vstack((np_tower_start, np_tower_end))) ) tower_pole.SetWidth(0, 0.8, 0.8) set_true_color(tower_pole, 0, 255, 255) # 画塔名和呼高 cad.model.AddText( f"{tower_info.tower_name}", APoint(*(np_tower_end + np.array([-5, 13])).tolist()), 5, ) if ( abs(math.floor(tower_info.tower_height) - tower_info.tower_height) < 0.1 ): # 考虑了半米呼高的情况 draw_tower_height_str = f"{tower_info.tower_height:.0f}" else: draw_tower_height_str = f"{tower_info.tower_height:.1f}" cad.model.AddText( f"{tower_info.tower_type}-{draw_tower_height_str}", APoint(*(np_tower_end + np.array([-5, 5])).tolist()), 5, ) draw_count += 1 # 画弧垂 if not is_first_tower: # 从第二基塔开始画 draw_k = tower_info.back_k span = tower_info.mileage_in_s - last_tower_info.mileage_in_s last_tower_fiting = last_tower_info.fitting last_tower_fitting_length = fitting_length_dict[last_tower_fiting] if last_tower_info.is_tension_tower: last_tower_fitting_length = 0 tower_fitting = tower_info.fitting tower_fitting_length = fitting_length_dict[tower_fitting] if tower_info.is_tension_tower: tower_fitting_length = 0 last_tower_height = last_tower_info.tower_height last_foundation_low = last_tower_info.foundation_low # 挂点高差 fiting_altitude_off = ( tower_info.altitude_off + (tower_height - foundation_low - tower_fitting_length) - ( last_tower_height - last_foundation_low - last_tower_fitting_length ) ) # 前侧高为正 # 画导线弧垂 x = np.linspace(0, span, int(span), endpoint=True) curve = curve_fun(x, span, draw_k, fiting_altitude_off) draw_curve_x = (first_tower_point[0]) + ( x + accu_mileage - span ) / 5 draw_curve_y = ( first_tower_point[1] + ( +curve + accu_altitude_off - tower_info.altitude_off - last_tower_info.foundation_low + last_tower_info.tower_height - last_tower_fitting_length ) * 2 ) draw_curve_x = draw_curve_x.reshape(len(draw_curve_x), 1) draw_curve_y = draw_curve_y.reshape(len(draw_curve_y), 1) draw_ground_curve_y = draw_curve_y - 18 * 2 # 切地线 draw_tree_curve_y = draw_curve_y - 13.5 * 2 # 切树线 draw_point = np.hstack( (draw_curve_x, draw_curve_y, np.zeros((len(draw_curve_x), 1))) ) draw_ground_curve_point = np.hstack( ( draw_curve_x, draw_ground_curve_y, np.zeros((len(draw_curve_x), 1)), ) ) draw_tree_curve_point = np.hstack( ( draw_curve_x, draw_tree_curve_y, np.zeros((len(draw_curve_x), 1)), ) ) added_curve = cad.model.AddPolyLine( draw_point.reshape(1, draw_curve_x.shape[0] * 3)[0] ) set_true_color(added_curve, *ColorEnume.wire_color_rgb) added_ground_curve = cad.model.AddPolyLine( draw_ground_curve_point.reshape( 1, draw_ground_curve_y.shape[0] * 3 )[0] ) set_true_color(added_ground_curve, *ColorEnume.ground_color_rgb) added_tree_curve = cad.model.AddPolyLine( draw_tree_curve_point.reshape( 1, draw_tree_curve_y.shape[0] * 3 )[0] ) set_true_color(added_tree_curve, *ColorEnume.tree_color_rgb) # 画档距 span_text_insert_point = np.array( [(accu_mileage - span / 2) / 5 + 50, 6] ) added_span_text = cad.model.AddText( f"{span:.0f}", APoint(*span_text_insert_point.tolist()), 3 ) set_true_color(added_span_text, *ColorEnume.span_text_color_rgb) is_first_tower = False last_tower_info = tower_info def saveAs(self, save_to): cad = self.cad doc = cad.doc doc.SaveAs(save_to) @define class ControlFile: _z_excel_file_path: str = attrs.field(init=True, kw_only=False) _z_file_path: str = "" _dwg_file_path: str = "" _from_tower_name: str = "" _end_tower_name: str = "" _consider_string_weight: bool = False _excel_string_weight_path: str = "" _excel_continuous_path: str = "" _s_file_path: str = "" _dir_prefix: str = "" _z_file_name: str = "" _close_cad_document: bool = attrs.field(init=True, kw_only=False, default=True) def __attrs_post_init__(self): z_excel_file_path = self._z_excel_file_path excel_pf = pd.read_excel(z_excel_file_path) pf_dict = excel_pf.to_dict("records")[0] z_excel_path = os.path.split(z_excel_file_path) self._z_file_name = pf_dict["Z文件"] dir_prefix = z_excel_path[0] self._dir_prefix = dir_prefix self._from_tower_name = pf_dict["起始塔号"] self._end_tower_name = pf_dict["终止塔号"] if pf_dict["是否考虑耐张串影响"] == "是": self._consider_string_weight = True self._excel_string_weight_path = pf_dict["计算耐张串影响用表格"] self._excel_continuous_path = pf_dict["计算连续档用表格"] self._z_file_path = os.path.join(dir_prefix, pf_dict["Z文件"]) self._dwg_file_path = os.path.join(dir_prefix, pf_dict["DWG文件"]) self._s_file_path = os.path.join(dir_prefix, pf_dict["S文件"]) def get_zt_dwg_file_path(self): # 获得生成的dwg文件名路径 return os.path.join(self._dir_prefix, "ZT" + self._z_file_name + ".dwg") def draw(self): if not os.path.exists(self._dwg_file_path): raise error.DWGFileNotExistError(self._dwg_file_path) continuous_plate = ContinuousPlate( self._dwg_file_path, self._s_file_path, self._from_tower_name, self._end_tower_name, ) continuous_plate.draw() string_impact_plate = StringImpactPlate( self._dwg_file_path, self._s_file_path, self._from_tower_name, self._excel_continuous_path, self._excel_string_weight_path, continuous_plate.cad, ) string_impact_plate.draw() cad = continuous_plate.cad cad.doc.SaveAs(self.get_zt_dwg_file_path()) # # 画完后再打开 # cad = None # continousePlate = None # with Apyautocad( # create_if_not_exists=True, visible=True, auto_close=False # ) as cad: # cad.app.Documents.Open(self.get_zt_dwg_file_path()) if self._close_cad_document: cad.doc.Close(False)