初始化

This commit is contained in:
2026-04-22 12:27:49 +08:00
commit 4857cb6e45
73 changed files with 20927 additions and 0 deletions

569
tools/ui/tabs/test_tab.py Normal file
View File

@@ -0,0 +1,569 @@
import json
import os
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QLineEdit, QFileDialog, QMessageBox,
QComboBox, QSpinBox, QGroupBox, QFormLayout,
QTableWidget, QHeaderView, QSizePolicy, QTableWidgetItem,
QCheckBox)
import arcpy
from ..runners.script_runner import ScriptRunner
class TestTab(QWidget):
"""导出成果图标签页"""
def __init__(self, parent=None):
super().__init__(parent)
self.main_window = parent
self.script_runner = ScriptRunner(self)
self.connect_signals()
self.init_ui()
self.load_settings()
def connect_signals(self):
"""连接脚本运行器信号"""
# 连接ScriptRunner的信号
self.script_runner.started.connect(self.on_script_started)
self.script_runner.finished.connect(self.on_script_finished)
self.script_runner.error.connect(self.on_script_error)
self.script_runner.log.connect(self.log_message)
def init_ui(self):
layout = QVBoxLayout(self)
# 配置文件设置组
config_group = QGroupBox("配置设置")
config_layout = QVBoxLayout(config_group)
# 配置文件选择
config_file_layout = QHBoxLayout()
config_file_layout.addWidget(QLabel("配置文件:"))
self.config_file_path = QLineEdit()
config_file_layout.addWidget(self.config_file_path)
config_file_btn = QPushButton("浏览...")
config_file_btn.clicked.connect(self.browse_config_file)
config_file_layout.addWidget(config_file_btn)
config_layout.addLayout(config_file_layout)
# 区县名字设置
county_name_layout = QHBoxLayout()
county_name_layout.addWidget(QLabel("区县名称:"))
self.county_name = QLineEdit()
self.county_name.setPlaceholderText("请输入区县名称")
county_name_layout.addWidget(self.county_name)
config_layout.addLayout(county_name_layout)
# 图层名称选择
layer_select_layout = QHBoxLayout()
layer_select_layout.addWidget(QLabel("选择图层:"))
self.layer_name_combo = QComboBox()
self.layer_name_combo.setMinimumWidth(200)
self.layer_name_combo.currentIndexChanged.connect(self.on_layer_name_changed)
layer_select_layout.addWidget(self.layer_name_combo)
layer_select_layout.addStretch()
config_layout.addLayout(layer_select_layout)
# 配置文件设置显示区域
export_layout = QVBoxLayout()
export_layout.addWidget(QLabel("配置文件设置:"))
export_layout.minimumHeightForWidth(200)
self.export_config_display = QTableWidget(0, 2)
self.export_config_display.setHorizontalHeaderLabels(["元素名称", "元素内容"])
header = self.export_config_display.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setDefaultSectionSize(150)
self.export_config_display.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
export_layout.addWidget(self.export_config_display)
config_layout.addLayout(export_layout)
layout.addWidget(config_group)
# 输入输出设置组
io_group = QGroupBox("输入输出设置")
io_layout = QVBoxLayout()
# 地图文档选择
doc_layout = QHBoxLayout()
doc_layout.addWidget(QLabel("模板文件:"))
self.template_aprx_file = QLineEdit()
doc_layout.addWidget(self.template_aprx_file)
browse_doc_btn = QPushButton("浏览...")
browse_doc_btn.clicked.connect(self.browse_doc)
doc_layout.addWidget(browse_doc_btn)
io_layout.addLayout(doc_layout)
# 数据源选择
data_layout = QHBoxLayout()
data_layout.addWidget(QLabel("数据源:"))
self.data_source_path = QLineEdit()
data_layout.addWidget(self.data_source_path)
browse_data_btn = QPushButton("浏览...")
browse_data_btn.clicked.connect(self.browse_data_source)
data_layout.addWidget(browse_data_btn)
io_layout.addLayout(data_layout)
# 符号系统文件夹选择
symbol_layout = QHBoxLayout()
symbol_layout.addWidget(QLabel("符号系统:"))
self.symbol_path = QLineEdit()
symbol_layout.addWidget(self.symbol_path)
browse_symbol_btn = QPushButton("浏览...")
browse_symbol_btn.clicked.connect(self.browse_symbol_file)
symbol_layout.addWidget(browse_symbol_btn)
io_layout.addLayout(symbol_layout)
# 输出路径选择
output_layout = QHBoxLayout()
output_layout.addWidget(QLabel("输出路径:"))
self.output_path = QLineEdit()
output_layout.addWidget(self.output_path)
browse_output_btn = QPushButton("浏览...")
browse_output_btn.clicked.connect(self.browse_output)
output_layout.addWidget(browse_output_btn)
io_layout.addLayout(output_layout)
io_group.setLayout(io_layout)
layout.addWidget(io_group)
# 导出设置组
export_group = QGroupBox("导出设置")
export_layout = QFormLayout()
# 格式选择
self.format_combo = QComboBox()
self.format_combo.addItems(["PDF", "PNG", "JPG", "TIFF", "EPS", "SVG", "AI"])
export_layout.addRow("导出格式:", self.format_combo)
# 分辨率设置
self.resolution_spinbox = QSpinBox()
self.resolution_spinbox.setRange(72, 1200)
self.resolution_spinbox.setSingleStep(12)
self.resolution_spinbox.setValue(300)
self.resolution_spinbox.setSuffix(" DPI")
export_layout.addRow("分辨率:", self.resolution_spinbox)
# 强制重新生成选项
self.force_regenerate = QCheckBox("强制重新生成工程文件")
self.force_regenerate.setChecked(False)
self.force_regenerate.setToolTip("勾选后将忽略已存在的工程文件,重新生成")
export_layout.addRow("处理选项:", self.force_regenerate)
export_group.setLayout(export_layout)
layout.addWidget(export_group)
# 操作按钮
button_layout = QHBoxLayout()
self.export_layout_btn = QPushButton("仅导出布局")
self.export_layout_btn.clicked.connect(self.on_export_layout)
self.export_btn = QPushButton("导出")
self.export_btn.clicked.connect(self.on_export)
self.batch_export_btn = QPushButton("批量导出")
self.batch_export_btn.clicked.connect(self.on_batch_export)
self.export_existing_btn = QPushButton("导出已有工程")
self.export_existing_btn.setToolTip("仅导出输出文件夹中已存在的工程文件")
self.export_existing_btn.clicked.connect(self.on_export_existing)
self.test_btn = QPushButton("测试功能")
self.test_btn.setToolTip("测试功能")
self.test_btn.clicked.connect(self.on_test_functions)
button_layout.addStretch()
button_layout.addWidget(self.export_layout_btn)
button_layout.addWidget(self.export_btn)
button_layout.addWidget(self.batch_export_btn)
button_layout.addWidget(self.export_existing_btn)
button_layout.addWidget(self.test_btn)
layout.addLayout(button_layout)
layout.addStretch()
def browse_config_file(self):
"""浏览配置文件"""
initial_path = os.getcwd()
file_path, _ = QFileDialog.getOpenFileName(self, "选择配置文件", initial_path, "JSON Files (*.json);;All Files (*.*)")
if file_path:
self.config_file_path.setText(file_path)
self.load_export_config(file_path)
def browse_doc(self):
"""浏览地图文档"""
file_path, _ = QFileDialog.getOpenFileName(
self,
"选择地图文档",
"",
"ArcGIS Pro Project (*.aprx);;All Files (*.*)"
)
if file_path:
self.template_aprx_file.setText(file_path)
def browse_data_source(self):
"""浏览数据源"""
dir_path = QFileDialog.getExistingDirectory(
self,
"选择数据源目录"
)
if dir_path:
self.data_source_path.setText(dir_path)
def browse_symbol_file(self):
"""浏览符号系统文件夹"""
dir_path = QFileDialog.getExistingDirectory(
self,
"选择符号系统文件夹"
)
if dir_path:
self.symbol_path.setText(dir_path)
def browse_output(self):
"""选择输出路径"""
dir_path = QFileDialog.getExistingDirectory(
self,
"选择输出路径"
)
if dir_path:
self.output_path.setText(dir_path)
def load_export_config(self, file_path):
"""加载导出配置文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
config = json.load(f)
export_config = config.get('export_config', {})
if not export_config:
QMessageBox.warning(self, "警告", "配置文件中未找到导出设置")
return
# 保存配置到实例变量
self._config = config
self._export_config = export_config
# 清空并添加图层名称到下拉框
self.layer_name_combo.clear()
self.layer_name_combo.addItems(export_config.keys())
# 如果有图层,自动选择第一个
if self.layer_name_combo.count() > 0:
self.layer_name_combo.setCurrentIndex(0)
self.log_message(f"已加载配置文件: {file_path}")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载配置文件失败: {str(e)}")
def on_layer_name_changed(self, index):
"""图层名称变更时更新配置显示"""
if index < 0 or not hasattr(self, '_export_config'):
return
layer_name = self.layer_name_combo.currentText()
layer_config = self._export_config.get(layer_name, {})
# 清空并重新添加行
self.export_config_display.setRowCount(0)
for i, (key, value) in enumerate(layer_config.items()):
self.export_config_display.insertRow(i)
self.export_config_display.setItem(i, 0, QTableWidgetItem(key))
self.export_config_display.setItem(i, 1, QTableWidgetItem(str(value)))
def on_script_started(self, message):
"""脚本开始执行回调"""
self.set_buttons_enabled(False)
self.log_message(message)
def on_script_finished(self, task_id:str, success:bool, message:str):
"""脚本执行完成回调"""
self.set_buttons_enabled(True)
self.log_message("脚本执行完成")
def on_script_error(self, error_msg):
"""脚本执行错误回调"""
self.set_buttons_enabled(True)
self.log_message(f"错误: {error_msg}")
QMessageBox.critical(self, "错误", error_msg)
def set_buttons_enabled(self, enabled):
"""设置按钮启用状态"""
self.export_btn.setEnabled(enabled)
self.batch_export_btn.setEnabled(enabled)
self.export_layout_btn.setEnabled(enabled)
self.export_existing_btn.setEnabled(enabled)
self.test_btn.setEnabled(enabled)
def on_export(self):
"""导出按钮点击事件"""
# 验证输入
if not self.validate_inputs():
return
# 获取参数
params = self.get_export_params()
if not params:
return
# 添加配置文件参数
params['config_file'] = self.config_file_path.text()
# 调用导出地图脚本
self.script_runner.run_export_map(params)
def on_batch_export(self):
"""批量导出按钮点击事件"""
# 验证输入
if not self.validate_inputs(check_layer=False):
return
# 获取参数
params = self.get_export_params(include_layer=False)
if not params:
return
# 检查配置是否包含图层
if not hasattr(self, '_export_config') or not self._export_config:
QMessageBox.warning(self, "警告", "请先加载配置文件")
return
# 添加配置文件路径参数
params['config_file'] = self.config_file_path.text()
params['export_config'] = self._export_config
# 检查可用图层
polygon_list = list(self._export_config.keys())
available_layers = []
for layer_name in polygon_list:
layer_path = os.path.join(params.get("data_source_path"), layer_name)
if arcpy.Exists(layer_path):
available_layers.append(layer_name)
params['polygon_list'] = available_layers
# 确认是否批量导出所有图层
layer_count = len(available_layers)
result = QMessageBox.question(
self,
"确认",
f"数据源可用图层 {layer_count} 个,是否继续?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if result == QMessageBox.No:
return
# 调用批量导出脚本
self.script_runner.run_batch_export_map(params)
def validate_inputs(self, check_layer=True):
"""验证输入参数"""
if check_layer and self.layer_name_combo.currentIndex() < 0:
QMessageBox.warning(self, "警告", "请选择一个图层")
return False
if not self.county_name.text():
QMessageBox.warning(self, "警告", "请输入区县名称")
return False
if not self.template_aprx_file.text() or not os.path.exists(self.template_aprx_file.text()):
QMessageBox.warning(self, "警告", "请选择有效的模板文件")
return False
if not self.data_source_path.text() or not os.path.exists(self.data_source_path.text()):
QMessageBox.warning(self, "警告", "请选择有效的数据源")
return False
if not self.output_path.text():
QMessageBox.warning(self, "警告", "请选择输出路径")
return False
return True
def get_export_params(self, include_layer=True):
"""获取导出参数"""
try:
params = {
'county_name': self.county_name.text(),
'template_aprx_file': self.template_aprx_file.text(),
'data_source_path': self.data_source_path.text(),
'symbol_path': self.symbol_path.text(),
'output_path': self.output_path.text(),
'force_regenerate': self.force_regenerate.isChecked()
}
if include_layer:
params['layer_name'] = self.layer_name_combo.currentText()
return params
except Exception as e:
QMessageBox.critical(self, "错误", f"获取参数失败: {str(e)}")
return None
def load_settings(self):
"""加载设置"""
if not self.main_window:
return
try:
settings = self.main_window.settings
last_paths = settings.get('last_paths', {})
if last_paths.get('config_file'):
self.config_file_path.setText(last_paths.get('config_file', ''))
if os.path.exists(last_paths.get('config_file', '')):
self.load_export_config(last_paths.get('config_file', ''))
self.county_name.setText(last_paths.get('county_name', ''))
self.template_aprx_file.setText(last_paths.get('template_aprx_file', ''))
self.data_source_path.setText(last_paths.get('data_source_path', ''))
self.symbol_path.setText(last_paths.get('symbol_path', ''))
self.output_path.setText(last_paths.get('output_path', ''))
export_settings = settings.get('export_settings', {})
# 设置导出格式
format_index = self.format_combo.findText(export_settings.get('default_format', 'PDF'))
if format_index >= 0:
self.format_combo.setCurrentIndex(format_index)
# 设置分辨率
self.resolution_spinbox.setValue(export_settings.get('resolution', 300))
except Exception as e:
self.log_message(f"加载设置失败: {str(e)}")
def on_export_layout(self):
"""仅导出布局按钮点击事件"""
# 验证输入
if not self.validate_inputs():
return
# 获取参数
export_params = self.get_export_params()
if not export_params:
return
# 检查导出配置和图层名称
layer_name = self.layer_name_combo.currentText()
if not hasattr(self, '_export_config') or layer_name not in self._export_config:
QMessageBox.warning(self, "警告", "请先加载配置文件并选择图层")
return
# 准备工程文件路径
single_export_config = self._export_config.get(layer_name, {})
temp_file_name = single_export_config['项目名称'].split('\n')[1]
file_name = temp_file_name.replace('{区县占位符}', export_params['county_name'])
aprx_path = os.path.join(export_params['output_path'], f"{file_name}.aprx")
# 检查工程文件是否存在
if not os.path.exists(aprx_path):
result = QMessageBox.question(
self,
"确认",
f"工程文件不存在: {aprx_path}\n是否先生成工程文件?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if result == QMessageBox.Yes:
# 先生成工程文件
# 添加配置文件路径参数
export_params['config_file'] = self.config_file_path.text()
# 执行生成工程文件
self.script_runner.run_export_map(export_params)
return
else:
return
# 准备导出布局参数
layout_params = {
'aprx_path': aprx_path,
'output_path': export_params['output_path'],
'export_format': self.format_combo.currentText(),
'resolution': self.resolution_spinbox.value(),
'output_name': file_name
}
# 调用导出布局脚本
self.script_runner.run_export_layout(layout_params)
def on_export_existing(self):
"""导出已有工程按钮点击事件"""
# 验证输出路径
if not self.output_path.text() or not os.path.exists(self.output_path.text()):
QMessageBox.warning(self, "警告", "请选择有效的输出路径")
return
output_path = self.output_path.text()
# 查找所有aprx文件
aprx_files = []
for file in os.listdir(output_path):
if file.lower().endswith('.aprx'):
aprx_files.append(os.path.join(output_path, file))
if not aprx_files:
QMessageBox.warning(self, "警告", f"在指定目录中未找到aprx文件: {output_path}")
return
# 确认是否导出所有工程文件
result = QMessageBox.question(
self,
"确认",
f"将导出 {len(aprx_files)} 个工程文件的布局,是否继续?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if result == QMessageBox.No:
return
# 准备批量导出布局参数
batch_params = {
'aprx_folder': output_path,
'output_path': os.path.join(output_path, self.format_combo.currentText().lower()),
'export_format': self.format_combo.currentText(),
'resolution': self.resolution_spinbox.value()
}
# 调用批量导出布局脚本
self.script_runner.run_batch_export_layout(batch_params)
def log_message(self, message):
"""日志输出"""
if self.main_window and hasattr(self.main_window, 'log_signal'):
self.main_window.log_signal.emit(message)
else:
print(message)
def on_test_functions(self):
"""测试功能按钮点击事件"""
self.log_message("正在执行测试功能...")
try:
# 调用测试脚本
test_params = {
'message': "测试成功!这是来自脚本的消息。",
'count': 1
}
# 使用script_runner的run_test_script方法
if hasattr(self.script_runner, 'run_test_script'):
self.script_runner.run_test_script(test_params)
self.log_message("测试脚本调用已启动,请查看日志")
else:
# 如果没有run_test_script方法尝试调用test_script.py
script_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'core', 'test_script.py'))
if os.path.exists(script_path):
self.log_message(f"找到测试脚本: {script_path}")
args = {
'message': test_params['message'],
'count': test_params['count']
}
self.script_runner.run_script(script_path, args)
else:
QMessageBox.information(self, "测试", "找不到测试脚本: test_script.py")
self.log_message(f"找不到测试脚本: {script_path}")
except Exception as e:
self.log_message(f"测试功能调用失败: {str(e)}")
QMessageBox.critical(self, "错误", f"测试功能调用失败: {str(e)}")