feat: 创建数据处理UI工程

This commit is contained in:
lilongkun 2026-01-12 09:21:42 +08:00
commit 62900edd85
42 changed files with 104800 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
*.o
*.a
*.exe
*.pyc
*.pyd
*.log
*.temp
*.dat
*.DAT
/other
/pyinstaller/dist
/pyinstaller/build

410
app/color.py Normal file
View File

@ -0,0 +1,410 @@
import math
from PyQt6.QtGui import QColor
color_normal_table = [
QColor(85,87,83),
QColor(239,41,41),
QColor(138,226,52),
QColor(252,233,79),
QColor(114,159,207),
QColor(173,127,168),
QColor(52,226,226),
QColor(238,238,236),
]
color_bold_table = [
QColor(46,52,54),
QColor(204,0,0),
QColor(78,154,6),
QColor(196,160,0),
QColor(52,101,164),
QColor(117,80,123),
QColor(6,152,154),
QColor(211,215,207),
]
color4_table = [
QColor(12, 12, 12),
QColor(197, 15, 31),
QColor(19, 161, 14),
QColor(193, 156, 0),
QColor(0, 55, 218),
QColor(136, 23, 218),
QColor(58, 150, 221),
QColor(204, 204, 204),
QColor(118, 118, 118),
QColor(231, 72, 86),
QColor(22, 198, 12),
QColor(249, 241, 165),
QColor(59, 120, 255),
QColor(180, 0, 158),
QColor(97, 214, 214),
QColor(242, 242, 242),
]
# color4_table = [
# QColor(1, 1, 1),
# QColor(222, 56, 43),
# QColor(57, 181, 74),
# QColor(255, 199, 6),
# QColor(0, 111, 184),
# QColor(118, 38, 113),
# QColor(44, 181, 233),
# QColor(204, 204, 204),
# QColor(128, 128, 128),
# QColor(255, 0, 0),
# QColor(0, 255, 0),
# QColor(255, 255, 0),
# QColor(0, 0, 255),
# QColor(255, 0, 255),
# QColor(0, 255, 255),
# QColor(255, 255, 255),
# ]
color8_table = [
QColor(0x00, 0x00, 0x00),
QColor(0x80, 0x00, 0x00),
QColor(0x00, 0x80, 0x00),
QColor(0x80, 0x80, 0x00),
QColor(0x00, 0x00, 0x80),
QColor(0x80, 0x00, 0x80),
QColor(0x00, 0x80, 0x80),
QColor(0xc0, 0xc0, 0xc0),
QColor(0x80, 0x80, 0x80),
QColor(0xff, 0x00, 0x00),
QColor(0x00, 0xff, 0x00),
QColor(0xff, 0xff, 0x00),
QColor(0x00, 0x00, 0xff),
QColor(0xff, 0x00, 0xff),
QColor(0x00, 0xff, 0xff),
QColor(0xff, 0xff, 0xff),
QColor(0x00, 0x00, 0x00),
QColor(0x00, 0x00, 0x5f),
QColor(0x00, 0x00, 0x87),
QColor(0x00, 0x00, 0xaf),
QColor(0x00, 0x00, 0xd7),
QColor(0x00, 0x00, 0xff),
QColor(0x00, 0x5f, 0x00),
QColor(0x00, 0x5f, 0x5f),
QColor(0x00, 0x5f, 0x87),
QColor(0x00, 0x5f, 0xaf),
QColor(0x00, 0x5f, 0xd7),
QColor(0x00, 0x5f, 0xff),
QColor(0x00, 0x87, 0x00),
QColor(0x00, 0x87, 0x5f),
QColor(0x00, 0x87, 0x87),
QColor(0x00, 0x87, 0xaf),
QColor(0x00, 0x87, 0xd7),
QColor(0x00, 0x87, 0xff),
QColor(0x00, 0xaf, 0x00),
QColor(0x00, 0xaf, 0x5f),
QColor(0x00, 0xaf, 0x87),
QColor(0x00, 0xaf, 0xaf),
QColor(0x00, 0xaf, 0xd7),
QColor(0x00, 0xaf, 0xff),
QColor(0x00, 0xd7, 0x00),
QColor(0x00, 0xd7, 0x5f),
QColor(0x00, 0xd7, 0x87),
QColor(0x00, 0xd7, 0xaf),
QColor(0x00, 0xd7, 0xd7),
QColor(0x00, 0xd7, 0xff),
QColor(0x00, 0xff, 0x00),
QColor(0x00, 0xff, 0x5f),
QColor(0x00, 0xff, 0x87),
QColor(0x00, 0xff, 0xaf),
QColor(0x00, 0xff, 0xd7),
QColor(0x00, 0xff, 0xff),
QColor(0x5f, 0x00, 0x00),
QColor(0x5f, 0x00, 0x5f),
QColor(0x5f, 0x00, 0x87),
QColor(0x5f, 0x00, 0xaf),
QColor(0x5f, 0x00, 0xd7),
QColor(0x5f, 0x00, 0xff),
QColor(0x5f, 0x5f, 0x00),
QColor(0x5f, 0x5f, 0x5f),
QColor(0x5f, 0x5f, 0x87),
QColor(0x5f, 0x5f, 0xaf),
QColor(0x5f, 0x5f, 0xd7),
QColor(0x5f, 0x5f, 0xff),
QColor(0x5f, 0x87, 0x00),
QColor(0x5f, 0x87, 0x5f),
QColor(0x5f, 0x87, 0x87),
QColor(0x5f, 0x87, 0xaf),
QColor(0x5f, 0x87, 0xd7),
QColor(0x5f, 0x87, 0xff),
QColor(0x5f, 0xaf, 0x00),
QColor(0x5f, 0xaf, 0x5f),
QColor(0x5f, 0xaf, 0x87),
QColor(0x5f, 0xaf, 0xaf),
QColor(0x5f, 0xaf, 0xd7),
QColor(0x5f, 0xaf, 0xff),
QColor(0x5f, 0xd7, 0x00),
QColor(0x5f, 0xd7, 0x5f),
QColor(0x5f, 0xd7, 0x87),
QColor(0x5f, 0xd7, 0xaf),
QColor(0x5f, 0xd7, 0xd7),
QColor(0x5f, 0xd7, 0xff),
QColor(0x5f, 0xff, 0x00),
QColor(0x5f, 0xff, 0x5f),
QColor(0x5f, 0xff, 0x87),
QColor(0x5f, 0xff, 0xaf),
QColor(0x5f, 0xff, 0xd7),
QColor(0x5f, 0xff, 0xff),
QColor(0x87, 0x00, 0x00),
QColor(0x87, 0x00, 0x5f),
QColor(0x87, 0x00, 0x87),
QColor(0x87, 0x00, 0xaf),
QColor(0x87, 0x00, 0xd7),
QColor(0x87, 0x00, 0xff),
QColor(0x87, 0x5f, 0x00),
QColor(0x87, 0x5f, 0x5f),
QColor(0x87, 0x5f, 0x87),
QColor(0x87, 0x5f, 0xaf),
QColor(0x87, 0x5f, 0xd7),
QColor(0x87, 0x5f, 0xff),
QColor(0x87, 0x87, 0x00),
QColor(0x87, 0x87, 0x5f),
QColor(0x87, 0x87, 0x87),
QColor(0x87, 0x87, 0xaf),
QColor(0x87, 0x87, 0xd7),
QColor(0x87, 0x87, 0xff),
QColor(0x87, 0xaf, 0x00),
QColor(0x87, 0xaf, 0x5f),
QColor(0x87, 0xaf, 0x87),
QColor(0x87, 0xaf, 0xaf),
QColor(0x87, 0xaf, 0xd7),
QColor(0x87, 0xaf, 0xff),
QColor(0x87, 0xd7, 0x00),
QColor(0x87, 0xd7, 0x5f),
QColor(0x87, 0xd7, 0x87),
QColor(0x87, 0xd7, 0xaf),
QColor(0x87, 0xd7, 0xd7),
QColor(0x87, 0xd7, 0xff),
QColor(0x87, 0xff, 0x00),
QColor(0x87, 0xff, 0x5f),
QColor(0x87, 0xff, 0x87),
QColor(0x87, 0xff, 0xaf),
QColor(0x87, 0xff, 0xd7),
QColor(0x87, 0xff, 0xff),
QColor(0xaf, 0x00, 0x00),
QColor(0xaf, 0x00, 0x5f),
QColor(0xaf, 0x00, 0x87),
QColor(0xaf, 0x00, 0xaf),
QColor(0xaf, 0x00, 0xd7),
QColor(0xaf, 0x00, 0xff),
QColor(0xaf, 0x5f, 0x00),
QColor(0xaf, 0x5f, 0x5f),
QColor(0xaf, 0x5f, 0x87),
QColor(0xaf, 0x5f, 0xaf),
QColor(0xaf, 0x5f, 0xd7),
QColor(0xaf, 0x5f, 0xff),
QColor(0xaf, 0x87, 0x00),
QColor(0xaf, 0x87, 0x5f),
QColor(0xaf, 0x87, 0x87),
QColor(0xaf, 0x87, 0xaf),
QColor(0xaf, 0x87, 0xd7),
QColor(0xaf, 0x87, 0xff),
QColor(0xaf, 0xaf, 0x00),
QColor(0xaf, 0xaf, 0x5f),
QColor(0xaf, 0xaf, 0x87),
QColor(0xaf, 0xaf, 0xaf),
QColor(0xaf, 0xaf, 0xd7),
QColor(0xaf, 0xaf, 0xff),
QColor(0xaf, 0xd7, 0x00),
QColor(0xaf, 0xd7, 0x5f),
QColor(0xaf, 0xd7, 0x87),
QColor(0xaf, 0xd7, 0xaf),
QColor(0xaf, 0xd7, 0xd7),
QColor(0xaf, 0xd7, 0xff),
QColor(0xaf, 0xff, 0x00),
QColor(0xaf, 0xff, 0x5f),
QColor(0xaf, 0xff, 0x87),
QColor(0xaf, 0xff, 0xaf),
QColor(0xaf, 0xff, 0xd7),
QColor(0xaf, 0xff, 0xff),
QColor(0xd7, 0x00, 0x00),
QColor(0xd7, 0x00, 0x5f),
QColor(0xd7, 0x00, 0x87),
QColor(0xd7, 0x00, 0xaf),
QColor(0xd7, 0x00, 0xd7),
QColor(0xd7, 0x00, 0xff),
QColor(0xd7, 0x5f, 0x00),
QColor(0xd7, 0x5f, 0x5f),
QColor(0xd7, 0x5f, 0x87),
QColor(0xd7, 0x5f, 0xaf),
QColor(0xd7, 0x5f, 0xd7),
QColor(0xd7, 0x5f, 0xff),
QColor(0xd7, 0x87, 0x00),
QColor(0xd7, 0x87, 0x5f),
QColor(0xd7, 0x87, 0x87),
QColor(0xd7, 0x87, 0xaf),
QColor(0xd7, 0x87, 0xd7),
QColor(0xd7, 0x87, 0xff),
QColor(0xd7, 0xaf, 0x00),
QColor(0xd7, 0xaf, 0x5f),
QColor(0xd7, 0xaf, 0x87),
QColor(0xd7, 0xaf, 0xaf),
QColor(0xd7, 0xaf, 0xd7),
QColor(0xd7, 0xaf, 0xff),
QColor(0xd7, 0xd7, 0x00),
QColor(0xd7, 0xd7, 0x5f),
QColor(0xd7, 0xd7, 0x87),
QColor(0xd7, 0xd7, 0xaf),
QColor(0xd7, 0xd7, 0xd7),
QColor(0xd7, 0xd7, 0xff),
QColor(0xd7, 0xff, 0x00),
QColor(0xd7, 0xff, 0x5f),
QColor(0xd7, 0xff, 0x87),
QColor(0xd7, 0xff, 0xaf),
QColor(0xd7, 0xff, 0xd7),
QColor(0xd7, 0xff, 0xff),
QColor(0xff, 0x00, 0x00),
QColor(0xff, 0x00, 0x5f),
QColor(0xff, 0x00, 0x87),
QColor(0xff, 0x00, 0xaf),
QColor(0xff, 0x00, 0xd7),
QColor(0xff, 0x00, 0xff),
QColor(0xff, 0x5f, 0x00),
QColor(0xff, 0x5f, 0x5f),
QColor(0xff, 0x5f, 0x87),
QColor(0xff, 0x5f, 0xaf),
QColor(0xff, 0x5f, 0xd7),
QColor(0xff, 0x5f, 0xff),
QColor(0xff, 0x87, 0x00),
QColor(0xff, 0x87, 0x5f),
QColor(0xff, 0x87, 0x87),
QColor(0xff, 0x87, 0xaf),
QColor(0xff, 0x87, 0xd7),
QColor(0xff, 0x87, 0xff),
QColor(0xff, 0xaf, 0x00),
QColor(0xff, 0xaf, 0x5f),
QColor(0xff, 0xaf, 0x87),
QColor(0xff, 0xaf, 0xaf),
QColor(0xff, 0xaf, 0xd7),
QColor(0xff, 0xaf, 0xff),
QColor(0xff, 0xd7, 0x00),
QColor(0xff, 0xd7, 0x5f),
QColor(0xff, 0xd7, 0x87),
QColor(0xff, 0xd7, 0xaf),
QColor(0xff, 0xd7, 0xd7),
QColor(0xff, 0xd7, 0xff),
QColor(0xff, 0xff, 0x00),
QColor(0xff, 0xff, 0x5f),
QColor(0xff, 0xff, 0x87),
QColor(0xff, 0xff, 0xaf),
QColor(0xff, 0xff, 0xd7),
QColor(0xff, 0xff, 0xff),
QColor(0x08, 0x08, 0x08),
QColor(0x12, 0x12, 0x12),
QColor(0x1c, 0x1c, 0x1c),
QColor(0x26, 0x26, 0x26),
QColor(0x30, 0x30, 0x30),
QColor(0x3a, 0x3a, 0x3a),
QColor(0x44, 0x44, 0x44),
QColor(0x4e, 0x4e, 0x4e),
QColor(0x58, 0x58, 0x58),
QColor(0x62, 0x62, 0x62),
QColor(0x6c, 0x6c, 0x6c),
QColor(0x76, 0x76, 0x76),
QColor(0x80, 0x80, 0x80),
QColor(0x8a, 0x8a, 0x8a),
QColor(0x94, 0x94, 0x94),
QColor(0x9e, 0x9e, 0x9e),
QColor(0xa8, 0xa8, 0xa8),
QColor(0xb2, 0xb2, 0xb2),
QColor(0xbc, 0xbc, 0xbc),
QColor(0xc6, 0xc6, 0xc6),
QColor(0xd0, 0xd0, 0xd0),
QColor(0xda, 0xda, 0xda),
QColor(0xe4, 0xe4, 0xe4),
QColor(0xee, 0xee, 0xee),
]
def qcolor(c):
return QColor(*[int(i) for i in c])
def hsi2rgb(c):
h, s, i = c
pi = math.pi * 2
if h <= pi / 3:
b = (1 - s) / 3
r = (1 + s * math.cos(h) / math.cos(pi / 6 - h)) / 3
g = 1 - (r + b)
elif h <= pi / 3 * 2:
h -= pi / 3
r = (1 - s) / 3
g = (1 + s * math.cos(h) / math.cos(pi / 6 - h)) / 3
b = 1 - (r + g)
else:
h -= pi / 3 * 2
g = (1 - s) / 3
b = (1 + s * math.cos(h) / math.cos(pi / 6 - h)) / 3
r = 1 - (g + b)
r = 3 * i * r
g = 3 * i * g
b = 3 * i * b
return [r, g, b]
def rgb2hsi(c):
r, g, b = c
rg = r - g
rb = r - b
gb = g - b
h = math.acos((rg + rb) / (2 * math.sqrt(rg ** 2 + rb * gb)))
if b > g:
h = 2 * math.pi - h
i = (r + g + b) / 3
s = 1 - min(r, g, b) / i
return [h, s, i]
def hsv2rgb(c):
h, s, v = c
y = v * s
x = y * (1 - abs(h / 60 % 2 - 1))
m = v - y
h %= 360
if h < 0:
h += 360
if h <= 60:
r, g, b = y, x, 0
elif h <= 120:
r, g, b = x, y, 0
elif h <= 180:
r, g, b = 0, y, x
elif h <= 240:
r, g, b = 0, x, y
elif h <= 300:
r, g, b = x, 0, y
else:
r, g, b = y, 0, x
r = (r + m) * 255
g = (g + m) * 255
b = (b + m) * 255
return [r, g, b]
black = "\033[0;30m"
dark_gray = "\033[1;30m"
red = "\033[0;31m"
light_red = "\033[1;31m"
green = "\033[0;32m"
light_green = "\033[1;32m"
orange = "\033[0;33m"
yellow = "\033[1;33m"
blue = "\033[0;34m"
light_blue = "\033[1;34m"
purple = "\033[0;35m"
light_purple = "\033[1;35m"
cyan = "\033[0;36m"
light_cyan = "\033[1;36m"
light_gray = "\033[0;37m"
white = "\033[1;37m"
nc = "\033[0m"

34
app/config.py Normal file
View File

@ -0,0 +1,34 @@
import os
import sys
import yaml
# if os.path.exists("_internal"):
# base_path = "./_internal"
# else:
# base_path = "./"
base_path = "./"
config_path = os.path.join(base_path, "config")
default_file = os.path.join(config_path, "default.yml")
config_file = os.path.join(config_path, "config.yml")
if not os.path.exists(config_path):
os.mkdir(config_path)
if os.path.exists(default_file):
with open(default_file, "r", encoding = "utf-8") as fobj:
config = yaml.safe_load(fobj)
if config is None:
config = {}
else:
config = {}
if os.path.exists(config_file):
with open(config_file, "r", encoding = "utf-8") as fobj:
config_new = yaml.safe_load(fobj)
if not (config_new is None):
for key, value in config_new.items():
config[key] = value
image_path = os.path.join(base_path, "image")
script_path = os.path.join(base_path, "script")
data_path = os.path.join(base_path, "data")
doc_path = os.path.join(base_path, "doc")

10
app/config/config.yml Normal file
View File

@ -0,0 +1,10 @@
main_window:
geometry: !!binary |
AdnQywADAAAAAABCAAAAIAAAB38AAAQ3AAACDAAAAQEAAAchAAADmwAAAAACAAAAB4AAAABCAAAA
RQAAB38AAAQ3
state: !!binary |
AAAA/wAAAAD9AAAAAwAAAAAAAACYAAADWvwCAAAAAfsAAAAWAHcAaQBkAGcAZQB0ACAAdAByAGUA
ZQEAAAAYAAADWgAAAF4A////AAAAAQAAARAAAANa/AIAAAAB+wAAAB4AdwBpAGQAZwBlAHQAIABp
AG4AdABlAHIAYQBjAHQBAAAAGAAAA1oAAACxAP///wAAAAMAAAc+AAAAZfwBAAAAAfsAAAAWAHcA
aQBkAGcAZQB0ACAAdABlAHIAbQEAAAAAAAAHPgAAAJAA////AAAFigAAA1oAAAAEAAAABAAAAAgA
AAAI/AAAAAA=

217
app/config/default.yml Normal file
View File

@ -0,0 +1,217 @@
tree:
- id: coord_calc
label: 坐标计算
- id: measure
label: 轨道数据
display:
coord_calc:
- id: surface_statistics
label: 平面资料
type: table
row: 7
col: 8
column_header:
- 序号
- 交点号
- X(N)坐标
- Y(E)坐标
- 半径
- Ls1
- Ls2
- 桩号
format_script:
format_surface_statistic.py
datafile: surface_statistics.csv
- id: alignment_table
label: 直曲表
type: table
row: 5
col: 22
format_script:
format_alignment_table.py
datafile: alignment_table.csv
span:
- - 0
- 0
- 1
- 21
- - 1
- 0
- 2
- 1
- - 1
- 3
- 2
- 1
- - 1
- 1
- 1
- 2
- - 1
- 4
- 1
- 2
- - 1
- 6
- 1
- 7
- - 1
- 13
- 1
- 5
- - 1
- 18
- 1
- 3
- id: linear_feature
label: 线元要素表
type: table
row: 1
col: 11
column_header:
- 桩号
- 线元长度
- 起始半径
- 结束半径
- 起始曲率
- 结束曲率
- 方位角
- X
- Y
- 线元类型
- 缓曲参数
format_script:
format_linear_feature.py
- id: longitudinal_profile
label: 纵断面
type: table
row: 20
col: 8
column_header:
- 变坡点桩号
- 变坡点高程
- 半径
- 坡长
- i(%)
- L(m)
- T(m)
- E(m)
format_script:
format_longitudinal_profile.py
- id: coord_calc
label: 坐标计算
type: table
row: 6
col: 14
format_script:
format_coord_calc.py
datafile: coord_calc.csv
span:
- - 0
- 0
- 2
- 2
- - 0
- 2
- 1
- 2
- - 0
- 4
- 2
- 1
- - 0
- 5
- 2
- 1
- - 0
- 6
- 1
- 4
- - 0
- 10
- 1
- 4
measure:
- id: measure_data
label: 轨道测量数据
type: table
col: 9
row: 1
column_header:
- 左轨编号
- 左轨高程(m)
- 右轨编号
- 右轨高程(m)
- 左右高差(mm)
- 左侧线形(mm)
- 右侧线形(mm)
- 左侧高低值(mm)
- 右侧高低值(mm)
format_script:
format_measure_data.py
- id: road_graph
label: 轨道竖向线形图
type: graph
axis_x_title: 线路测试断面
axis_y_title: 轨顶高程(m)
- id: road_relative_graph
label: 轨道轨道面相对竖向线形图
type: graph
axis_x_title: 线路测试断面
axis_y_title: 轨顶高程(mm)
- id: road_horizon_graph
label: 轨道水平情况
type: graph
axis_x_title: 线路测试断面
axis_y_title: 轨道水平(mm)
- id: road_concave_graph
label: 轨道高低情况
type: graph
axis_x_title: 线路测试断面
axis_y_title: 轨道高低(mm)
interact:
measure_data:
- type: input
label: 高差参数
id: height_diff_param
- type: button
label: 导入测量数据
script: load_measure_data.py
- type: button
label: 计算线形和高低值
script: calc_measure_data.py
surface_statistics:
- type: button
label: 生成直曲表
script: create_alignment_table.py
alignment_table:
- type: input
label: 直线段桩距
id: line_gap
text: "20"
- type: input
label: 曲线段桩距
id: curve_gap
text: "10"
- type: button
label: 桩号生成
script: create_stake_point.py
longitudinal_profile:
- type: button
label: 参数计算
script: calc_slope_param.py
- type: button
label: 清除计算结果
script: clear_slope_param.py
coord_calc:
- type: button
label: 中桩坐标计算
script: calc_center_stake.py
- type: button
label: 高程计算
script: calc_elevation.py
- type: button
label: 边桩坐标计算
script: calc_side_stake.py

View File

@ -0,0 +1,3 @@
直 线 、曲 线 及 转 角 一 览 表,,,,,,,,,,,,,,,,,,,,
交点号,交点坐标,,交点桩号,偏角值,,曲线要素值,,,,,,,曲线主点桩号,,,,,直线长度及方向,,,左右偏
,N(X),E(Y),,左偏,右偏,R,Ls1,Ls2,T1,T2,L,E,ZH,HY,OZ,YH,HZ,直线长度,交点间距,计算方位角
1 直 线 、曲 线 及 转 角 一 览 表,,,,,,,,,,,,,,,,,,,,
2 交点号,交点坐标,,交点桩号,偏角值,,曲线要素值,,,,,,,曲线主点桩号,,,,,直线长度及方向,,,左右偏
3 ,N(X),E(Y),,左偏,右偏,R,Ls1,Ls2,T1,T2,L,E,ZH,HY,OZ,YH,HZ,直线长度,交点间距,计算方位角

6
app/data/coord_calc.csv Normal file
View File

@ -0,0 +1,6 @@
桩号,,中线坐标,,切线方位角,高程,左边桩坐标,,,,右边桩坐标,,,
,,N(X),E(Y),,,距离,右夹角,N(X),E(Y),距离,右夹角,N(X),E(Y)
,K1+794.807
,K1+799.647
,K1+805.054
,K1+809.894
1 桩号,,中线坐标,,切线方位角,高程,左边桩坐标,,,,右边桩坐标,,,
2 ,,N(X),E(Y),,,距离,右夹角,N(X),E(Y),距离,右夹角,N(X),E(Y)
3 ,K1+794.807
4 ,K1+799.647
5 ,K1+805.054
6 ,K1+809.894

View File

@ -0,0 +1,7 @@
1,QD,3194554.194,501250.714,,,,0
2,JD1,3194824.682,501594.205,700,200,200
3,JD2,3196190.070,501605.670,1500,200,200
4,JD3,3197202.632,502209.577,900,110,110
5,JD4,3197635.505,502305.092,700,130,130
6,JD5,3198039.668,502655.089,700,130,130
7,JD6,3200279.842,502851.404,,,
1 1,QD,3194554.194,501250.714,,,,0
2 2,JD1,3194824.682,501594.205,700,200,200
3 3,JD2,3196190.070,501605.670,1500,200,200
4 4,JD3,3197202.632,502209.577,900,110,110
5 5,JD4,3197635.505,502305.092,700,130,130
6 6,JD5,3198039.668,502655.089,700,130,130
7 7,JD6,3200279.842,502851.404,,,

27
app/data/temp.csv Normal file
View File

@ -0,0 +1,27 @@
,10.11743,,10.15147
,10.1187,,10.15095
,10.12116,,10.15089
,10.10898,,10.14153
,10.10388,,10.12936
,10.08782,,10.11529
,10.06438,,10.09277
,10.04788,,10.07494
,10.03274,,10.06181
,10.01209,,10.04402
,9.99383,,10.01958
,9.97242,,10.00426
,9.94911,,9.96898
,9.92069,,9.94508
,9.89699,,9.92501
,9.87115,,9.9056
,9.85531,,9.89211
,9.85643,,9.89182
,9.85109,,9.88541
,9.82577,,9.85734
,9.8073,,9.83796
,9.80619,,9.83136
,9.80005,,9.83231
,9.79032,,9.8218
,9.79293,,9.82281
,9.77558,,9.8075
,9.75878,,9.79372
1 10.11743 10.15147
2 10.1187 10.15095
3 10.12116 10.15089
4 10.10898 10.14153
5 10.10388 10.12936
6 10.08782 10.11529
7 10.06438 10.09277
8 10.04788 10.07494
9 10.03274 10.06181
10 10.01209 10.04402
11 9.99383 10.01958
12 9.97242 10.00426
13 9.94911 9.96898
14 9.92069 9.94508
15 9.89699 9.92501
16 9.87115 9.9056
17 9.85531 9.89211
18 9.85643 9.89182
19 9.85109 9.88541
20 9.82577 9.85734
21 9.8073 9.83796
22 9.80619 9.83136
23 9.80005 9.83231
24 9.79032 9.8218
25 9.79293 9.82281
26 9.77558 9.8075
27 9.75878 9.79372

63162
app/doc/a.css Normal file

File diff suppressed because one or more lines are too long

37397
app/doc/b.css Normal file

File diff suppressed because it is too large Load Diff

539
app/doc/开发指南.html Normal file

File diff suppressed because one or more lines are too long

98
app/formatter.py Normal file
View File

@ -0,0 +1,98 @@
import re
import math
#将弧度转换为度分秒
def rad2dms(r):
d = r * 180 / math.pi
m = (d - int(d)) * 60
d = int(d)
s = (m - int(m)) * 60
m = int(m)
return d, m, s
#将度分秒转换为弧度
def dms2rad(d, m, s):
return (d + m / 60 + s / 360) / 180 * math.pi
class string:
dtype = str
def __init__(self):
pass
def str2val(self, s):
return s
def val2str(self, v):
return v
class integer:
dtype = int
def __init__(self, fmt = "%d"):
self.fmt = fmt
def str2val(self, s):
s = s.strip()
if not s:
return None
try:
v = int(s)
except:
v = int(float(s))
return v
def val2str(self, v):
return self.fmt % v
class decimal:
dtype = float
def __init__(self, fmt = "%.3f", epsilon = 1e-4):
self.fmt = fmt
self.epsilon = epsilon
def str2val(self, s):
s = s.strip()
if not s:
return None
return float(s)
def val2str(self, v):
if abs(v) < self.epsilon:
v = 0.0
return self.fmt % v
class kdecimal:
dtype = float
def __init__(self, fmt = "%07.3f", epsilon = 1e-4):
self.fmt = fmt
self.epsilon = epsilon
def str2val(self, s):
s = s.strip()
if not s:
return None
match = re.match(r"[kK](\d+)\+(\d+(?:\.\d+)?)", s)
if match is None:
raise RuntimeError(f"pattern not matched: {s}")
a = match.group(1)
b = match.group(2)
return float(a + b)
def val2str(self, v):
a = v // 1000
b = v % 1000
if abs(b - 1000) < self.epsilon:
a += 1
b = 0.0
elif abs(b) < self.epsilon:
b = 0.0
return f"K%d+{self.fmt}" % (a, b)
class angle:
dtype = float
def __init__(self):
pass
def str2val(self, s):
s = s.strip()
if not s:
return None
match = re.match(r"(\d+)°(\d+)(\d+(?:\.\d+)?)″", s)
if match is None:
raise RuntimeError(f"pattern not matched: {s}")
d = int(match.group(1))
m = int(match.group(2))
s = float(match.group(3))
return dms2rad(d, m, s)
def val2str(self, v):
return "%2d°%02d%05.2f" % rad2dms(v)

39
app/main.py Normal file
View File

@ -0,0 +1,39 @@
# -*- encoding: utf-8 -*-
import os
import sys
import argparse
from IPython.terminal.embed import InteractiveShellEmbed as interactive_shell_embed
import IPython.core.page
import config
from main_window import main_window
from main_app import main_app
def page_printer(data, start = 0, screen_lines = 0, pager_cmd = None):
if isinstance(data, dict):
data = data["text/plain"]
print(data)
IPython.core.page.page = page_printer
ipshell = interactive_shell_embed(
user_ns = globals(), colors = "Linux",
)
parser = argparse.ArgumentParser(
prog = "道路数据处理",
description = "",
# epilog = "Text at the bottom of help",
)
parser.add_argument("-f", "--file", type = str, help = "启动前加载的Python脚本文件", default = "")
arg = parser.parse_args()
if __name__ == "__main__":
app = main_app(sys.argv)
window = main_window(ipshell)
window.show()
window.redirect()
if arg.file:
window.load_script(arg.file)
sys.exit(app.exec())

26
app/main_app.py Normal file
View File

@ -0,0 +1,26 @@
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication)
from PyQt6.QtGui import (QPalette, QColor, QColorConstants)
class main_app(QApplication):
def __init__(self, *arg):
super(main_app, self).__init__(*arg)
self.setStyle("Fusion")
palette = QPalette()
palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))
palette.setColor(QPalette.ColorRole.WindowText, QColorConstants.White)
palette.setColor(QPalette.ColorRole.Base, QColor(25, 25, 25))
palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ColorRole.ToolTipBase, QColorConstants.White)
palette.setColor(QPalette.ColorRole.ToolTipText, QColorConstants.Black)
palette.setColor(QPalette.ColorRole.Text, QColorConstants.White)
palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ColorRole.ButtonText, QColorConstants.White)
palette.setColor(QPalette.ColorRole.BrightText, QColorConstants.Red)
palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.ColorRole.HighlightedText, QColorConstants.Black)
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor(150, 150, 150))
self.setPalette(palette)

304
app/main_window.py Normal file
View File

@ -0,0 +1,304 @@
# -*- encoding: utf-8 -*-
import os
import sys
import re
import time
import yaml
import webbrowser
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QMainWindow, QFileDialog,
QMessageBox,
)
from PyQt6.QtGui import (
QIcon, QAction, QFont,
)
import color
import config
from widget_display import widget_display
from widget_tree import widget_tree
from widget_interact import widget_interact
from widget_term import widget_term
import formatter
import sheet
font = QFont("consolas", 10)
font.setFamilies(["consolas", "黑体"])
class message_about(QMessageBox):
def __init__(self, window, *arg):
super(message_about, self).__init__(*arg)
self.window = window
self.setIcon(QMessageBox.Icon.Information)
self.setText("关于")
self.setInformativeText("by 李隆坤\n nugnikoll@qq.com")
self.setWindowTitle("关于")
# self.setDetailedText("The details are as follows:")
self.setStandardButtons(QMessageBox.StandardButton.Ok)
class main_window(QMainWindow):
def __init__(self, ipshell, *arg):
super(main_window, self).__init__(*arg)
self.ipshell = ipshell
self.stdout_save = sys.stdout
self.stderr_save = sys.stderr
sheet.window = self
self.ipshell.user_ns["formatter"] = formatter
self.ipshell.user_ns["sheet"] = sheet.sheet
self.ipshell.user_ns["cell"] = sheet.cell
self.ipshell.user_ns["graph"] = sheet.graph
self.ipshell.user_ns["plot"] = sheet.plot
self.ipshell.user_ns["param"] = sheet.param
self.setFont(font)
self.setWindowTitle("数据处理")
self.statusBar().showMessage("就绪")
self.create_menubar()
self.widget_display = widget_display(self)
self.widget_display.setObjectName("widget display")
self.setCentralWidget(self.widget_display)
self.widget_tree = widget_tree(self)
self.widget_tree.setObjectName("widget tree")
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.widget_tree)
self.widget_interact = widget_interact(self)
self.widget_interact.setObjectName("widget interact")
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.widget_interact)
self.widget_term = widget_term(self)
self.widget_term.setObjectName("widget term")
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.widget_term)
self.widget_display.initial_table()
if "main_window" in config.config:
if "geometry" in config.config["main_window"]:
self.restoreGeometry(config.config["main_window"]["geometry"])
if "state" in config.config["main_window"]:
self.restoreState(config.config["main_window"]["state"])
#redirect iostream
def redirect(self):
self.stdout_save = sys.stdout
self.stderr_save = sys.stderr
sys.stdout = self.widget_term.text_term
sys.stderr = self.widget_term.text_term
def restore_redirect(self):
sys.stdout = self.stdout_save
sys.stderr = self.stderr_save
def create_menubar(self):
menu_file = self.menuBar().addMenu("文件(&F)")
# act_new_game = QAction(
# "新文件(&N)", self, shortcut = "Ctrl+N",
# statusTip = "创建一个新文件", triggered = self.new_file
# )
# menu_file.addAction(act_new_game)
act_import_data = QAction(
"导入数据(&I)", self, shortcut = "Ctrl+I",
statusTip = "从文件导入数据到表格", triggered = self.on_import_data
)
menu_file.addAction(act_import_data)
act_export_data = QAction(
"导出数据(&E)", self, shortcut = "Ctrl+T",
statusTip = "将表格数据导出到文件", triggered = self.on_export_data
)
menu_file.addAction(act_export_data)
act_reset_data = QAction(
"重置数据(&R)", self, #shortcut = "Ctrl+R",
statusTip = "将表格数据重置为初始数据", triggered = self.on_reset_data
)
menu_file.addAction(act_reset_data)
act_load_script = QAction(
"运行脚本(&L)", self, shortcut = "Ctrl+L",
statusTip = "加载并运行一个Python脚本", triggered = self.on_load_script
)
menu_file.addAction(act_load_script)
act_quit = QAction(
"退出(&Q)", self, shortcut = "Alt+F4",
statusTip = "退出程序", triggered = self.close
)
menu_file.addAction(act_quit)
# menu_edit = self.menuBar().addMenu("编辑(&E)")
# act_undo = QAction(
# "撤销(&U)", self, shortcut = "Ctrl+Z",
# statusTip = "撤销最新修改", triggered = self.undo
# )
# menu_edit.addAction(act_undo)
# act_redo = QAction(
# "重做(&R)", self, shortcut = "Ctrl+Y",
# statusTip = "恢复最新修改", triggered = self.redo
# )
# menu_edit.addAction(act_redo)
menu_window = self.menuBar().addMenu("窗口(&W)")
act_widget_tree = QAction(
"项目窗口(&P)", self,
statusTip = "显示/隐藏 项目窗口", triggered = self.toggle_widget_tree
)
menu_window.addAction(act_widget_tree)
act_widget_interact = QAction(
"交互窗口(&I)", self,
statusTip = "显示/隐藏 交互窗口", triggered = self.toggle_widget_interact
)
menu_window.addAction(act_widget_interact)
act_widget_term = QAction(
"终端窗口(&T)", self,
statusTip = "显示/隐藏 终端窗口", triggered = self.toggle_widget_term
)
menu_window.addAction(act_widget_term)
menu_help = self.menuBar().addMenu("帮助(&H)")
act_doc_develop = QAction(
"开发指南(&D)", self, shortcut = "F1",
statusTip = "在浏览器中阅读开发指南", triggered = self.show_doc_develop
)
menu_help.addAction(act_doc_develop)
act_about = QAction(
"关于(&A)", self,
statusTip = "关于数据处理", triggered = self.show_about
)
menu_help.addAction(act_about)
def on_import_data(self, filename = None):
if self.widget_display.get_current_widget() is None:
return
if not filename:
if hasattr(self.on_import_data, "filename"):
path_hint = os.path.dirname(self.on_import_data.filename)
else:
path_hint = ""
filename, _ = QFileDialog.getOpenFileName(self, "从文件导入数据", path_hint, "CSV文件(*.csv)")
if not filename:
return
if len(filename) < 4 or filename[-4:] != ".csv":
filename += ".csv"
filename = filename.replace("\\", "\\\\")
filename = filename.replace("\"", "\\\"")
widget_id = self.widget_display.get_current_id()
self.process(f"sheet(\"{widget_id}\").load_file(\"{filename}\")")
def on_export_data(self, filename = None):
if self.widget_display.get_current_widget() is None:
return
if not filename:
if hasattr(self.on_export_data, "filename"):
path_hint = os.path.dirname(self.on_export_data.filename)
else:
path_hint = ""
filename, _ = QFileDialog.getSaveFileName(self, "导出数据到文件", path_hint, "CSV文件(*.csv)")
if not filename:
return
if len(filename) < 4 or filename[-4:] != ".csv":
filename += ".csv"
filename = filename.replace("\\", "\\\\")
filename = filename.replace("\"", "\\\"")
widget_id = self.widget_display.get_current_id()
self.process(f"sheet(\"{widget_id}\").dump_file(\"{filename}\")")
def on_reset_data(self):
widget_id = self.widget_display.get_current_id()
self.process(f"sheet(\"{widget_id}\").reset_content()")
def get_config(self):
data = {
"main_window": {
"geometry": self.saveGeometry().data(),
"state": self.saveState().data(),
#"widget_interact": self.widget_interact.get_config(),
},
}
return data
def closeEvent(self, event):
self.restore_redirect()
data = self.get_config()
with open(config.config_file, "w", encoding = "utf-8") as fobj:
yaml.safe_dump(data, fobj, allow_unicode = True)
super().closeEvent(event)
#process command
def process(self, s):
s = s.replace("\u2029", "\n")
print(f"{color.light_green}>>{color.nc}" + s)
if not s:
return
if s[0] == "%":
result = self.ipshell.magic(s)
if result:
print(result)
else:
query = re.search(r"[_0-9a-zA-Z.]+\?", s)
if query:
result = self.ipshell.magic("pinfo " + query.group()[:-1])
else:
query = re.search(r"[_0-9a-zA-Z.]+\?\?", s)
if query:
result = self.ipshell.magic("pinfo2 " + query.group()[:-1])
self.ipshell.ex(s)
#load and execute a script
def load_script(self, filename):
with open(filename, "r", encoding = "utf-8") as fobj:
content = fobj.read()
self.ipshell.ex(content)
#load and execute a script
def on_load_script(self, filename = None):
if not filename:
filename, _ = QFileDialog.getOpenFileName(self, "加载python脚本", "", "Python脚本(*.py)")
if not filename:
return
filename = filename.replace("\\", "\\\\")
filename = filename.replace("\"", "\\\"")
self.process(f"window.load_script(\"{filename}\")")
def undo(self):
pass
def redo(self):
pass
def show_doc_develop(self, event):
doc_file = os.path.join(config.doc_path, "开发指南.html")
webbrowser.open_new_tab(doc_file)
def show_about(self, event):
if not hasattr(self, "msg_about"):
self.msg_about = message_about(self)
self.msg_about.exec()
def toggle_widget_interact(self, event):
if self.widget_interact.isVisible():
self.widget_interact.hide()
else:
self.widget_interact.show()
def toggle_widget_tree(self, event):
if self.widget_tree.isVisible():
self.widget_tree.hide()
else:
self.widget_tree.show()
def toggle_widget_term(self, event):
if self.widget_term.isVisible():
self.widget_term.hide()
else:
self.widget_term.show()

View File

@ -0,0 +1,106 @@
#计算中桩坐标
import math
#直曲表
sheet_align = sheet("alignment_table")
#坐标计算表
sheet_coord = sheet("coord_calc")
row1 = 4
row2 = 2
with sheet_align:
ss1 = 0
while not sheet_coord[row2, 1].empty():
lxzh = sheet_coord[row2, 1].value #待计算点桩号
xjd = cell(row1, 1).value #交点x坐标
yjd = cell(row1, 2).value #交点y坐标
kjd = cell(row1, 3).value #jd点桩号
a0 = cell(row1, 20).value #后切线方位角
if cell(row1, 13).empty():
x = xjd - (kjd - lxzh) * math.cos(a0)
y = yjd - (kjd - lxzh) * math.sin(a0)
sheet_coord[row2, 2].value = x
sheet_coord[row2, 3].value = y
sheet_coord[row2, 4].value = a0
else:
a1 = cell(row1 + 1, 20).value #前切线方位角
r = cell(row1, 6).value #圆曲线半径
ls1 = cell(row1, 7).value #第一缓和曲线长
ls2 = cell(row1, 8).value #第二缓和曲线长
t1 = cell(row1, 9).value #第一切线长
t2 = cell(row1, 10).value #第二切线长
ll = cell(row1, 11).value #曲线长
ly = ll - ls1 - ls2 #圆曲线长
kzh = cell(row1, 13).value #zh点桩号
khy = cell(row1, 14).value #hy点桩号
kqz = cell(row1, 15).value #qz点桩号
kyh = cell(row1, 16).value #yh点桩号
khz = cell(row1, 17).value #hz点桩号
zy = cell(row1, 21).value #路线左右偏代码
if lxzh <= kzh: #直线段上的点
l = kzh - lxzh
x = xjd - (t1 + l) * math.cos(a0)
y = yjd - (t1 + l) * math.sin(a0)
sheet_coord[row2, 2].value = x
sheet_coord[row2, 3].value = y
sheet_coord[row2, 4].value = a0
elif lxzh > kzh and lxzh < khy: #第一回旋线上的点
if ss1 != 3:
x1 = xjd - t1 * math.cos(a0)
y1 = yjd - t1 * math.sin(a0)
ss1 = 3
l = lxzh - kzh
xp = l - l ** 5 / (40 * (r * ls1) ** 2) + l ** 9 / (3456 * (r * ls1) ** 4)
yp = l ** 3 / (6 * r * ls1) - l ** 7 / (336 * (r * ls1) ** 3) + l ** 11 / (42240 * (r * ls1) ** 5)
x = x1 + xp * math.cos(a0) - zy * yp * math.sin(a0)
y = y1 + xp * math.sin(a0) + zy * yp * math.cos(a0)
b = a0 + zy * l * l / (2 * r * ls1)
b %= math.pi * 2
sheet_coord[row2, 2].value = x
sheet_coord[row2, 3].value = y
sheet_coord[row2, 4].value = b
elif lxzh >= khy and lxzh <= kyh: #圆曲线上的点
if ss1 != 4:
x1 = xjd - t1 * math.cos(a0)
y1 = yjd - t1 * math.sin(a0)
q1 = ls1 / 2 - ls1 ** 3 / (240 * r ** 2) + ls1 ** 5 / (34560 * r ** 4)
p1 = ls1 ** 2 / (24 * r) - ls1 ** 4 / (2688 * r ** 3) + ls1 ** 6 / (506880 * r ** 5)
ss1 = 4
l = lxzh - kzh
tp = (2 * l - ls1) / 2 / r
xp = r * math.sin(tp) + q1
yp = r * (1 - math.cos(tp)) + p1
x = x1 + xp * math.cos(a0) - zy * yp * math.sin(a0)
y = y1 + xp * math.sin(a0) + zy * yp * math.cos(a0)
b = a0 + zy * tp
b %= math.pi * 2
sheet_coord[row2, 2].value = x
sheet_coord[row2, 3].value = y
sheet_coord[row2, 4].value = b
elif lxzh > kyh and lxzh < khz: #第二回旋线上的点
if ss1 != 5:
x1 = xjd + t2 * math.cos(a1)
y1 = yjd + t2 * math.sin(a1)
ss1 = 5
l = khz - lxzh
xp = l - l ** 5 / (40 * (r * ls2) ** 2) + l ** 9 / (3456 * (r * ls2) ** 4)
yp = l ** 3 / (6 * r * ls2) - l ** 7 / (336 * (r * ls2) ** 3) + l ** 11 / (42240 * (r * ls2) ** 5)
x = x1 - xp * math.cos(a1) - zy * yp * math.sin(a1)
y = y1 - xp * math.sin(a1) + zy * yp * math.cos(a1)
b = a1 - zy * l * l / (2 * ls2 * r)
b %= math.pi * 2
sheet_coord[row2, 2].value = x
sheet_coord[row2, 3].value = y
sheet_coord[row2, 4].value = b
else:
ss1 = 10
row1 += 1
row2 -= 1
row2 += 1
sheet_coord.fit_content()

View File

@ -0,0 +1,67 @@
# 计算中桩设计高程
import color
#纵断面表
sheet_longitudinal = sheet("longitudinal_profile")
#坐标计算表
sheet_coord = sheet("coord_calc")
def sgn(x):
if x > 0:
return 1
if x < 0:
return -1
return 0
def calc_elevation():
with sheet_longitudinal:
row = 0
while not cell(row, 0).empty():
row += 1
if row < 2:
print(f"{color.red}错误{color.nc}:纵断面表缺少高程设计数据")
return
max_row = row
min_chainage = cell(0, 0).value
max_chainage = cell(max_row - 1, 0).value
row = 2
d = 0
while not sheet_coord[row, 1].empty():
chainage = sheet_coord[row, 1].value
if min_chainage <= chainage <= max_chainage:
row2 = 1
while not cell(row2, 0).empty():
if chainage < cell(row2, 0).value:
break
row2 = row2 + 1
start_chainage = cell(row2 - 1, 0).value #起始里程
end_chainage = cell(row2, 0).value #终止里程
if cell(row2 - 1, 2).empty():
start_radius = None
else:
start_radius = cell(row2 - 1, 2).value * sgn(cell(row2, 4).value - cell(row2 - 1, 4).value) #起始半径
end_radius = cell(row2, 2).value * sgn(cell(row2 + 1, 4).value - cell(row2, 4).value) #终止半径
start_t = cell(row2 - 1, 6).value or 0 #起始切线长
end_t = cell(row2, 6).value #终止切线长
start_h = cell(row2 - 1, 1).value #起始高程
ratio = cell(row2, 4).value / 100 #坡度
elevation = start_h + (chainage - start_chainage) * ratio
if chainage < start_chainage + start_t:
d = (start_chainage + start_t - chainage) ** 2 / 2 / start_radius
elif chainage > end_chainage - end_t:
if end_t < 0.000001:
d = 0
else:
d = (chainage - end_chainage + end_t) ** 2 / 2 / end_radius
elevation += d
sheet_coord[row, 5].value = elevation
row += 1
calc_elevation()

View File

@ -0,0 +1,71 @@
#计算轨道数据
import numpy as np
import color
#轨道数据表
sheet_measure = sheet("measure_data")
def linear_regression(x, y):
n = x.size
sx = x.sum()
sx2 = (x ** 2).sum()
sy = y.sum()
sxy = (x * y).sum()
delta = n * sx2 - sx ** 2
a = (n * sxy - sx * sy) / delta
b = (sx2 * sy - sx * sxy) / delta
return a, b
def calc_measure_data():
diff_param = param("height_diff_param").strip()
if not diff_param:
print(f"{color.yellow}警告:{color.nc}缺少高差参数")
diff_param = 0
else:
diff_param = float(diff_param)
with sheet_measure:
row = 0
left = []
right = []
while not cell(row, 1).empty():
left.append(cell(row, 1).value)
right.append(cell(row, 3).value)
row += 1
left = np.array(left, dtype = np.float64)
right = np.array(right, dtype = np.float64)
n = left.size
index = np.array(range(left.size), dtype = np.float64)
h_diff = (left - right) * 1000 + diff_param
# left_a, left_b = linear_regression(index, left)
# right_a, right_b = linear_regression(index, right)
left_a = (left[-1] - left[0]) / (n - 1)
left_b = left[0]
right_a = (right[-1] - right[0]) / (n - 1)
right_b = right[0]
left_diff = (left - left_a * index - left_b) * 1000
right_diff = (right - right_a * index - right_b) * 1000
left_diff2 = left_diff[1: -1] - (left_diff[2:] + left_diff[:-2]) / 2
right_diff2 = right_diff[1: -1] - (right_diff[2:] + right_diff[:-2]) / 2
for row, data in enumerate(h_diff):
cell(row, 4).value = data
for row, data in enumerate(left_diff):
cell(row, 5).value = data
for row, data in enumerate(right_diff):
cell(row, 6).value = data
for row, data in enumerate(left_diff2):
cell(row + 1 , 7).value = data
for row, data in enumerate(right_diff2):
cell(row + 1 , 8).value = data
graph("road_graph").plot(index, left, "左轨", index, right, "右轨")
graph("road_relative_graph").plot(index, left_diff, "左轨", index, right_diff, "右轨")
graph("road_horizon_graph").plot(index, h_diff, "轨道水平")
graph("road_concave_graph").plot(index[1:-1], left_diff2, "左轨", index[1:-1], right_diff2, "右轨")
calc_measure_data()
sheet_measure.fit_content()

View File

@ -0,0 +1,31 @@
#计算边桩坐标
import math
sheet_coord = sheet("coord_calc")
with sheet_coord:
row = 2
while not cell(row, 2).empty():
x = cell(row, 2).value
y = cell(row, 3).value
a = cell(row, 4).value
if not cell(row, 6).empty():
d = cell(row, 6).value
if cell(row, 7).empty():
ap = math.pi / 2
else:
ap = cell(row, 7).value
cell(row, 8).value = x - math.cos(a + ap) * d
cell(row, 9).value = y - math.sin(a + ap) * d
if not cell(row, 10).empty():
d = cell(row, 10).value
if cell(row, 11).empty():
ap = math.pi / 2
else:
ap = cell(row, 11).value
cell(row, 12).value = x + math.cos(a + ap) * d
cell(row, 13).value = y + math.sin(a + ap) * d
row += 1
sheet_coord.fit_content()

View File

@ -0,0 +1,20 @@
#计算坡道参数
sheet_longitudinal = sheet("longitudinal_profile")
with sheet_longitudinal:
row = 1
while not cell(row, 1).empty():
cell(row, 3).value = cell(row, 0).value - cell(row - 1, 0).value #坡长
cell(row, 4).value = (cell(row, 1).value - cell(row - 1, 1).value) / cell(row, 3).value * 100 #坡度
row = row + 1
row = 1
while not cell(row + 1, 1).empty():
diff = cell(row + 1, 4).value - cell(row, 4).value #坡度差
cell(row, 5).value = cell(row, 2).value * abs(diff / 100) #曲线长
cell(row, 6).value = cell(row, 5).value / 2 #切线长
cell(row, 7).value = cell(row, 6).value * diff / 400 #外距
row += 1
sheet_longitudinal.fit_content()

View File

@ -0,0 +1,8 @@
# 清空计算的坡道参数
sheet_longitudinal = sheet("longitudinal_profile")
with sheet_longitudinal:
for row in range(sheet_longitudinal.row):
for col in range(3, sheet_longitudinal.col):
cell(row, col).value = None

View File

@ -0,0 +1,142 @@
# 生成直曲表
import math
import numpy as np
#计算方位角
def azimuth(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
if abs(dy) < 1e-6:
if dx > 0:
return 0
else:
return math.pi
theta = math.atan(dy / dx)
if dx < 0:
theta += math.pi
#print(dx, dy, theta / math.pi * 180)
return theta
#计算两点距离
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
return math.sqrt(dx**2 + dy**2)
#平面资料表
sheet_surface = sheet("surface_statistics")
#直曲表
sheet_align = sheet("alignment_table")
#创建3*5矩阵 数据类型为64位浮点数 初始值为0
#用于存储中间数据
pm = np.zeros((3, 5), dtype = np.float64)
with sheet_surface:
jd = cell(0, 1).value
pm[0, 0] = cell(0, 2).value
pm[0, 1] = cell(0, 3).value
stt = cell(0, 7).value
with sheet_align:
sheet_align.clear_content()
sheet_align.initial()
cell(3, 0).value = jd
cell(3, 1).value = pm[0, 0]
cell(3, 2).value = pm[0, 1]
cell(3, 3).value = stt
for row in range(0, sheet_surface.row):
with sheet_surface:
if cell(row + 2, 2).empty():
break
pm[0,0] = cell(row, 2).value
pm[0,1] = cell(row, 3).value
jd = cell(row + 1, 1).value
for col in range(2, 7):
pm[1, col - 2] = cell(row + 1, col).value
pm[2, col - 2] = cell(row + 2, col).value
theta1 = azimuth(pm[0, 0], pm[0, 1], pm[1, 0], pm[1, 1])
theta2 = azimuth(pm[1, 0], pm[1, 1], pm[2, 0], pm[2, 1])
d = distance(pm[0, 0], pm[0, 1], pm[1, 0], pm[1, 1])
dtheta = theta2 - theta1
sign = 1
if dtheta < 0:
sign = -1
dtheta = abs(dtheta)
# 计算曲线要素
q1 = pm[1, 3] / 2 - pm[1, 3] ** 3 / (240 * pm[1, 2] ** 2) + pm[1, 3] ** 5 / (34560 * pm[1, 2] ** 4)
q2 = pm[1, 4] / 2 - pm[1, 4] ** 3 / (240 * pm[1, 2] ** 2) + pm[1, 4] ** 5 / (34560 * pm[1, 2] ** 4)
p1 = pm[1, 3] ** 2 / (24 * pm[1, 2]) - pm[1, 3] ** 4 / (2688 * pm[1, 2] ** 3) + pm[1, 3] ** 6 / (506880 * pm[1, 2] ** 5)
p2 = pm[1, 4] ** 2 / (24 * pm[1, 2]) - pm[1, 4] ** 4 / (2688 * pm[1, 2] ** 3) + pm[1, 4] ** 6 / (506880 * pm[1, 2] ** 5)
t1 = q1 + (pm[1, 2] + p2 - (pm[1, 2] + p1) * math.cos(dtheta)) / math.sin(dtheta)
t2 = q2 + (pm[1, 2] + p1 - (pm[1, 2] + p2) * math.cos(dtheta)) / math.sin(dtheta)
ly = pm[1, 2] * dtheta - (pm[1, 3] + pm[1, 4]) / 2
ll = ly + pm[1, 3] + pm[1, 4]
eh = (pm[1, 2] + p1 / 2 + p2 / 2) / math.cos(dtheta / 2) - pm[1, 2]
# 计算主点桩号
# 填写直曲表
cell(row + 4, 0).value = jd
cell(row + 4, 1).value = pm[1, 0]
cell(row + 4, 2).value = pm[1, 1]
if sign == 1:
cell(row + 4, 5).value = dtheta
else:
cell(row + 4, 4).value = dtheta
cell(row + 4, 6).value = pm[1, 2] #半径
cell(row + 4, 7).value = pm[1, 3] #第一缓和曲线
cell(row + 4, 8).value = pm[1, 4] #第二缓和曲线
cell(row + 4, 9).value = t1 #第一切线长
cell(row + 4, 10).value = t2 #第二切线长
cell(row + 4, 11).value = ll #曲线长
cell(row + 4, 12).value = eh #外距
cell(row + 4, 19).value = d #交点间距
t1_before = cell(row + 3, 9).value or 0
t2_before = cell(row + 3, 10).value or 0
ll_before = cell(row + 3, 11).value or 0
cell(row + 4, 18).value = cell(row + 4, 19).value - t2_before - cell(row + 4, 9).value #夹直线长度
cell(row + 4, 20).value = theta1 #计算方位角
cell(row + 4, 21).value = sign #路线左右偏代码
cell(row + 4, 13).value = cell(row + 3, 3).value - t1_before + ll_before + cell(row + 4, 18).value #ZH点桩号
cell(row + 4, 3).value = cell(row + 4, 13).value + cell(row + 4, 9).value #交点桩号
cell(row + 4, 17).value = cell(row + 4, 13).value + cell(row + 4, 11).value #HZ点桩号
cell(row + 4, 14).value = cell(row + 4, 13).value + cell(row + 4, 7).value #HY点桩号
cell(row + 4, 16).value = cell(row + 4, 17).value - cell(row + 4, 8).value #YH点桩号
cell(row + 4, 15).value = cell(row + 4, 14).value + ly / 2 #QZ点桩号
with sheet_surface:
pm[0, 0] = cell(row, 2).value
pm[0, 1] = cell(row, 3).value
jd = cell(row + 1, 1).value
for col in range(5):
pm[1, col] = cell(row + 1, col + 2).value
#计算方位角
theta1 = azimuth(pm[0, 0], pm[0, 1], pm[1, 0], pm[1, 1])
d = distance(pm[0, 0], pm[0, 1], pm[1, 0], pm[1, 1])
#填写直曲表
cell(row + 4, 0).value = jd
cell(row + 4, 1).value = pm[1, 0]
cell(row + 4, 2).value = pm[1, 1]
cell(row + 4, 19).value = d
cell(row + 4, 18).value = cell(row + 4, 19).value - cell(row + 3, 10).value - (cell(row + 4, 9).value or 0)
cell(row + 4, 3).value = cell(row + 3, 17).value + cell(row + 4, 18).value
cell(row + 4, 20).value = theta1
sheet_align.fit_content()
script_file = os.path.join(config.script_path,"create_linear_feature.py")
window.load_script(script_file)

View File

@ -0,0 +1,167 @@
# 生成线元要素表
import copy
import numpy
#直曲表
sheet_align = sheet("alignment_table")
#线元数据表
sheet_linear = sheet("linear_feature")
#生成线元数据
sheet_linear.clear_content()
row1 = 3
row2 = 0
epsilon = 0.0005
sheet_linear[row2, 0].value = sheet_align[row1, 3].value
sheet_linear[row2, 6].value = sheet_align[row1 + 1, 20].value
sheet_linear[row2, 7].value = sheet_align[row1, 1].value
sheet_linear[row2, 8].value = sheet_align[row1, 2].value
row1 += 1
row2 += 1
while not sheet_align[row1, 6].empty():
sheet_linear[row2, 0].value = sheet_align[row1, 13].value
if abs(sheet_linear[row2, 0].value - sheet_linear[row2 - 1, 0].value) > epsilon:
sheet_linear[row2, 2].value = 0
sheet_linear[row2, 3].value = 0
row2 += 1
sheet_linear[row2, 0].value = sheet_align[row1, 14].value
if abs(sheet_linear[row2, 0].value - sheet_linear[row2 - 1, 0].value) > epsilon:
sheet_linear[row2, 2].value = 0
sheet_linear[row2, 3].value = sheet_align[row1, 6].value * sheet_align[row1, 21].value
row2 += 1
sheet_linear[row2, 0].value = sheet_align[row1, 16].value
if abs(sheet_linear[row2, 0].value - sheet_linear[row2 - 1, 0].value) > epsilon:
sheet_linear[row2, 2].value = sheet_align[row1, 6].value * sheet_align[row1, 21].value
sheet_linear[row2, 3].value = sheet_linear[row2, 2].value
row2 += 1
sheet_linear[row2, 0].value = sheet_align[row1, 17].value
if abs(sheet_linear[row2, 0].value - sheet_linear[row2 - 1, 0].value) > epsilon:
sheet_linear[row2, 2].value = sheet_align[row1, 6].value * sheet_align[row1, 21].value
sheet_linear[row2, 3].value = 0
row2 += 1
row1 += 1
sheet_linear[row2, 0].value = sheet_align[row1, 3].value
sheet_linear[row2, 2].value = 0
sheet_linear[row2, 3].value = 0
#线元要素计算
def calc_linear_type(x, y):
#0-圆曲线1-直线2-缓和曲线
epsilon = 0.00001
if abs(x - y) >= epsilon:
return 2
if abs(x) < epsilon:
return 1
return 0
# 定义桩点
class stake_point:
def __init__(
self, chainage = 0, x = 0, y = 0, z = 0,
alpha = 0, beta = 0, rho = 0
):
self.chainage = chainage #里程
self.x = x #x坐标
self.y = y #y坐标
self.z = z #高程
self.alpha = alpha #切线方位角
self.beta = beta #
self.rho = rho #曲率
# 缓和曲线
# 起点里程、曲率、坐标、切线方位角以及终点里程、曲率确定一条缓和曲线
class curve:
def __init__(self, start = None, end = None):
if start is None:
self.start = stake_point()
else:
self.start = copy.copy(start) #起始桩点
if end is None:
self.end = stake_point()
else:
self.end = copy.copy(end) #终止桩点
# 求缓和曲线曲率变化参数
def get_ratio(self):
return math.sqrt((self.end.chainage - self.start.chainage) / abs(self.end.rho - self.start.rho))
# 求里程为chainage的一点处的曲率、坐标及切线方位角采用Gauss-Legendre公式计算
def get_point(self, chainage):
l = chainage - self.start.chainage
ll = self.end.chainage - self.start.chainage
if abs(ll) < 0.001:
return copy.copy(self.start)
sp = stake_point(chainage = chainage)
# 求点的切线方位角及曲率
sp.rho = self.start.rho + (self.end.rho - self.start.rho) * l / ll # 曲率线性变化
sp.alpha = self.start.alpha + (self.start.rho + sp.rho) * l / 2
sp.alpha %= 2 * math.pi
f = np.zeros((2, 4), dtype = np.float64)
f[0, 0] = 0.0694318442
f[0, 1] = 0.3300094782
f[0, 2] = 1 - f[0, 1]
f[0, 3] = 1 - f[0, 0]
f[1, 0] = 0.1739274226
f[1, 1] = 0.3260725774
f[1, 2] = f[1, 1]
f[1, 3] = f[1, 0]
s1 = 0
s2 = 0
a0 = self.start.alpha
c0 = self.start.rho
c1 = self.end.rho
h = (c1 - c0) * l * l / (2 * ll)
for i in range(4):
s1 = s1 + f[1, i] * math.cos(a0 + c0 * l * f[0, i] + h * f[0, i] * f[0, i])
s2 = s2 + f[1, i] * math.sin(a0 + c0 * l * f[0, i] + h * f[0, i] * f[0, i])
sp.x = self.start.x + l * s1
sp.y = self.start.y + l * s2
return sp
row = 1
with sheet_linear:
while not cell(row, 0).empty():
cell(row, 1).value = cell(row, 0).value - cell(row - 1, 0).value
if cell(row, 2).value != 0:
cell(row, 4).value = 1 / cell(row, 2).value
if cell(row, 3).value != 0:
cell(row, 5).value = 1 / cell(row, 3).value
cell(row, 9).value = calc_linear_type(cell(row, 2).value, cell(row, 3).value)
cv = curve()
cv.start.chainage = cell(row - 1, 0).value
cv.start.rho = (cell(row, 4).value or 0)
cv.start.alpha = cell(row - 1, 6).value
cv.start.x = cell(row - 1, 7).value
cv.start.y = cell(row - 1, 8).value
cv.end.chainage = cell(row, 0).value
cv.end.rho = (cell(row, 5).value or 0)
if cell(row, 9).value == 2:
cell(row, 10).value = cv.get_ratio()
sp = cv.get_point(cv.end.chainage)
cell(row, 6).value = sp.alpha
cell(row, 7).value = sp.x
cell(row, 8).value = sp.y
row += 1
sheet_linear.fit_content()

View File

@ -0,0 +1,59 @@
# 生成桩号
import re
import color
line_gap = int(param("line_gap"))
curve_gap = int(param("curve_gap"))
with sheet("alignment_table"):
if cell(3, 2).empty():
print(f"{color.red}错误{color.nc}:缺少数据")
start = cell(3, 3).value
j = 4
while not cell(j, 3).empty():
j += 1
end = cell(j - 1, 3).value
align_table = [0] * 5
list_label = ["ZH", "HY", "QZ", "YH", "HZ", ""]
list_stake = []
pos = start - (start % line_gap)
with sheet("alignment_table"):
row = 5
while not cell(row, 13).empty():
for k in range(13, 18):
align_table[k - 13] = cell(row, k).value
while pos + line_gap < align_table[0]:
pos += line_gap
list_stake.append([row, pos, 5])
pos = align_table[0] - (align_table[0] % curve_gap)
while pos + curve_gap < align_table[4]:
pos += curve_gap
list_stake.append([row, pos, 5])
pos = align_table[4] - (align_table[4] % line_gap)
for k in range(5):
list_stake.append([row, align_table[k], k])
row += 1
while pos + line_gap < end:
pos += line_gap
list_stake.append([row, pos, 5])
list_stake.append([row, end, 5])
with sheet("coord_calc") as sheet_coord:
for row in range(2, sheet_coord.row):
for col in range(6):
cell(row, col).clear()
list_stake.sort()
for row, stake in enumerate(list_stake):
row += 2
cell(row, 0).text = list_label[stake[2]]
cell(row, 1).value = stake[1]
sheet_coord.fit_content()

View File

@ -0,0 +1,15 @@
import formatter
#规定表格中的数据格式
def table_formatter(row, col):
if row < 3: # 直曲表前3行为表头不做格式转化
return None
if col in [6, 7, 8]:
return formatter.integer()
elif col in [1, 2, 9, 10, 11, 12, 18, 19]:
return formatter.decimal()
elif col in [3, 13, 14, 15, 16, 17]:
return formatter.kdecimal()
elif col in [4, 5, 20]:
return formatter.angle()
return None

View File

@ -0,0 +1,15 @@
import formatter
#规定表格中的数据格式
def table_formatter(row, col):
if row < 2:
return None
if col in [2, 3, 5, 6, 8, 9, 10, 12, 13, 14, 15, 16]:
return formatter.decimal()
elif col in [1]:
return formatter.kdecimal()
elif col in [4, 7, 11]:
return formatter.angle()
elif col in []:
return formatter.integer()
return None

View File

@ -0,0 +1,31 @@
import formatter
class linear_type:
dtype = int
table = ["圆曲线", "直线", "缓和曲线"]
def __init__(self):
pass
def str2val(self, s):
return linear_type.table.index(s)
def val2str(self, v):
return linear_type.table[v]
#规定表格中的数据格式
def table_formatter(row, col):
if col in [2, 3]:
return formatter.integer()
elif col in [1, 7, 8]:
return formatter.decimal()
elif col in [0]:
return formatter.kdecimal()
elif col in [6]:
return formatter.angle()
elif col in [4, 5]:
return formatter.decimal(fmt = "%.8f")
elif col in [10]:
return formatter.decimal(fmt = "%.2f")
elif col in [9]:
return linear_type()
return None

View File

@ -0,0 +1,13 @@
import formatter
#规定表格中的数据格式
def table_formatter(row, col):
if col in [2]:
return formatter.integer()
elif col in [1, 3, 4, 5, 6, 7]:
return formatter.decimal()
elif col in [0]:
return formatter.kdecimal()
elif col in []:
return formatter.angle()
return None

View File

@ -0,0 +1,11 @@
import formatter
#规定表格中的数据格式
def table_formatter(row, col):
if col in []:
return formatter.decimal() # 小数 默认精确到后三位
elif col in [4, 5, 6, 7, 8]:
return formatter.decimal("%.2f") # 小数 精确到后两位
elif col in [1, 3]:
return formatter.decimal("%.5f") # 小数 精确到后五位
return None

View File

@ -0,0 +1,13 @@
import formatter
#规定表格中的数据格式
def table_formatter(row, col):
if col in [0, 4, 5, 6, 7]:
return formatter.integer() # 整数
elif col in [2, 3]:
return formatter.decimal() # 小数
elif col in []:
return formatter.kdecimal() # k#+###.###格式的小数
elif col in []:
return formatter.angle() # 角度
return None

View File

@ -0,0 +1,123 @@
#导入测量的轨道数据
import re
from PyQt6.QtWidgets import QFileDialog
import color
def get_filename(filename = None):
if not filename:
if hasattr(get_filename, "filename"):
path_hint = os.path.dirname(get_filename.filename)
else:
path_hint = ""
filename, _ = QFileDialog.getOpenFileName(window, "从DAT文件导入数据", path_hint, "DAT数据文件(*.dat *.DAT)")
if not filename:
return
if len(filename) < 4 or filename[-4:].lower() != ".dat":
filename += ".dat"
filename = filename.replace("\\", "\\\\")
filename = filename.replace("\"", "\\\"")
return filename
def load_measure_data(file_name):
with open(file_name, "r") as fobj:
raw = fobj.readlines()
flag = False
r1 = []
for line in raw:
line = line.strip()
match = re.match(r"For\s+M5\|Adr\s+", line)
if match is None:
raise RuntimeError("Unrecognized data: {line}")
m = match.group(0)
pos = len(m)
line = line[pos:]
match = re.match(r"\d+\|([0-9A-Z]+)\s+", line)
if match is None:
raise RuntimeError("Unrecognized data: {line}")
m = match.group(0)
pos = len(m)
line = line[pos:]
match = re.match(r"[0-9a-zA-Z\.\-]+", line)
if match is None:
raise RuntimeError("Unrecognized data: {line}")
label = match.group(0)
pos = len(label)
line = line[pos:]
match = re.match(r"KZ\d*", label)
if not match is None:
label = match.group(0)
pos = len(label)
line = line[pos:]
#print("控制点", label, line)
continue
if label == "Intermediate":
flag = True
continue
if label == "End":
flag = False
continue
if not flag:
continue
match = re.match(r"[A-Z]*\d+[\.\-]\d+", label)
if match is None:
continue
data = line.split("|")
data = data[3]
match = re.match(r"[0-9a-zA-Z]+\s+(\d+\.\d+)", data)
if match is None:
raise RuntimeError(f"找不到所需数据: {data}")
data = match.group(1)
#print("轨道点", label, data)
r1.append([label, data])
r2 = []
for label, data in r1:
match = re.match(r"([A-Z]*)(\d+)[\.\-](\d+)", label)
l = match.group(1)
d1 = int(match.group(2))
d2 = int(match.group(3))
d3 = float(data)
r2.append([l, d1, d2, d3])
r2.sort(key = lambda x: (x[1], x[2]))
# for line in r2:
# print(f"{line[0]}{line[1]}.{line[2]} {line[3]}")
return r2
filename = get_filename()
if filename:
#轨道数据表
sheet_measure = sheet("measure_data")
data = load_measure_data(filename)
sheet_measure.clear_content()
with sheet_measure:
for line in data:
label = "%s%d.%d" % tuple(line[:3])
row = line[1] - 1
flag = line[2] > 1
if line[2] > 2:
print(f"{color.yellow}警告{color.nc}:编号{label}第2个数字大于2当作右轨处理")
data = line[3]
if flag:
cell(row, 2).value = label
cell(row, 3).value = data
else:
cell(row, 0).value = label
cell(row, 1).value = data
sheet_measure.fit_content()

164
app/sheet.py Normal file
View File

@ -0,0 +1,164 @@
window = None
class project:
list_id = []
def __init__(self, id):
self.id = id
def __enter__(self):
project.list_id.append(self.id)
return self
def __exit__(self, *args):
project.list_id.pop()
class sheet:
list_widget = []
def __init__(self, sheet_id):
widget = window.widget_display.map_table[sheet_id]
self.id = sheet_id
self.widget = widget
def __enter__(self):
sheet.list_widget.append(self.widget)
return self
def __exit__(self, *args):
sheet.list_widget.pop()
def at(self, *args):
c = cell(*args, widget = self.widget)
return c
def __getitem__(self, index):
if type(index) is tuple:
return self.at(*index)
else:
return self.at(index)
@property
def row(self):
return self.widget.row
@row.setter
def row(self, row):
self.widget.row = row
@property
def col(self):
return self.widget.col
@col.setter
def col(self, col):
self.widget.col = col
def clear(self):
self.widget.clear()
def clear_content(self):
self.widget.clearContents()
def initial(self):
self.widget.initial()
def reset_content(self):
self.widget.reset_content()
def fit_content(self):
self.widget.fit_content()
def load_data(self, *arg, **kwarg):
self.widget.load_data(*arg, **kwarg)
def dump_data(self, *arg, **kwarg):
return self.widget.dump_data(*arg, **kwarg)
def load_str(self, *arg, **kwarg):
self.widget.load_str(*arg, **kwarg)
def dump_str(self, *arg, **kwarg):
return self.widget.dump_str(*arg, **kwarg)
def load_file(self, *arg, **kwarg):
self.widget.load_file(*arg, **kwarg)
def dump_file(self, *arg, **kwarg):
self.widget.dump_file(*arg, **kwarg)
class cell:
def __init__(self, *arg, widget = None):
if len(arg) == 1:
pos = arg[0]
pos = pos.lower()
row = None
col = None
match = re.match(r"([a-z]+)(\d+)", pos)
if not match:
raise RuntimeError(f"unrecognized pos: \"{pos}\"")
row = match.group(2)
cl = match.group(1)
row = int(row) - 1
col = 0
for chr in cl:
col *= 26
col += ord(chr) - ord("a")
elif len(arg) == 2:
row = arg[0]
col = arg[1]
else:
raise RuntimeError("The number of params should not be more than 2")
self.item = None
if widget is None and sheet.list_widget:
widget = sheet.list_widget[-1]
if widget:
self.item = widget.at(row, col)
@property
def text(self):
return self.item.text
@text.setter
def text(self, s):
self.item.text = s
@property
def value(self):
return self.item.value
@value.setter
def value(self, v):
self.item.value = v
def empty(self):
return self.item.empty()
def clear(self):
self.item.value = None
class graph:
list_widget = []
def __init__(self, graph_id):
widget = window.widget_display.map_graph[graph_id]
self.id = graph_id
self.widget = widget
def __enter__(self):
graph.list_widget.append(self.widget)
return self
def __exit__(self, *args):
graph.list_widget.pop()
def plot(self, *arg, **kwarg):
self.widget.plot(*arg, **kwarg)
def plot(*arg, **kwarg):
widget = graph.list_widget[-1]
widget.plot(*arg, **kwarg)
def param(id):
map_input = window.widget_interact.widget_grid.map_input
result = map_input.get(id)
return result

110
app/text_term.py Normal file
View File

@ -0,0 +1,110 @@
import re
from PyQt6.QtWidgets import (
QTextEdit,
)
from PyQt6.QtGui import (
QColor,
QTextCharFormat, QTextCursor
)
import color
class text_term(QTextEdit):
def __init__(self, window, limit = None, *args):
super().__init__(*args)
self.window = window
self.setTabStopDistance(40)
self.setReadOnly(True)
self.limit = limit
def erase_text(self, pos_begin, pos_end):
cursor = self.textCursor()
cursor.setPosition(pos_begin)
cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, pos_end - pos_begin)
cursor.removeSelectedText()
def write(self, s):
cursor = self.textCursor()
cursor.setPosition(cursor.currentFrame().lastPosition())
ptn_color = r"\x1b\[([0-9]+(?:;[0-9]+)*)m"
g = re.finditer(ptn_color, s)
pos1 = 0
fmt = QTextCharFormat()
fore_color = QColor(200, 200, 200)
back_color = QColor(0, 0, 0)
for i in g:
cursor.insertText(s[pos1:i.span()[0]], fmt)
text_color = i.group()
match = re.fullmatch(ptn_color, text_color)
match = match.group(1)
match = [int(i) for i in match.split(";")]
# print(text_color, match, file = self.window.stdout_save)
match.reverse()
flag_normal = False
flag_bold = False
while match:
m = match.pop()
if m <= 1:
fore_color = QColor(200, 200, 200)
back_color = QColor(0, 0, 0)
if m == 1:
flag_bold = True
else:
flag_normal = True
elif (
30 <= m <= 37 or 40 <= m <= 47
or 90 <= m <= 97 or 100 <= m <= 107
):
flag = False
if m >= 40:
m -= 10
flag = True
if m >= 90:
m -= 90 - 38
m -= 30
if flag_normal:
fore_color = color.color_normal_table[m]
elif flag_bold:
fore_color = color.color_bold_table[m]
elif not flag:
fore_color = color.color4_table[m]
else:
back_color = color.color4_table[m]
elif m == 38 or m == 48:
if m == 38:
m = match.pop()
m = match.pop()
fore_color = color.color8_table[m]
elif m == 48:
m = match.pop()
m = match.pop()
back_color = color.color8_table[m]
elif m in (39, 49):
if m == 39:
fore_color = QColor(200, 200, 200)
else:
back_color = QColor(0, 0, 0)
else:
# cursor.insertText(text_color)
break
fmt.setForeground(fore_color)
fmt.setBackground(back_color)
self.setTextColor(fore_color)
pos1 = i.span()[1]
cursor.insertText(s[pos1:], fmt)
if not (self.limit is None):
surplus = self.document().characterCount() - self.limit
if surplus > 0:
self.erase_text(0, surplus)
self.ensureCursorVisible()
self.setTextCursor(cursor)
def flush(self):
pass

7
app/util.py Normal file
View File

@ -0,0 +1,7 @@
import importlib.util
def load_module(path):
spec = importlib.util.spec_from_file_location("dynamic_module", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module

763
app/widget_display.py Normal file
View File

@ -0,0 +1,763 @@
# -*- encoding: utf-8 -*-
import os
import copy
import math
from functools import partial
import numpy as np
from PyQt6.QtCore import (
Qt, QSize, QPoint, QPointF,
QMimeData,
)
from PyQt6.QtWidgets import (
QWidget, QTabWidget, QTableWidget, QTableWidgetItem,
QVBoxLayout, QMenu, QSizePolicy, QHeaderView,
QApplication, QStyledItemDelegate,
)
from PyQt6.QtGui import (
QIcon, QPainter, QColor, QPen, QBrush, QPalette,
QFont, QPainterPath, QImage, QAction,
QValidator, QIntValidator, QDoubleValidator,
)
from PyQt6.QtCharts import QChartView, QChart, QLineSeries, QScatterSeries, QValueAxis
from config import config, data_path, script_path
import util
font = QFont("consolas", 14)
font.setFamilies(["consolas", "黑体"])
def str2data(s, delimiter = None, line_end = "\n"):
if delimiter is None:
delimiter = [",", "\t", ";"]
data = s.split(line_end)
data = [line for line in data]
for i, line in enumerate(data):
ss = ""
l = []
for c in line:
if c in delimiter:
l.append(ss)
ss = ""
else:
ss += c
l.append(ss)
data[i] = l
return data
def data2str(data, delimiter = ",", line_end = "\n"):
return line_end.join([delimiter.join(line) for line in data])
class optional_int_validator(QIntValidator):
def validate(self, text, pos):
if not text:
return (QValidator.State.Acceptable, text, pos)
return super().validate(text, pos)
class optional_doublle_validator(QDoubleValidator):
def validate(self, text, pos):
if not text:
return (QValidator.State.Acceptable, text, pos)
return super().validate(text, pos)
def get_step(span):
base = math.log10(span)
base += 0.1
b = int(base)
c = int((b - base) * 3)
b = 10.0 ** b
if c == 0:
step = b / 10
elif c == 1:
step = b / 5
else:
step = b / 2
return step
def get_tick(a, b):
step = get_step(b - a)
list_tick = []
tick = a - a % step
if tick < a:
tick += step
while tick <= b:
list_tick.append(tick)
tick += step
return list_tick
class header_view(QHeaderView):
def __init__(self, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu)
def contextMenuEvent(self, event):
# Get the clicked header position
pos = event.pos() # Position relative to the header
# Get the logical index of the clicked section
orient = self.orientation()
index = self.logicalIndexAt(pos)
parent = self.parent()
menu_table = QMenu()
act_insert_above = QAction("在上方插入行", menu_table, triggered = partial(parent.insert_row, index))
act_insert_below = QAction("在下方插入行", menu_table, triggered = partial(parent.insert_row, index + 1))
act_remove_row = QAction("删除行", menu_table, triggered = partial(parent.remove_row, index))
act_insert_left = QAction("在左侧插入列", menu_table, triggered = partial(parent.insert_col, index))
act_insert_right = QAction("在右侧插入列", menu_table, triggered = partial(parent.insert_col, index + 1))
act_remove_col = QAction("删除列", menu_table, triggered = partial(parent.remove_col, index))
if orient == Qt.Orientation.Horizontal:
menu_table.addActions([
act_insert_left,
act_insert_right,
act_remove_col,
])
else:
menu_table.addActions([
act_insert_above,
act_insert_below,
act_remove_row,
])
menu_table.exec(event.globalPos())
class table_item(QTableWidgetItem):
def __init__(self, text = "", value = None, formatter = None, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.data = None
self.formatter = formatter
if text:
self.text = text
elif value:
self.value = value
@property
def text(self):
return super().text()
@text.setter
def text(self, s):
if self.formatter:
self.data = self.formatter.str2val(s)
if self.data is None:
s = ""
else:
s = self.formatter.val2str(self.data)
super().setText(s)
@property
def value(self):
if not (self.data is None):
return self.data
if self.formatter:
value = self.formatter.str2val(super().text())
return value
return super().text()
@value.setter
def value(self, v):
if v is None:
super().setText("")
elif self.formatter:
super().setText(self.formatter.val2str(v))
else:
super().setText(str(v))
self.data = v
def empty(self):
return self.data is None
class item_delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
# Create the editor widget (e.g., QLineEdit)
editor = super().createEditor(parent, option, index)
row = index.row()
col = index.column()
item = self.parent().at(row, col)
if item.formatter is None:
pass
elif item.data is None:
item.setText("")
else:
item.setText(str(item.data))
if item.formatter:
if item.formatter.dtype is int:
editor.setValidator(optional_int_validator())
if item.formatter.dtype is float:
editor.setValidator(optional_doublle_validator())
width = self.parent().horizontalHeader().sectionSize(col)
editor.setFixedWidth(width)
#editor.setMinimumWidth(editor.width())
return editor
def setModelData(self, editor, model, index):
# Process data before saving to the model
text = editor.text()
row = index.row()
col = index.column()
item = self.parent().at(row, col)
if text == "":
item.value = None
else:
if item.formatter:
item.value = item.formatter.dtype(text)
else:
item.text = text
# def displayText(self, value, locale):
# return super().displayText(value, locale)
# def updateEditorGeometry(self, editor, option, index):
# row = index.row()
# col = index.column()
# item = self.parent().at(row, col)
# editor.setWidth(item.width())
# editor.setGeometry(option.rect)
# # editor.setGeometry(option.rect.adjusted(0, 0, 0, 0))
class widget_table(QTableWidget):
def __init__(self, window, id, config_tab, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.app = QApplication.instance()
horizontal_header = header_view(Qt.Orientation.Horizontal, self)
horizontal_header.setMinimumSectionSize(100)
self.setHorizontalHeader(horizontal_header)
self.setVerticalHeader(header_view(Qt.Orientation.Vertical, self))
self.setItemDelegate(item_delegate(self))
self.id = id
self.config_tab = config_tab
self.setFont(font)
self.formatter = None
@property
def row(self):
return super().rowCount()
@row.setter
def row(self, row):
super().setRowCount(row)
@property
def col(self):
return super().columnCount()
@col.setter
def col(self, col):
super().setColumnCount(col)
def at(self, row, col):
if row >= super().rowCount():
self.setRowCount(row + 1)
if col >= super().columnCount():
self.setColumnCount(col + 1)
item = super().item(row, col)
if item is None:
formatter = None
if self.formatter:
formatter = self.formatter(row, col)
item = table_item(formatter = formatter)
super().setItem(row, col, item)
return item
def clear_content(self):
super().clearContents()
def load_data(self, data, row_begin = 0, col_begin = 0, row_end = None, col_end = None, as_value = False):
row = row_begin
while (not row_end or row < row_end) and row < len(data):
line = data[row]
col = col_begin
while (not col_end or col < col_end) and col < len(line):
if as_value:
self.at(row, col).value = line[col]
else:
self.at(row, col).text = line[col]
col += 1
row += 1
def dump_data(self, row_begin = 0, col_begin = 0, row_end = None, col_end = None, as_value = False):
if row_end is None:
row_end = self.row
if col_end is None:
col_end = self.col
if row_end <= row_begin or col_end <= col_begin:
return []
data = []
for row in range(row_begin, row_end):
line = []
for col in range(col_begin, col_end):
if as_value:
line.append(self.at(row, col).value)
else:
line.append(self.at(row, col).text)
data.append(line)
return data
def load_str(self, s, delimiter = None, line_end = "\n", *arg, **kwarg):
data = str2data(s, delimiter, line_end)
self.load_data(data, *arg, **kwarg)
def dump_str(self, delimiter = ",", line_end = "\n", *arg, **kwarg):
data = self.dump_data(*arg, **kwarg)
s = data2str(data, delimiter, line_end)
return s
def load_file(self, filename, *arg, **kwarg):
with open(filename, "r", encoding="utf-8") as fobj:
content = fobj.read()
self.load_str(content, *arg, **kwarg)
def dump_file(self, filename, *arg, **kwarg):
s = self.dump_str(*arg, **kwarg)
with open(filename, "w", encoding="utf-8") as fobj:
fobj.write(s)
def initial(self):
config_tab = self.config_tab
if "row" in config_tab:
self.row = config_tab["row"]
if "col" in config_tab:
self.col = config_tab["col"]
if "column_header" in config_tab:
self.setHorizontalHeaderLabels(config_tab["column_header"])
if "format_script" in config_tab:
filename = os.path.join(script_path, config_tab["format_script"])
module = util.load_module(filename)
self.formatter = module.table_formatter
if "data" in config_tab:
data = config_tab["data"]
self.load_data(data)
if "datafile" in config_tab:
datafile = os.path.join(data_path, config_tab["datafile"])
self.load_file(datafile)
if "span" in config_tab:
for span in config_tab["span"]:
self.setSpan(*span)
self.fit_content()
def reset_content(self):
self.clear_content()
self.initial()
def fit_content(self):
header = self.horizontalHeader()
header.resizeSections(QHeaderView.ResizeMode.ResizeToContents)
def calc_index(self):
list_index = self.selectedIndexes()
if not list_index:
return
min_r = min(list_index, key = lambda x:x.row()).row()
max_r = max(list_index, key = lambda x:x.row()).row() + 1
min_c = min(list_index, key = lambda x:x.column()).column()
max_c = max(list_index, key = lambda x:x.column()).column() + 1
flag_rect = (max_r - min_r) * (max_c - min_c) != len(list_index)
flag_row = flag_rect and min_c == 0 and max_c == self.columnCount()
flag_col = flag_rect and min_r == 0 and max_r == self.rowCount()
return list_index, min_r, max_r, min_c, max_c, flag_rect, flag_row, flag_col
def load_clipboard(self):
clipboard = self.app.clipboard()
mimedata = clipboard.mimeData()
# if mimedata.hasHtml():
# # Handle HTML with explicit UTF-16 decoding
# raw_html = bytes(mimedata.data("text/html"))
# try:
# html = raw_html.decode("utf-16")
# except UnicodeDecodeError:
# html = raw_html.decode("utf-8", errors="replace")
# self.parse_html_table(html)
text = None
if mimedata.hasText():
# Handle plain text with encoding detection
raw_text = bytes(mimedata.data("text/plain"))
# Check for UTF-16 BOM
if raw_text.startswith(b"\xff\xfe") or raw_text.startswith(b"\xfe\xff"):
text = raw_text.decode("utf-16")
else:
try:
text = raw_text.decode("utf-8")
except UnicodeDecodeError:
text = raw_text.decode(locale.getpreferredencoding(), errors="replace")
if text is None:
return
data = str2data(text, delimiter=["\t"])
return data
def clear_select(self):
list_index = self.selectedIndexes()
for index in list_index:
row = index.row()
col = index.column()
self.at(row, col).value = None
def copy_select(self, as_value = False):
data = self.calc_index()
if not data:
return
list_index, min_r, max_r, min_c, max_c, flag_rect, flag_row, flag_col = data
data = [[""] * (max_c - min_c) for row in range(max_r - min_r)]
for index in list_index:
row = index.row()
col = index.column()
item = self.at(row, col)
if as_value:
if item.value is None:
data[row - min_r][col - min_c] = ""
else:
data[row - min_r][col - min_c] = str(self.at(row, col).value)
else:
data[row - min_r][col - min_c] = self.at(row, col).text
text = data2str(data, delimiter = "\t")
clipboard = self.app.clipboard()
mimedata = QMimeData()
mimedata.setText(text)
clipboard.setMimeData(mimedata)
def paste_select(self, as_value = False):
data = self.calc_index()
if not data:
return
list_index, min_r, max_r, min_c, max_c, flag_rect, flag_row, flag_col = data
data = self.load_clipboard()
for index in list_index:
row = index.row()
col = index.column()
r = row - min_r
if r >= len(data):
continue
c = col - min_c
line = data[r]
if c >= len(line):
continue
item = self.at(row, col)
if as_value and item.formatter:
if line[c] == "":
item.value = None
else:
dtype = item.formatter.dtype
try:
try:
item.value = dtype(line[c])
except:
if dtype is int:
item.value = int(float(line[c]))
except:
raise RuntimeError(f"无法将\"{line[c]}\"转化为{dype}类型")
else:
item.text = line[c]
def insert_row(self, row):
self.insertRow(row)
def remove_row(self, row):
self.removeRow(row)
def insert_col(self, col):
self.insertColumn(col)
def remove_col(self, col):
self.removeColumn(col)
def contextMenuEvent(self, event):
data = self.calc_index()
if not data:
return
list_index, min_r, max_r, min_c, max_c, flag_rect, flag_row, flag_col = data
menu_table = QMenu()
act_clear_select = QAction("清空所选区域", menu_table, triggered = self.clear_select)
act_copy_text = QAction("复制所选区域文本", menu_table, triggered = self.copy_select)
act_copy_value = QAction("复制所选区域数据", menu_table, triggered = partial(self.copy_select, True))
act_paste_text = QAction("粘贴文本到所选区域", menu_table, triggered = self.paste_select)
act_paste_value = QAction("粘贴数据到所选区域", menu_table, triggered = partial(self.paste_select, True))
menu_table.addActions([
act_clear_select,
act_copy_text,
act_copy_value,
act_paste_text,
act_paste_value,
])
menu_table.exec(event.globalPos())
def keyPressEvent(self, event):
if (
event.key() == Qt.Key.Key_Delete
):
self.clear_select()
event.accept()
elif (
event.modifiers() == Qt.KeyboardModifier.ControlModifier
and event.key() == Qt.Key.Key_C
):
self.copy_select()
event.accept()
elif (
(event.modifiers() == Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier)
and event.key() == Qt.Key.Key_C
):
self.copy_select(as_value = True)
event.accept()
elif (
event.modifiers() == Qt.KeyboardModifier.ControlModifier
and event.key() == Qt.Key.Key_V
):
self.paste_select()
event.accept()
elif (
(event.modifiers() == Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier)
and event.key() == Qt.Key.Key_V
):
self.paste_select(as_value = True)
event.accept()
else:
super().keyPressEvent(event)
class chart_plot(QChart):
def __init__(self, window, config_tab, *arg):
super().__init__(*arg)
self.window = window
self.setBackgroundBrush(QBrush(QColor(0, 0, 0)))
self.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)
self.legend().setLabelColor(QColor(220, 220, 220))
self.axis_x = QValueAxis()
self.axis_y = QValueAxis()
if "axis_x_title" in config_tab:
title = config_tab["axis_x_title"]
self.axis_x.setTitleText(title)
if "axis_y_title" in config_tab:
title = config_tab["axis_y_title"]
self.axis_y.setTitleText(title)
axis_list = [self.axis_x, self.axis_y]
for axis in axis_list:
axis.setTitleBrush(QBrush(QColor(220, 220, 220)))
axis.setLabelsColor(QColor(220, 220, 220))
axis.setLinePenColor(QColor(200, 200, 200))
axis.setGridLineColor(QColor(100, 100, 100))
self.addAxis(self.axis_x, Qt.AlignmentFlag.AlignBottom)
self.addAxis(self.axis_y, Qt.AlignmentFlag.AlignLeft)
self.list_name = []
def plot(self, *arg, **kwarg):
self.removeAllSeries()
self.list_name.clear()
min_x, max_x = None, None
min_y, max_y = None, None
for i in range((len(arg) + 2) // 3):
x = arg[i * 3]
y = arg[i * 3 + 1]
line = QLineSeries()
if i * 3 + 2 < len(arg):
name = arg[i * 3 + 2]
else:
name = f"数据{i}"
line.setName(name)
self.list_name.append(name)
self.addSeries(line)
scatter = QScatterSeries()
scatter.setColor(line.color())
scatter.setMarkerSize(10)
scatter.setPointLabelsColor(line.color())
#scatter.setPointLabelsVisible()
self.addSeries(scatter)
line.attachAxis(self.axis_x)
line.attachAxis(self.axis_y)
scatter.attachAxis(self.axis_x)
scatter.attachAxis(self.axis_y)
marker = self.legend().markers(scatter)
for i in marker:
i.setVisible(False)
for i in range(len(x)):
xx = x[i]
yy = y[i]
point = QPointF(xx, yy)
line.append(point)
scatter.append(point)
# Calculate and set axis ranges
x1, x2 = min(x), max(x)
y1, y2 = min(y), max(y)
if min_x is None:
min_x, max_x = x1, x2
min_y, max_y = y1, y2
else:
min_x, max_x = min(min_x, x1), max(max_x, x2)
min_y, max_y = min(min_y, y1), max(max_y, y2)
# Add 10% padding to axis ranges
min_x, max_x = min_x - 0.1 * (max_x - min_x), max_x + 0.1 * (max_x - min_x)
min_y, max_y = min_y - 0.1 * (max_y - min_y), max_y + 0.1 * (max_y - min_y)
self.axis_x.setRange(min_x, max_x)
self.axis_y.setRange(min_y, max_y)
step = get_step(max_x - min_x)
self.axis_x.setTickType(QValueAxis.TickType.TicksDynamic);
self.axis_x.setTickAnchor(min_x - min_x % step);
self.axis_x.setTickInterval(step);
step = get_step(max_y - min_y)
self.axis_y.setTickType(QValueAxis.TickType.TicksDynamic);
self.axis_y.setTickAnchor(min_y - min_y % step);
self.axis_y.setTickInterval(step);
def contextMenuEvent(self, event):
if not self.list_name:
return
menu = QMenu()
for index, name in enumerate(self.list_name):
act_toggle_series = QAction(f"显示/隐藏{name}", menu, triggered = partial(self.toggle_series, index))
menu.addAction(act_toggle_series)
menu.exec(event.screenPos())
def toggle_series(self, index):
line = self.series()[index * 2]
scatter = self.series()[index * 2 + 1]
flag = line.isVisible()
flag = not flag
line.setVisible(flag)
scatter.setVisible(flag)
marker = self.legend().markers(scatter)
for i in marker:
i.setVisible(False)
class chart_view(QChartView):
def __init__(self, window, id, config_tab, *arg):
super().__init__(*arg)
self.window = window
self.id = id
self.config_tab = config_tab
self.setRenderHint(QPainter.RenderHint.Antialiasing)
self.chart_plot = chart_plot(window, config_tab)
self.setChart(self.chart_plot)
def plot(self, *arg, **kwarg):
self.chart_plot.plot(*arg, **kwarg)
class tab_widget(QTabWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.currentChanged.connect(self.on_current_changed)
self.setFont(font)
def on_current_changed(self, index):
list_id = self.window.widget_display.list_id
if index < 0 or not list_id:
return
table_id = list_id[index]
self.window.widget_interact.load_interact(table_id)
class widget_display(QWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.setFont(font)
layout_base = QVBoxLayout()
self.setLayout(layout_base)
self.tab_widget = tab_widget(window)
layout_base.addWidget(self.tab_widget)
self.map_table = {}
self.map_graph = {}
self.list_id = []
if not ("display" in config):
return
config_display = config["display"]
for project, config_project in config_display.items():
for config_tab in config_project:
if not "type" in config_tab:
continue
if config_tab["type"] == "table":
id = config_tab["id"]
label = config_tab["label"]
widget = widget_table(self.window, id, config_tab)
self.map_table[id] = widget
elif config_tab["type"] == "graph":
id = config_tab["id"]
label = config_tab["label"]
widget = chart_view(self.window, id, config_tab)
self.map_graph[id] = widget
def initial_table(self):
for widget in self.map_table.values():
widget.initial()
def load_project(self, project):
self.list_id.clear()
self.tab_widget.clear()
if not ("display" in config):
return
config_display = config["display"]
if not (project in config_display):
return
config_project = config_display[project]
for config_tab in config_project:
if not "type" in config_tab:
continue
id = config_tab["id"]
label = config_tab["label"]
widget = None
if config_tab["type"] == "table":
widget = self.map_table[id]
elif config_tab["type"] == "graph":
widget = self.map_graph[id]
self.list_id.append(id)
self.tab_widget.addTab(widget, label)
self.tab_widget.setTabToolTip(self.tab_widget.count() - 1, f"id:{id}")
def get_current_widget(self):
widget = self.tab_widget.currentWidget()
return widget
def get_current_id(self):
widget = self.get_current_widget()
if widget is None:
return
return self.get_current_widget().id
def load_table(self, data):
widget = self.get_current_widget()
if widget is None:
return
widget.load_table(data)
def dump_table(self):
widget = self.get_current_widget()
if widget is None:
return
return widget.dump_table()

130
app/widget_interact.py Normal file
View File

@ -0,0 +1,130 @@
# -*- encoding:utf-8 -*-
import os
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtWidgets import (
QWidget, QDockWidget, QTabWidget, QVBoxLayout, QHBoxLayout,
QGridLayout, QPushButton, QLabel, QLineEdit, QGroupBox,
)
from PyQt6.QtGui import (
QFont, QIcon, QColor, QIntValidator, QDoubleValidator,
)
import color
from config import config, script_path
font = QFont("consolas", 14)
font.setFamilies(["consolas", "黑体"])
class edit_input(QLineEdit):
def __init__(self, window, id, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.id = id
self.textChanged.connect(self.on_text_changed)
def on_text_changed(self):
if self.id:
self.window.widget_interact.widget_grid.map_input[self.id] = self.text()
class button_input(QPushButton):
def __init__(self, window, script, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.script = script
self.clicked.connect(self.on_click)
def on_click(self):
if not self.script:
return
filename = os.path.join(script_path, self.script)
if not os.path.isfile(filename):
raise RuntimeError(f"the path is not a valid file: \"{filename}\"")
filename = filename.replace("\\", "\\\\")
filename = filename.replace("\"", "\\\"")
self.window.process(f"window.load_script(\"{filename}\")")
class widget_grid(QWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
layout_base = QVBoxLayout()
self.setLayout(layout_base)
group_grid = QGroupBox()
layout_base.addWidget(group_grid)
self.layout_grid = QGridLayout()
group_grid.setLayout(self.layout_grid)
self.list_widget = []
self.map_input = {}
def load_interact(self, table_id):
for widget in self.list_widget:
self.layout_grid.removeWidget(widget)
widget.deleteLater()
self.list_widget.clear()
if not ("interact" in config):
return
config_interact = config["interact"]
if not (table_id in config_interact):
return
config_interact = config_interact[table_id]
row = 0
col = 0
for cfg in config_interact:
tp = cfg["type"]
label = cfg["label"]
if tp == "input":
if col > 0:
row += 1
col = 0
id = cfg.get("id")
if id in self.map_input:
text = self.map_input[id]
else:
text = cfg.get("text", "")
self.map_input[id] = text
label = QLabel(label)
label.setFont(font)
label.setToolTip(f"id:{id}")
edit = edit_input(self.window, id, text)
self.list_widget.append(label)
self.list_widget.append(edit)
self.layout_grid.addWidget(label, row, 0)
self.layout_grid.addWidget(edit, row, 1)
row += 1
elif tp == "button":
if "script" in cfg:
script = cfg["script"]
else:
script = None
button = button_input(self.window, script, label)
self.list_widget.append(button)
self.layout_grid.addWidget(button, row, col)
if col == 0:
col += 1
else:
row += 1
col = 0
class widget_interact(QDockWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)
self.setFont(font)
self.widget_grid = widget_grid(window)
self.setWidget(self.widget_grid)
def load_interact(self, table_id):
self.widget_grid.load_interact(table_id)

173
app/widget_term.py Normal file
View File

@ -0,0 +1,173 @@
import re
from PyQt6.QtCore import Qt, QPoint, QStringListModel
from PyQt6.QtWidgets import (
QDockWidget, QTextEdit, QPlainTextEdit,
QSplitter, QMenu, QCompleter,
)
from PyQt6.QtGui import (
QFont, QTextCursor, QKeySequence,
QAction, QPalette, QColorConstants,
)
from IPython.core.completer import (
provisionalcompleter, cursor_to_position,
_deduplicate_completions
)
from text_term import text_term
class completer_py(QCompleter):
def __init__(self, widget, *args):
super().__init__(*args)
model = QStringListModel([], self)
self.setWidget(widget)
self.setModel(model)
self.setModelSorting(QCompleter.ModelSorting.CaseInsensitivelySortedModel)
self.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.setWrapAround(False)
self.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
self.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.activated.connect(widget.insertCompletion)
class text_input(QPlainTextEdit):
def __init__(self, window, *arg):
super().__init__(*arg)
self.window = window
self.setTabStopDistance(40)
self.setPlaceholderText(
"%s 发送命令\n"
"%s 自动补全\n"
"%s%s 浏览命令历史\n"
% (
QKeySequence("Ctrl+Enter").toString(QKeySequence.SequenceFormat.NativeText),
QKeySequence("Ctrl+E").toString(QKeySequence.SequenceFormat.NativeText),
QKeySequence("Ctrl+Up").toString(QKeySequence.SequenceFormat.NativeText),
QKeySequence("Ctrl+Down").toString(QKeySequence.SequenceFormat.NativeText),
)
)
self.completer_py = completer_py(self)
self.history = [""]
self.history_idx = 0
def insertCompletion(self, completion):
tc = self.textCursor()
extra = len(completion) - len(self.completer_py.completionPrefix())
tc.movePosition(QTextCursor.MoveOperation.Left)
tc.movePosition(QTextCursor.MoveOperation.EndOfWord)
tc.insertText(completion[-extra:])
self.setTextCursor(tc)
def textUnderCursor(self):
tc = self.textCursor()
tc.select(QTextCursor.SelectionType.WordUnderCursor)
return tc.selectedText()
def focusInEvent(self, event):
if self.completer_py is not None:
self.completer_py.setWidget(self)
super().focusInEvent(event)
def keyPressEvent(self, event):
if(
event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return)
and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier)
):
s = self.toPlainText()
if not s:
return
self.window.process(s)
self.history[-1] = s
if len(self.history) >= 2 and self.history[-1] == self.history[-2]:
self.history.pop()
self.history_idx = len(self.history)
self.history.append("")
self.clear()
return
if(
event.key() == Qt.Key.Key_Up
and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier)
and self.history_idx > 0
):
if self.history_idx == len(self.history) - 1:
self.history[-1] = self.toPlainText()
self.history_idx -= 1
self.setPlainText(self.history[self.history_idx])
return
if(
event.key() == Qt.Key.Key_Down
and bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier)
and self.history_idx < len(self.history) - 1
):
self.history_idx += 1
self.setPlainText(self.history[self.history_idx])
return
if self.completer_py is not None and self.completer_py.popup().isVisible():
# The following keys are forwarded by the completer to the widget.
if event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return, Qt.Key.Key_Escape, Qt.Key.Key_Tab, Qt.Key.Key_Backtab):
event.ignore()
# Let the completer do default behavior.
return
isShortcut = (bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) and event.key() == Qt.Key.Key_E)
if self.completer_py is None or not isShortcut:
# Do not process the shortcut when we have a completer.
super().keyPressEvent(event)
ctrlOrShift = event.modifiers() & (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier)
if self.completer_py is None or (ctrlOrShift and len(event.text()) == 0):
return
eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
hasModifier = (event.modifiers() != Qt.KeyboardModifier.NoModifier) and not ctrlOrShift
completionPrefix = self.textUnderCursor()
if not isShortcut and (hasModifier or len(event.text()) == 0 or len(completionPrefix) < 3 or event.text()[-1] in eow):
self.completer_py.popup().hide()
return
pos = self.textCursor().position()
value = self.document().toRawText()
hint = re.search(r"[_0-9a-zA-Z.%]*$", value[:pos]).group()
pos_hint = pos - len(hint)
hint += re.search(r"^[_0-9a-zA-Z.%]*", value[pos:]).group()
with provisionalcompleter():
complete_list = _deduplicate_completions(hint, self.window.ipshell.Completer.completions(hint, pos - pos_hint))
complete_list = list(complete_list)
complete_list = [i.text for i in complete_list]
# complete_list = self.window.ipshell.complete(hint, cursor_pos = pos - pos_hint)
# print(complete_list)
self.completer_py.model().setStringList(complete_list)
if completionPrefix != self.completer_py.completionPrefix():
self.completer_py.setCompletionPrefix(completionPrefix)
self.completer_py.popup().setCurrentIndex(
self.completer_py.completionModel().index(0, 0))
cr = self.cursorRect()
cr.setWidth(self.completer_py.popup().sizeHintForColumn(0) + self.completer_py.popup().verticalScrollBar().sizeHint().width())
self.completer_py.complete(cr)
class widget_term(QDockWidget):
def __init__(self, window, *arg):
super().__init__(*arg)
self.window = window
self.setAllowedAreas(
Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea
| Qt.DockWidgetArea.TopDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea
)
font = QFont("consolas", 12)
font.setFamilies(["consolas", "黑体"])
self.setFont(font)
split = QSplitter(Qt.Orientation.Horizontal)
self.setWidget(split)
self.text_term = text_term(window, limit = 2 ** 16)
split.addWidget(self.text_term)
self.text_input = text_input(window)
split.addWidget(self.text_input)

68
app/widget_tree.py Normal file
View File

@ -0,0 +1,68 @@
# -*- encoding: utf-8 -*-
import os
import datetime
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QDockWidget, QTabWidget, QTreeWidget, QTreeWidgetItem,
)
from PyQt6.QtGui import (
QFont, QIcon,
)
from config import config
font = QFont("consolas", 14)
font.setFamilies(["consolas", "黑体"])
class tree_item(QTreeWidgetItem):
def __init__(self, data, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.data = data
class tree_widget(QTreeWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.setColumnCount(1)
self.setHeaderLabels(["项目"])
if "tree" in config:
config_tree = config["tree"]
def search(node, config):
for child in config:
id = child["id"]
label = child["label"]
data = {
"id": id,
}
item = tree_item(data, [label])
node.addChild(item)
if "child" in child:
search(item, child["child"])
search(self, config_tree)
self.itemClicked.connect(self.on_item_clicked)
def addChild(self, item):
return super().addTopLevelItem(item)
def on_item_clicked(self, item, column):
self.window.widget_display.load_project(item.data["id"])
class widget_tree(QDockWidget):
def __init__(self, window, *arg, **kwarg):
super().__init__(*arg, **kwarg)
self.window = window
self.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)
self.setFont(font)
self.tree_widget = tree_widget(window)
self.setWidget(self.tree_widget)

19
pyinstaller/build.ps1 Normal file
View File

@ -0,0 +1,19 @@
. "E:/program/env_win_gui/Scripts/Activate.ps1"
pyinstaller -y program.spec
$src = "../app/"
$dist = "./dist/dataprocess/"
$files = @("config/", "data/", "script/", "doc/")
foreach ($file in $files) {
$src_path = Join-Path -Path $src -ChildPath $file
$dist_path = Join-Path -Path $dist -ChildPath $file
# Check if the file exists before copying
if (Test-Path $src_path) {
Copy-Item -Recurse -Path $src_path -Destination $dist_path -Verbose
Write-Host "Copied: $src_path$dist_path"
} else {
Write-Warning "Path not found: $src_path"
}
}

172
pyinstaller/program.spec Normal file
View File

@ -0,0 +1,172 @@
# -*- mode: python coding: utf-8 -*-
# freeze the python application into a stand-alone executable
# https://www.pyinstaller.org/
# usage:
# pyinstaller -y visualstock.spec
import sys
import os
import shutil
block_cipher = None
a = Analysis(
["../app/main.py"],
pathex = ["./"],
binaries = [
#("../python/*.dll", "./"),
],
datas = [
#("../app/config/", "config"),
#("../app/data/", "data"),
#("../app/script/", "script"),
],
hiddenimports = ["numpy.core.multiarray"],
hookspath = [],
runtime_hooks = [],
excludes = [
"astroid",
"astropy",
"babel",
"bcolz",
"bcrypt",
"black",
"blib2to3",
"blosc",
"bokeh",
"boto3",
"botocore",
"bottleneck",
"branca",
#"certifi",
"contourpy",
"coverage",
"cryptography",
"cv2",
"cvxopt",
"cytoolz",
"Cython",
"dask",
"distributed",
"django",
"docutils",
"duckdb",
"erfa",
"greenlet",
"h5py",
"jedi",
"jsonschema",
"markupsafe",
"matplotlib",
"nacl",
"netCDF4",
"netcdftime",
"notebook",
"numexpr",
"parso",
"PIL",
"psutil",
"pyarrow",
"pydantic",
"pymongo",
"PyQt5",
"Pythonwin",
#"pytz",
#"regex",
"scikit-image",
"scikit-learn",
"scipy",
"sphinx",
"sqlalchemy",
"tables",
#"tcl",
"tcl8",
#"tk",
"torch",
"torchaudio",
"torchvision",
"tornado",
"msgpack",
"nbconvert",
"nbformat",
"simplejson",
#"win32",
"win32com",
"wx",
"zmq",
"zope",
],
win_no_prefer_redirects = False,
win_private_assemblies = False,
cipher = block_cipher,
noarchive = False
)
custom_exclude = [
"mkl_avx.dll",
"mkl_avx2.dll",
"mkl_avx512.dll",
#"mkl_core.dll",
#"mkl_def.dll",
#"mkl_intel_thread.dll",
"mkl_mc.dll",
"mkl_mc3.dll",
#"mkl_rt.dll",
"mkl_sequential.dll",
"mkl_tbb_thread.dll",
"mkl_vml_avx.dll",
"mkl_vml_avx2.dll",
"mkl_vml_avx512.dll",
"mkl_vml_cmpt.dll",
"mkl_vml_def.dll",
"mkl_vml_mc.dll",
"mkl_vml_mc2.dll",
"mkl_vml_mc3.dll",
"svml_dispmd.dll",
]
binaries = []
for item in a.binaries:
flag = True
for ptn in custom_exclude:
l = len(ptn)
i = item[0]
if len(i) < l:
continue
if i[-l:] == ptn:
flag = False
if flag:
binaries.append(item)
#print(len(binaries), binaries)
a.binaries = binaries
pyz = PYZ(
a.pure, a.zipped_data,
cipher = block_cipher
)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries = True,
name = "DataProcess",
debug = False,
bootloader_ignore_signals = False,
strip = False,
upx = False,
console = False,
#icon = "../image/visualstock.png"
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip = False,
upx = True,
upx_exclude = [],
name = "dataprocess"
)

10
pyinstaller/test.bat Normal file
View File

@ -0,0 +1,10 @@
pushd dist\dataprocess
set PATH_SAVE=%PATH%
set PATH=C:\WINDOWS\System32;
dataprocess.exe
set PATH=%PATH_SAVE%
popd