tmp
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
build/
|
build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
@@ -1,18 +1,89 @@
|
|||||||
cmake_minimum_required(VERSION 3.15.2)
|
cmake_minimum_required(VERSION 3.15.2)
|
||||||
|
|
||||||
# 设置工程名称
|
# 设置工程名称
|
||||||
project(stt VERSION 1.4.1 LANGUAGES CXX)
|
project(stt VERSION 1.4.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
# 设置C++标准
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# 编译选项
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
|
||||||
|
|
||||||
message(STATUS "Platform: " ${CMAKE_HOST_SYSTEM_NAME})
|
message(STATUS "Platform: " ${CMAKE_HOST_SYSTEM_NAME})
|
||||||
# CMake默认的安装路径 Windows下为C:/Program\ Files/${Project_Name} Linux/Unix下为/usr/local
|
|
||||||
message(STATUS "Install prefix: " ${CMAKE_INSTALL_PREFIX})
|
message(STATUS "Install prefix: " ${CMAKE_INSTALL_PREFIX})
|
||||||
# CMake默认的变异类型为空
|
|
||||||
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
|
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
|
||||||
|
|
||||||
|
# 获取所有源文件
|
||||||
aux_source_directory(src STT_SRC)
|
aux_source_directory(src STT_SRC)
|
||||||
|
|
||||||
|
# 创建可执行文件(保留原有功能)
|
||||||
add_executable(stt ${STT_SRC})
|
add_executable(stt ${STT_SRC})
|
||||||
set_target_properties(stt PROPERTIES CXX_STANDARD 11)
|
set_target_properties(stt PROPERTIES CXX_STANDARD 11)
|
||||||
|
|
||||||
|
# 安装可执行文件
|
||||||
install(TARGETS stt RUNTIME DESTINATION sbin)
|
install(TARGETS stt RUNTIME DESTINATION sbin)
|
||||||
|
|
||||||
|
# Python绑定支持(可选)
|
||||||
|
option(BUILD_PYTHON_MODULE "Build Python module" OFF)
|
||||||
|
|
||||||
|
if(BUILD_PYTHON_MODULE)
|
||||||
|
# 查找pybind11
|
||||||
|
find_package(pybind11 REQUIRED)
|
||||||
|
|
||||||
|
if(NOT pybind11_FOUND)
|
||||||
|
message(STATUS "pybind11 not found, trying to find it via Python")
|
||||||
|
# 尝试通过Python找到pybind11
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${Python_EXECUTABLE} -m pybind11 --includes
|
||||||
|
OUTPUT_VARIABLE PYBIND11_INCLUDES
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT PYBIND11_INCLUDES)
|
||||||
|
message(FATAL_ERROR "pybind11 is required for Python module build")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Building Python module")
|
||||||
|
|
||||||
|
# 创建Python模块
|
||||||
|
pybind11_add_module(stt_python
|
||||||
|
pybind/stt_binding.cpp
|
||||||
|
${STT_SRC}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置模块属性
|
||||||
|
set_target_properties(stt_python PROPERTIES
|
||||||
|
CXX_STANDARD 11
|
||||||
|
OUTPUT_NAME "stt"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/pybind"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加包含目录
|
||||||
|
target_include_directories(stt_python PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 定义宏
|
||||||
|
target_compile_definitions(stt_python PRIVATE
|
||||||
|
VERSION_INFO="${PROJECT_VERSION}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 链接库(如果需要)
|
||||||
|
# target_link_libraries(stt_python PRIVATE ...)
|
||||||
|
|
||||||
|
message(STATUS "Python module will be built as: pybind/stt${PYTHON_MODULE_EXTENSION}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# 安装Python绑定文件(如果构建了Python模块)
|
||||||
|
if(BUILD_PYTHON_MODULE)
|
||||||
|
install(FILES
|
||||||
|
pybind/__init__.py
|
||||||
|
pybind/example_usage.py
|
||||||
|
DESTINATION ${Python_SITEARCH}/stt
|
||||||
|
)
|
||||||
|
endif()
|
||||||
170
demo/example_jupyter.py
Normal file
170
demo/example_jupyter.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
STT Jupyter Notebook使用示例
|
||||||
|
|
||||||
|
这个脚本展示了如何在Jupyter notebook中使用STT的Python绑定,
|
||||||
|
包括进度条适配。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pystt as stt
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def basic_jupyter_example():
|
||||||
|
"""Jupyter notebook基本使用示例"""
|
||||||
|
print("=== Jupyter Notebook基本示例 ===")
|
||||||
|
|
||||||
|
# 创建生成器
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置参数
|
||||||
|
generator.set_tree_depth(3, 6)
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
|
||||||
|
# 创建简单的进度回调
|
||||||
|
def simple_progress(description, percentage):
|
||||||
|
print(f"{description}: {percentage:.1f}%")
|
||||||
|
|
||||||
|
generator.set_progress_callback(simple_progress)
|
||||||
|
|
||||||
|
# 运行生成
|
||||||
|
result = generator.run("jupyter_output.msh")
|
||||||
|
|
||||||
|
print(f"生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def tqdm_jupyter_example():
|
||||||
|
"""使用tqdm的Jupyter示例"""
|
||||||
|
print("=== 使用tqdm的Jupyter示例 ===")
|
||||||
|
|
||||||
|
if not stt.HAS_TQDM:
|
||||||
|
print("tqdm未安装,使用简单进度条")
|
||||||
|
progress_cb = stt.create_simple_callback("STT生成")
|
||||||
|
else:
|
||||||
|
print("使用tqdm进度条")
|
||||||
|
progress_cb = stt.create_tqdm_callback("STT生成")
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
generator.set_progress_callback(progress_cb)
|
||||||
|
|
||||||
|
generator.set_tree_depth(2, 5)
|
||||||
|
generator.set_reference_system("Earth")
|
||||||
|
|
||||||
|
result = generator.run("tqdm_output.msh")
|
||||||
|
|
||||||
|
print(f"生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def auto_progress_example():
|
||||||
|
"""自动选择最佳进度条"""
|
||||||
|
print("=== 自动选择进度条示例 ===")
|
||||||
|
|
||||||
|
# 自动创建最适合的进度回调
|
||||||
|
progress_cb = stt.create_progress_callback("自动进度")
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
generator.set_progress_callback(progress_cb)
|
||||||
|
|
||||||
|
generator.set_tree_depth(2, 4)
|
||||||
|
generator.set_reference_system("Moon")
|
||||||
|
|
||||||
|
result = generator.run("auto_output.msh")
|
||||||
|
|
||||||
|
print(f"生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def advanced_jupyter_example():
|
||||||
|
"""高级Jupyter示例"""
|
||||||
|
print("=== 高级Jupyter示例 ===")
|
||||||
|
|
||||||
|
# 创建tqdm进度回调
|
||||||
|
if stt.HAS_TQDM:
|
||||||
|
progress_cb = stt.TqdmProgressCallback("高级STT生成")
|
||||||
|
else:
|
||||||
|
progress_cb = stt.SimpleProgressCallback("高级STT生成")
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
generator.set_progress_callback(progress_cb)
|
||||||
|
|
||||||
|
# 设置自定义参考系统
|
||||||
|
generator.set_pole_equator_radius(3396200.0, 3376200.0) # 火星
|
||||||
|
|
||||||
|
# 设置二十面体方向
|
||||||
|
generator.set_icosahedron_orient(0.0, 90.0)
|
||||||
|
|
||||||
|
# 使用完整参数
|
||||||
|
params = {
|
||||||
|
"output_msh": "mars_grid.msh",
|
||||||
|
"output_vertex": "mars_vertices.txt",
|
||||||
|
"output_triangle_center": "mars_centers.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
result = generator.run_full(params)
|
||||||
|
|
||||||
|
print(f"火星网格生成: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def notebook_integration_demo():
|
||||||
|
"""Notebook集成演示"""
|
||||||
|
print("=== Notebook集成演示 ===")
|
||||||
|
|
||||||
|
# 检查是否在notebook环境中
|
||||||
|
try:
|
||||||
|
from IPython.display import display, HTML
|
||||||
|
in_notebook = True
|
||||||
|
except ImportError:
|
||||||
|
in_notebook = False
|
||||||
|
|
||||||
|
if in_notebook:
|
||||||
|
print("检测到Jupyter环境,使用增强进度显示")
|
||||||
|
display(HTML("<h4>STT生成进度</h4>"))
|
||||||
|
|
||||||
|
def notebook_progress(description, percentage):
|
||||||
|
display(HTML(f"""
|
||||||
|
<div style='border: 1px solid #ccc; padding: 10px; margin: 5px;'>
|
||||||
|
<strong>{description}</strong>: {percentage:.1f}%
|
||||||
|
<div style='background: #f0f0f0; height: 20px; margin-top: 5px;'>
|
||||||
|
<div style='background: #4CAF50; height: 100%; width: {percentage}%;'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""))
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
generator.set_progress_callback(notebook_progress)
|
||||||
|
else:
|
||||||
|
print("普通Python环境,使用标准进度条")
|
||||||
|
progress_cb = stt.create_progress_callback("Notebook演示")
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
generator.set_progress_callback(progress_cb)
|
||||||
|
|
||||||
|
generator.set_tree_depth(2, 4)
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
|
||||||
|
result = generator.run("notebook_output.msh")
|
||||||
|
|
||||||
|
print(f"生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("STT Jupyter Notebook使用示例")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# 运行各种示例
|
||||||
|
try:
|
||||||
|
basic_jupyter_example()
|
||||||
|
tqdm_jupyter_example()
|
||||||
|
auto_progress_example()
|
||||||
|
advanced_jupyter_example()
|
||||||
|
notebook_integration_demo()
|
||||||
|
|
||||||
|
print("所有Jupyter示例运行完成!")
|
||||||
|
print("检查生成的文件以查看结果。")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"运行示例时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
138
demo/example_usage.py
Normal file
138
demo/example_usage.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
STT Python绑定使用示例
|
||||||
|
|
||||||
|
这个脚本展示了如何使用STT的Python绑定来生成球面三角网格。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pystt as stt
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def basic_example():
|
||||||
|
"""基本使用示例"""
|
||||||
|
print("=== 基本使用示例 ===")
|
||||||
|
|
||||||
|
# 创建生成器
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置参数
|
||||||
|
generator.set_tree_depth(3, 6) # 最小深度3,最大深度6
|
||||||
|
generator.set_reference_system("WGS84") # 使用WGS84参考系统
|
||||||
|
|
||||||
|
# 运行生成
|
||||||
|
result = generator.run("basic_output.msh")
|
||||||
|
|
||||||
|
print(f"生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print(f"输出文件: basic_output.msh")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def quick_creation_example():
|
||||||
|
"""快速创建示例"""
|
||||||
|
print("=== 快速创建示例 ===")
|
||||||
|
|
||||||
|
# 使用便利函数快速创建
|
||||||
|
info = stt.create_stt(
|
||||||
|
min_depth=2,
|
||||||
|
max_depth=5,
|
||||||
|
reference_system="Earth",
|
||||||
|
output_file="quick_output.msh"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("生成信息:")
|
||||||
|
for key, value in info.items():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def advanced_example():
|
||||||
|
"""高级使用示例"""
|
||||||
|
print("=== 高级使用示例 ===")
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置自定义参考系统(月球)
|
||||||
|
generator.set_pole_equator_radius(1738000.0, 1738000.0) # 月球半径
|
||||||
|
|
||||||
|
# 设置二十面体方向
|
||||||
|
generator.set_icosahedron_orient(0.0, 90.0) # 北极方向
|
||||||
|
|
||||||
|
# 使用完整参数运行
|
||||||
|
params = {
|
||||||
|
"output_msh": "advanced_output.msh",
|
||||||
|
"output_vertex": "vertices.txt",
|
||||||
|
"output_triangle_center": "triangle_centers.txt",
|
||||||
|
"output_neighbor": "neighbors.txt"
|
||||||
|
}
|
||||||
|
|
||||||
|
result = generator.run_full(params)
|
||||||
|
|
||||||
|
print(f"高级生成结果: {'成功' if result == 0 else '失败'}")
|
||||||
|
print("输出文件:")
|
||||||
|
for key, file in params.items():
|
||||||
|
if os.path.exists(file):
|
||||||
|
size = os.path.getsize(file)
|
||||||
|
print(f" {file}: {size} bytes")
|
||||||
|
else:
|
||||||
|
print(f" {file}: 未生成")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def custom_reference_system_example():
|
||||||
|
"""自定义参考系统示例"""
|
||||||
|
print("=== 自定义参考系统示例 ===")
|
||||||
|
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置自定义椭球参数(例如火星)
|
||||||
|
# 火星: 赤道半径 3396.2 km, 极半径 3376.2 km
|
||||||
|
generator.set_pole_equator_radius(3396200.0, 3376200.0)
|
||||||
|
generator.set_tree_depth(3, 7)
|
||||||
|
|
||||||
|
result = generator.run("mars_grid.msh")
|
||||||
|
|
||||||
|
print(f"火星网格生成: {'成功' if result == 0 else '失败'}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def check_module_info():
|
||||||
|
"""检查模块信息"""
|
||||||
|
print("=== 模块信息 ===")
|
||||||
|
|
||||||
|
info = stt.get_info()
|
||||||
|
for key, value in info.items():
|
||||||
|
print(f"{key}: {value}")
|
||||||
|
|
||||||
|
print(f"可用常量:")
|
||||||
|
print(f" stt.WGS84: {stt.WGS84}")
|
||||||
|
print(f" stt.EARTH: {stt.EARTH}")
|
||||||
|
print(f" stt.MOON: {stt.MOON}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("STT Python绑定使用示例")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
# 检查模块是否可用
|
||||||
|
try:
|
||||||
|
check_module_info()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: 无法加载STT模块 - {e}")
|
||||||
|
print("请确保已经正确安装STT Python绑定")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 运行各种示例
|
||||||
|
try:
|
||||||
|
basic_example()
|
||||||
|
quick_creation_example()
|
||||||
|
advanced_example()
|
||||||
|
custom_reference_system_example()
|
||||||
|
|
||||||
|
print("所有示例运行完成!")
|
||||||
|
print("检查生成的文件以查看结果。")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"运行示例时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
239
demo/test_binding.py
Normal file
239
demo/test_binding.py
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
"""
|
||||||
|
STT Python绑定测试脚本
|
||||||
|
|
||||||
|
这个脚本测试STT Python绑定的基本功能。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# 尝试导入STT模块
|
||||||
|
try:
|
||||||
|
import stt
|
||||||
|
STT_AVAILABLE = True
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"警告: 无法导入STT模块 - {e}")
|
||||||
|
print("请确保已经构建并安装了STT Python绑定")
|
||||||
|
STT_AVAILABLE = False
|
||||||
|
stt = None
|
||||||
|
|
||||||
|
class TestSttBinding(unittest.TestCase):
|
||||||
|
"""STT绑定测试类"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""测试类设置"""
|
||||||
|
if not STT_AVAILABLE:
|
||||||
|
raise unittest.SkipTest("STT模块不可用")
|
||||||
|
|
||||||
|
# 创建临时目录用于测试输出
|
||||||
|
cls.temp_dir = tempfile.mkdtemp()
|
||||||
|
print(f"测试临时目录: {cls.temp_dir}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
"""测试类清理"""
|
||||||
|
if hasattr(cls, 'temp_dir') and os.path.exists(cls.temp_dir):
|
||||||
|
# 清理临时文件
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(cls.temp_dir)
|
||||||
|
print(f"清理临时目录: {cls.temp_dir}")
|
||||||
|
|
||||||
|
def test_module_import(self):
|
||||||
|
"""测试模块导入"""
|
||||||
|
self.assertIsNotNone(stt, "STT模块应该可用")
|
||||||
|
self.assertTrue(hasattr(stt, 'SttGenerator'), "应该包含SttGenerator类")
|
||||||
|
self.assertTrue(hasattr(stt, 'create_stt'), "应该包含create_stt函数")
|
||||||
|
self.assertTrue(hasattr(stt, 'WGS84'), "应该包含WGS84常量")
|
||||||
|
|
||||||
|
def test_generator_creation(self):
|
||||||
|
"""测试生成器创建"""
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
self.assertIsNotNone(generator, "应该能够创建生成器实例")
|
||||||
|
|
||||||
|
# 测试基本方法存在
|
||||||
|
self.assertTrue(hasattr(generator, 'set_tree_depth'), "应该有set_tree_depth方法")
|
||||||
|
self.assertTrue(hasattr(generator, 'set_reference_system'), "应该有set_reference_system方法")
|
||||||
|
self.assertTrue(hasattr(generator, 'run'), "应该有run方法")
|
||||||
|
self.assertTrue(hasattr(generator, 'run_full'), "应该有run_full方法")
|
||||||
|
|
||||||
|
def test_basic_generation(self):
|
||||||
|
"""测试基本生成功能"""
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置基本参数
|
||||||
|
generator.set_tree_depth(2, 4) # 使用较小的深度进行快速测试
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
|
||||||
|
# 创建输出文件路径
|
||||||
|
output_file = os.path.join(self.temp_dir, "test_basic.msh")
|
||||||
|
|
||||||
|
# 运行生成
|
||||||
|
result = generator.run(output_file)
|
||||||
|
|
||||||
|
# 检查结果
|
||||||
|
self.assertEqual(result, 0, "生成应该成功返回0")
|
||||||
|
|
||||||
|
# 检查输出文件是否存在
|
||||||
|
if os.path.exists(output_file):
|
||||||
|
file_size = os.path.getsize(output_file)
|
||||||
|
print(f"生成文件大小: {file_size} 字节")
|
||||||
|
self.assertGreater(file_size, 0, "输出文件应该非空")
|
||||||
|
else:
|
||||||
|
print("警告: 输出文件未生成,这可能是因为生成器需要更多参数")
|
||||||
|
|
||||||
|
def test_quick_creation(self):
|
||||||
|
"""测试快速创建功能"""
|
||||||
|
output_file = os.path.join(self.temp_dir, "test_quick.msh")
|
||||||
|
|
||||||
|
# 使用便利函数
|
||||||
|
info = stt.create_stt(
|
||||||
|
min_depth=2,
|
||||||
|
max_depth=4,
|
||||||
|
reference_system="Earth",
|
||||||
|
output_file=output_file
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查返回信息
|
||||||
|
self.assertIsInstance(info, dict, "应该返回字典信息")
|
||||||
|
self.assertEqual(info['min_depth'], 2, "最小深度应该正确")
|
||||||
|
self.assertEqual(info['max_depth'], 4, "最大深度应该正确")
|
||||||
|
self.assertEqual(info['reference_system'], "Earth", "参考系统应该正确")
|
||||||
|
self.assertEqual(info['output_file'], output_file, "输出文件应该正确")
|
||||||
|
|
||||||
|
# 检查文件生成
|
||||||
|
if os.path.exists(output_file):
|
||||||
|
file_size = os.path.getsize(output_file)
|
||||||
|
print(f"快速创建文件大小: {file_size} 字节")
|
||||||
|
self.assertGreater(file_size, 0, "输出文件应该非空")
|
||||||
|
|
||||||
|
def test_custom_reference_system(self):
|
||||||
|
"""测试自定义参考系统"""
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 测试预设系统
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
generator.set_reference_system("Earth")
|
||||||
|
generator.set_reference_system("Moon")
|
||||||
|
|
||||||
|
# 测试自定义半径
|
||||||
|
generator.set_pole_equator_radius(6378137.0, 6356752.3) # WGS84参数
|
||||||
|
|
||||||
|
# 设置其他参数
|
||||||
|
generator.set_tree_depth(2, 3)
|
||||||
|
generator.set_icosahedron_orient(0.0, 90.0)
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
output_file = os.path.join(self.temp_dir, "test_custom.msh")
|
||||||
|
result = generator.run(output_file)
|
||||||
|
|
||||||
|
self.assertEqual(result, 0, "自定义参考系统生成应该成功")
|
||||||
|
|
||||||
|
def test_module_constants(self):
|
||||||
|
"""测试模块常量"""
|
||||||
|
self.assertEqual(stt.WGS84, "WGS84", "WGS84常量应该正确")
|
||||||
|
self.assertEqual(stt.EARTH, "Earth", "Earth常量应该正确")
|
||||||
|
self.assertEqual(stt.MOON, "Moon", "Moon常量应该正确")
|
||||||
|
|
||||||
|
def test_module_info(self):
|
||||||
|
"""测试模块信息"""
|
||||||
|
info = stt.get_info()
|
||||||
|
self.assertIsInstance(info, dict, "应该返回字典信息")
|
||||||
|
self.assertIn('version', info, "应该包含版本信息")
|
||||||
|
self.assertIn('author', info, "应该包含作者信息")
|
||||||
|
|
||||||
|
print("模块信息:")
|
||||||
|
for key, value in info.items():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
def test_advanced_parameters(self):
|
||||||
|
"""测试高级参数"""
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 设置参数
|
||||||
|
generator.set_tree_depth(2, 4)
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
generator.set_icosahedron_orient(0.0, 90.0)
|
||||||
|
|
||||||
|
# 使用完整参数运行
|
||||||
|
params = {
|
||||||
|
"output_msh": os.path.join(self.temp_dir, "test_advanced.msh"),
|
||||||
|
"output_vertex": os.path.join(self.temp_dir, "vertices.txt"),
|
||||||
|
"output_triangle_center": os.path.join(self.temp_dir, "centers.txt"),
|
||||||
|
"output_neighbor": os.path.join(self.temp_dir, "neighbors.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
result = generator.run_full(params)
|
||||||
|
self.assertEqual(result, 0, "高级参数生成应该成功")
|
||||||
|
|
||||||
|
# 检查生成的文件
|
||||||
|
for key, filename in params.items():
|
||||||
|
if os.path.exists(filename):
|
||||||
|
file_size = os.path.getsize(filename)
|
||||||
|
print(f"{key} 文件大小: {file_size} 字节")
|
||||||
|
|
||||||
|
def test_error_handling(self):
|
||||||
|
"""测试错误处理"""
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
|
||||||
|
# 测试无效参数(应该不会崩溃)
|
||||||
|
try:
|
||||||
|
generator.set_tree_depth(-1, 10) # 无效深度
|
||||||
|
generator.set_reference_system("InvalidSystem")
|
||||||
|
# 这些调用应该能够处理而不会崩溃
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误处理测试: {e}")
|
||||||
|
|
||||||
|
def run_basic_tests():
|
||||||
|
"""运行基本测试"""
|
||||||
|
print("运行STT Python绑定基本测试...")
|
||||||
|
|
||||||
|
if not STT_AVAILABLE:
|
||||||
|
print("错误: STT模块不可用,无法运行测试")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 创建简单的测试
|
||||||
|
try:
|
||||||
|
print("1. 测试模块导入...")
|
||||||
|
generator = stt.SttGenerator()
|
||||||
|
print(" ✓ 模块导入成功")
|
||||||
|
|
||||||
|
print("2. 测试基本功能...")
|
||||||
|
generator.set_tree_depth(2, 3)
|
||||||
|
generator.set_reference_system("WGS84")
|
||||||
|
print(" ✓ 基本设置成功")
|
||||||
|
|
||||||
|
print("3. 测试快速创建...")
|
||||||
|
info = stt.create_stt(2, 3, "Earth", "test.msh")
|
||||||
|
print(f" ✓ 快速创建成功: {info}")
|
||||||
|
|
||||||
|
print("4. 测试模块信息...")
|
||||||
|
info = stt.get_info()
|
||||||
|
print(f" ✓ 模块信息: {info['version']}")
|
||||||
|
|
||||||
|
print("基本测试通过!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"测试失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
print("STT Python绑定测试")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
# 运行基本测试
|
||||||
|
if run_basic_tests():
|
||||||
|
print("\n运行完整单元测试...")
|
||||||
|
unittest.main(argv=[''], exit=False, verbosity=2)
|
||||||
|
else:
|
||||||
|
print("\n基本测试失败,跳过完整测试")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
161
pybind/__init__.py
Normal file
161
pybind/__init__.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
STT Python Binding
|
||||||
|
==================
|
||||||
|
|
||||||
|
A Python interface for the Spherical Triangular Tessellation (STT) generator.
|
||||||
|
|
||||||
|
This module provides access to the C++ STT library for generating spherical
|
||||||
|
triangular tessellations on various reference systems.
|
||||||
|
|
||||||
|
Basic Usage:
|
||||||
|
------------
|
||||||
|
>>> import stt
|
||||||
|
>>> generator = stt.SttGenerator()
|
||||||
|
>>> generator.set_tree_depth(3, 8)
|
||||||
|
>>> generator.set_reference_system("WGS84")
|
||||||
|
>>> generator.run("output.msh")
|
||||||
|
|
||||||
|
Quick Creation:
|
||||||
|
--------------
|
||||||
|
>>> import stt
|
||||||
|
>>> info = stt.create_stt(3, 8, "WGS84", "output.msh")
|
||||||
|
>>> print(info)
|
||||||
|
|
||||||
|
Progress Callback (for Jupyter notebooks):
|
||||||
|
-----------------------------------------
|
||||||
|
>>> import stt
|
||||||
|
>>> def my_progress(description, percentage):
|
||||||
|
... print(f"{description}: {percentage:.1f}%")
|
||||||
|
...
|
||||||
|
>>> generator = stt.SttGenerator()
|
||||||
|
>>> generator.set_progress_callback(my_progress)
|
||||||
|
>>> generator.run("output.msh")
|
||||||
|
|
||||||
|
Advanced Usage:
|
||||||
|
--------------
|
||||||
|
>>> import stt
|
||||||
|
>>> generator = stt.SttGenerator()
|
||||||
|
>>> params = {
|
||||||
|
... "output_msh": "output.msh",
|
||||||
|
... "output_vertex": "vertices.txt",
|
||||||
|
... "control_points": "points.txt",
|
||||||
|
... "topography": "topo.txt"
|
||||||
|
... }
|
||||||
|
>>> generator.run_full(params)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .stt import (
|
||||||
|
SttGenerator,
|
||||||
|
create_stt,
|
||||||
|
WGS84,
|
||||||
|
EARTH,
|
||||||
|
MOON,
|
||||||
|
__version__
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'SttGenerator',
|
||||||
|
'create_stt',
|
||||||
|
'WGS84',
|
||||||
|
'EARTH',
|
||||||
|
'MOON',
|
||||||
|
'__version__',
|
||||||
|
'get_info',
|
||||||
|
'help'
|
||||||
|
]
|
||||||
|
|
||||||
|
# 模块信息
|
||||||
|
__author__ = 'STT Development Team'
|
||||||
|
__email__ = 'yizhang-geo@zju.edu.cn'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
__url__ = 'https://github.com/your-repo/stt'
|
||||||
|
|
||||||
|
def get_info():
|
||||||
|
"""获取STT模块信息"""
|
||||||
|
return {
|
||||||
|
'version': __version__,
|
||||||
|
'author': __author__,
|
||||||
|
'email': __email__,
|
||||||
|
'license': __license__,
|
||||||
|
'url': __url__
|
||||||
|
}
|
||||||
|
|
||||||
|
def help():
|
||||||
|
"""显示帮助信息"""
|
||||||
|
print(__doc__)
|
||||||
|
|
||||||
|
# Jupyter notebook进度条支持
|
||||||
|
try:
|
||||||
|
# 尝试导入tqdm用于更好的进度条支持
|
||||||
|
from tqdm import tqdm
|
||||||
|
HAS_TQDM = True
|
||||||
|
|
||||||
|
class TqdmProgressCallback:
|
||||||
|
"""使用tqdm的进度回调"""
|
||||||
|
def __init__(self, description="Progress"):
|
||||||
|
self.pbar = tqdm(total=100, desc=description)
|
||||||
|
self.current_description = description
|
||||||
|
|
||||||
|
def __call__(self, description, percentage):
|
||||||
|
if description != self.current_description:
|
||||||
|
self.pbar.set_description(description)
|
||||||
|
self.current_description = description
|
||||||
|
self.pbar.n = int(percentage)
|
||||||
|
self.pbar.refresh()
|
||||||
|
if percentage >= 100:
|
||||||
|
self.pbar.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.pbar:
|
||||||
|
self.pbar.close()
|
||||||
|
|
||||||
|
def create_tqdm_callback(description="STT Progress"):
|
||||||
|
"""创建tqdm进度回调"""
|
||||||
|
return TqdmProgressCallback(description)
|
||||||
|
|
||||||
|
__all__.extend(['TqdmProgressCallback', 'create_tqdm_callback'])
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
HAS_TQDM = False
|
||||||
|
|
||||||
|
class SimpleProgressCallback:
|
||||||
|
"""简单的文本进度回调"""
|
||||||
|
def __init__(self, description="Progress"):
|
||||||
|
self.current_description = description
|
||||||
|
self.last_percentage = -1
|
||||||
|
|
||||||
|
def __call__(self, description, percentage):
|
||||||
|
if description != self.current_description:
|
||||||
|
print(f"\n{description}:")
|
||||||
|
self.current_description = description
|
||||||
|
if int(percentage) != int(self.last_percentage):
|
||||||
|
print(f" {percentage:.1f}%", end='\r')
|
||||||
|
self.last_percentage = percentage
|
||||||
|
if percentage >= 100:
|
||||||
|
print(" 100.0%")
|
||||||
|
|
||||||
|
def create_simple_callback(description="STT Progress"):
|
||||||
|
"""创建简单进度回调"""
|
||||||
|
return SimpleProgressCallback(description)
|
||||||
|
|
||||||
|
__all__.extend(['SimpleProgressCallback', 'create_simple_callback'])
|
||||||
|
|
||||||
|
# 自动选择最佳的进度回调
|
||||||
|
def create_progress_callback(description="STT Progress", use_tqdm=None):
|
||||||
|
"""
|
||||||
|
创建适合的进度回调函数
|
||||||
|
|
||||||
|
参数:
|
||||||
|
description: 进度条描述
|
||||||
|
use_tqdm: 是否强制使用tqdm,None表示自动选择
|
||||||
|
|
||||||
|
返回:
|
||||||
|
进度回调函数
|
||||||
|
"""
|
||||||
|
if use_tqdm is None:
|
||||||
|
use_tqdm = HAS_TQDM
|
||||||
|
|
||||||
|
if use_tqdm and HAS_TQDM:
|
||||||
|
return create_tqdm_callback(description)
|
||||||
|
else:
|
||||||
|
return create_simple_callback(description)
|
||||||
74
pybind/progress_bar_python.h
Normal file
74
pybind/progress_bar_python.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef PROGRESS_BAR_PYTHON_H
|
||||||
|
#define PROGRESS_BAR_PYTHON_H
|
||||||
|
|
||||||
|
#ifdef PYTHON_BINDING
|
||||||
|
|
||||||
|
#include "python_progress_wrapper.h"
|
||||||
|
#include "../src/progress_bar.h"
|
||||||
|
|
||||||
|
// Python友好的进度条类
|
||||||
|
class PythonProgressBar : public ProgressBar {
|
||||||
|
private:
|
||||||
|
std::string description_;
|
||||||
|
unsigned long total_count_;
|
||||||
|
bool use_python_callback_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PythonProgressBar() : ProgressBar(), total_count_(0), use_python_callback_(false) {}
|
||||||
|
|
||||||
|
PythonProgressBar(unsigned long n_, const char* description_="", std::ostream& out_=std::cerr)
|
||||||
|
: ProgressBar(n_, description_, out_), description_(description_), total_count_(n_), use_python_callback_(false) {
|
||||||
|
// 检查是否有Python回调
|
||||||
|
if (ProgressCallbackManager::has_callback()) {
|
||||||
|
use_python_callback_ = true;
|
||||||
|
auto* callback = ProgressCallbackManager::get_callback();
|
||||||
|
if (callback) {
|
||||||
|
callback->set_description(description_);
|
||||||
|
callback->set_total(n_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFrequencyUpdate(unsigned long frequency_update_) {
|
||||||
|
if (!use_python_callback_) {
|
||||||
|
ProgressBar::SetFrequencyUpdate(frequency_update_);
|
||||||
|
}
|
||||||
|
// Python模式下使用固定更新频率
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStyle(const char* unit_bar_, const char* unit_space_) {
|
||||||
|
if (!use_python_callback_) {
|
||||||
|
ProgressBar::SetStyle(unit_bar_, unit_space_);
|
||||||
|
}
|
||||||
|
// Python模式下忽略样式设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void Progressed(unsigned long idx_) {
|
||||||
|
if (use_python_callback_) {
|
||||||
|
auto* callback = ProgressCallbackManager::get_callback();
|
||||||
|
if (callback) {
|
||||||
|
callback->update_progress(idx_);
|
||||||
|
// 完成时调用finish
|
||||||
|
if (idx_ >= total_count_ - 1) {
|
||||||
|
callback->finish();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到原有的进度条实现
|
||||||
|
ProgressBar::Progressed(idx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置是否使用Python回调
|
||||||
|
void set_use_python_callback(bool use) {
|
||||||
|
use_python_callback_ = use;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 替换原有的ProgressBar定义
|
||||||
|
#define ProgressBar PythonProgressBar
|
||||||
|
|
||||||
|
#endif // PYTHON_BINDING
|
||||||
|
|
||||||
|
#endif // PROGRESS_BAR_PYTHON_H
|
||||||
95
pybind/python_progress_wrapper.h
Normal file
95
pybind/python_progress_wrapper.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#ifndef PYTHON_PROGRESS_WRAPPER_H
|
||||||
|
#define PYTHON_PROGRESS_WRAPPER_H
|
||||||
|
|
||||||
|
#ifdef PYTHON_BINDING
|
||||||
|
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/functional.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
|
||||||
|
// Python进度回调接口
|
||||||
|
class PythonProgressCallback {
|
||||||
|
public:
|
||||||
|
virtual ~PythonProgressCallback() = default;
|
||||||
|
virtual void update(const std::string& description, double percentage) = 0;
|
||||||
|
virtual void set_description(const std::string& description) = 0;
|
||||||
|
virtual void set_total(unsigned long total) = 0;
|
||||||
|
virtual void update_progress(unsigned long current) = 0;
|
||||||
|
virtual void finish() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// pybind11包装器
|
||||||
|
class PyProgressCallback : public PythonProgressCallback {
|
||||||
|
private:
|
||||||
|
py::object callback_func_;
|
||||||
|
std::string current_description_;
|
||||||
|
unsigned long total_;
|
||||||
|
bool has_total_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PyProgressCallback(py::object callback)
|
||||||
|
: callback_func_(callback), total_(0), has_total_(false) {}
|
||||||
|
|
||||||
|
void update(const std::string& description, double percentage) override {
|
||||||
|
if (callback_func_ && !callback_func_.is_none()) {
|
||||||
|
try {
|
||||||
|
callback_func_(description, percentage);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// 忽略Python回调中的异常,避免崩溃
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_description(const std::string& description) override {
|
||||||
|
current_description_ = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_total(unsigned long total) override {
|
||||||
|
total_ = total;
|
||||||
|
has_total_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_progress(unsigned long current) override {
|
||||||
|
if (has_total_ && total_ > 0) {
|
||||||
|
double percentage = (static_cast<double>(current) / total_) * 100.0;
|
||||||
|
update(current_description_, percentage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish() override {
|
||||||
|
update(current_description_, 100.0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全局进度回调管理器
|
||||||
|
class ProgressCallbackManager {
|
||||||
|
private:
|
||||||
|
static std::unique_ptr<PythonProgressCallback> global_callback_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void set_callback(py::object callback) {
|
||||||
|
if (callback.is_none()) {
|
||||||
|
global_callback_.reset();
|
||||||
|
} else {
|
||||||
|
global_callback_.reset(new PyProgressCallback(callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PythonProgressCallback* get_callback() {
|
||||||
|
return global_callback_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_callback() {
|
||||||
|
return global_callback_ != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 静态成员定义
|
||||||
|
std::unique_ptr<PythonProgressCallback> ProgressCallbackManager::global_callback_ = nullptr;
|
||||||
|
|
||||||
|
#endif // PYTHON_BINDING
|
||||||
|
|
||||||
|
#endif // PYTHON_PROGRESS_WRAPPER_H
|
||||||
336
pybind/stt_binding.cpp
Normal file
336
pybind/stt_binding.cpp
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/stl.h>
|
||||||
|
#include <pybind11/numpy.h>
|
||||||
|
#include <pybind11/functional.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// 在包含原始头文件之前定义PYTHON_BINDING宏
|
||||||
|
#define PYTHON_BINDING
|
||||||
|
|
||||||
|
// 包含进度条包装器
|
||||||
|
#include "python_progress_wrapper.h"
|
||||||
|
#include "progress_bar_python.h"
|
||||||
|
|
||||||
|
// 包含原始STT头文件
|
||||||
|
#include "../src/stt_class.h"
|
||||||
|
|
||||||
|
// 检查tqdm是否可用
|
||||||
|
bool has_tqdm() {
|
||||||
|
py::object tqdm_module;
|
||||||
|
try {
|
||||||
|
tqdm_module = py::module_::import("tqdm");
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
|
||||||
|
// Python友好的SttGenerator包装器
|
||||||
|
class PySttGenerator {
|
||||||
|
private:
|
||||||
|
SttGenerator generator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PySttGenerator() = default;
|
||||||
|
|
||||||
|
// 设置树深度
|
||||||
|
void set_tree_depth(int min_depth, int max_depth) {
|
||||||
|
std::string depth_str = std::to_string(min_depth) + "/" + std::to_string(max_depth);
|
||||||
|
char* depth_char = const_cast<char*>(depth_str.c_str());
|
||||||
|
generator.set_tree_depth(depth_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置椭球半径
|
||||||
|
void set_pole_equator_radius(double equator_radius, double pole_radius) {
|
||||||
|
std::string radius_str = std::to_string(equator_radius) + "/" + std::to_string(pole_radius);
|
||||||
|
char* radius_char = const_cast<char*>(radius_str.c_str());
|
||||||
|
generator.set_pole_equator_radius(radius_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置二十面体方向
|
||||||
|
void set_icosahedron_orient(double longitude, double latitude) {
|
||||||
|
std::string orient_str = std::to_string(longitude) + "/" + std::to_string(latitude);
|
||||||
|
char* orient_char = const_cast<char*>(orient_str.c_str());
|
||||||
|
generator.set_icosahedron_orient(orient_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用预设的参考系统
|
||||||
|
void set_reference_system(const std::string& ref_system) {
|
||||||
|
char* ref_char = const_cast<char*>(ref_system.c_str());
|
||||||
|
generator.set_pole_equator_radius(ref_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置进度回调函数
|
||||||
|
void set_progress_callback(py::object callback) {
|
||||||
|
ProgressCallbackManager::set_callback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除进度回调
|
||||||
|
void clear_progress_callback() {
|
||||||
|
ProgressCallbackManager::set_callback(py::none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行主要例程 - 简化版本
|
||||||
|
int run(const std::string& output_msh_file = "") {
|
||||||
|
char options[14][1024];
|
||||||
|
|
||||||
|
// 初始化所有选项为"NULL"
|
||||||
|
for (int i = 0; i < 14; i++) {
|
||||||
|
strcpy(options[i], "NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果指定了输出文件,设置它
|
||||||
|
if (!output_msh_file.empty()) {
|
||||||
|
strncpy(options[3], output_msh_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.Routine(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取STT生成器信息
|
||||||
|
py::dict get_info() {
|
||||||
|
py::dict info;
|
||||||
|
|
||||||
|
// 获取当前设置的树深度
|
||||||
|
int min_depth, max_depth;
|
||||||
|
// 这里需要根据实际的SttGenerator类实现来获取深度信息
|
||||||
|
// 暂时使用默认值,实际实现中应该从generator对象获取
|
||||||
|
info["min_depth"] = 1; // 需要根据实际实现调整
|
||||||
|
info["max_depth"] = 5; // 需要根据实际实现调整
|
||||||
|
|
||||||
|
// 获取参考系统信息
|
||||||
|
info["reference_system"] = "WGS84"; // 需要根据实际实现调整
|
||||||
|
|
||||||
|
// 获取椭球半径信息
|
||||||
|
info["equator_radius"] = 6378137.0; // WGS84默认值,需要根据实际实现调整
|
||||||
|
info["pole_radius"] = 6356752.314245; // WGS84默认值,需要根据实际实现调整
|
||||||
|
|
||||||
|
// 获取二十面体方向
|
||||||
|
info["icosahedron_longitude"] = 0.0; // 需要根据实际实现调整
|
||||||
|
info["icosahedron_latitude"] = 0.0; // 需要根据实际实现调整
|
||||||
|
|
||||||
|
// 添加版本信息
|
||||||
|
info["version"] = "1.0.0";
|
||||||
|
info["author"] = "STT Development Team";
|
||||||
|
info["email"] = "stt@example.com";
|
||||||
|
info["license"] = "MIT";
|
||||||
|
info["url"] = "https://github.com/stt/stt-generator";
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行主要例程 - 完整版本
|
||||||
|
int run_full(const py::dict& params) {
|
||||||
|
char options[14][1024];
|
||||||
|
|
||||||
|
// 初始化所有选项为"NULL"
|
||||||
|
for (int i = 0; i < 14; i++) {
|
||||||
|
strcpy(options[i], "NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理参数字典
|
||||||
|
if (params.contains("output_msh")) {
|
||||||
|
std::string msh_file = py::str(params["output_msh"]);
|
||||||
|
strncpy(options[3], msh_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("output_vertex")) {
|
||||||
|
std::string vertex_file = py::str(params["output_vertex"]);
|
||||||
|
strncpy(options[4], vertex_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("output_triangle_center")) {
|
||||||
|
std::string tri_file = py::str(params["output_triangle_center"]);
|
||||||
|
strncpy(options[5], tri_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("output_neighbor")) {
|
||||||
|
std::string neighbor_file = py::str(params["output_neighbor"]);
|
||||||
|
strncpy(options[6], neighbor_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("control_points")) {
|
||||||
|
std::string points_file = py::str(params["control_points"]);
|
||||||
|
strncpy(options[7], points_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("control_lines")) {
|
||||||
|
std::string lines_file = py::str(params["control_lines"]);
|
||||||
|
strncpy(options[8], lines_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("control_polygons")) {
|
||||||
|
std::string poly_file = py::str(params["control_polygons"]);
|
||||||
|
strncpy(options[9], poly_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("control_circles")) {
|
||||||
|
std::string circles_file = py::str(params["control_circles"]);
|
||||||
|
strncpy(options[10], circles_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("outline_shape")) {
|
||||||
|
std::string outline_file = py::str(params["outline_shape"]);
|
||||||
|
strncpy(options[11], outline_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("hole_shape")) {
|
||||||
|
std::string hole_file = py::str(params["hole_shape"]);
|
||||||
|
strncpy(options[12], hole_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.contains("topography")) {
|
||||||
|
std::string topo_file = py::str(params["topography"]);
|
||||||
|
strncpy(options[13], topo_file.c_str(), 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator.Routine(options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 便利函数 - 快速创建STT
|
||||||
|
py::dict create_stt(int min_depth, int max_depth,
|
||||||
|
const std::string& reference_system = "WGS84",
|
||||||
|
const std::string& output_file = "output.msh") {
|
||||||
|
|
||||||
|
PySttGenerator gen;
|
||||||
|
gen.set_tree_depth(min_depth, max_depth);
|
||||||
|
gen.set_reference_system(reference_system);
|
||||||
|
|
||||||
|
int result = gen.run(output_file);
|
||||||
|
|
||||||
|
py::dict info;
|
||||||
|
info["min_depth"] = min_depth;
|
||||||
|
info["max_depth"] = max_depth;
|
||||||
|
info["reference_system"] = reference_system;
|
||||||
|
info["output_file"] = output_file;
|
||||||
|
info["success"] = (result == 0);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模块级别的get_info函数
|
||||||
|
py::dict module_get_info() {
|
||||||
|
py::dict info;
|
||||||
|
|
||||||
|
// 添加模块信息
|
||||||
|
info["version"] = "1.0.0";
|
||||||
|
info["author"] = "STT Development Team";
|
||||||
|
info["email"] = "stt@example.com";
|
||||||
|
info["license"] = "MIT";
|
||||||
|
info["url"] = "https://github.com/stt/stt-generator";
|
||||||
|
info["description"] = "Spherical Triangular Tessellation (STT) generator";
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
PYBIND11_MODULE(pystt, m) {
|
||||||
|
m.doc() = R"pbdoc(
|
||||||
|
STT Python Binding
|
||||||
|
------------------
|
||||||
|
A Python interface for the Spherical Triangular Tessellation (STT) generator.
|
||||||
|
|
||||||
|
This module provides access to the C++ STT library for generating
|
||||||
|
spherical triangular tessellations on various reference systems.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Support for multiple reference systems (WGS84, Earth, Moon, custom)
|
||||||
|
- Configurable tree depth for mesh refinement
|
||||||
|
- Progress callback support for Jupyter notebooks
|
||||||
|
- Output to various file formats (.msh, .txt)
|
||||||
|
)pbdoc";
|
||||||
|
|
||||||
|
// 进度条回调函数类型定义
|
||||||
|
using ProgressCallback = std::function<void(const std::string&, double)>;
|
||||||
|
|
||||||
|
// 主类绑定
|
||||||
|
py::class_<PySttGenerator>(m, "SttGenerator")
|
||||||
|
.def(py::init<>(), "Create a new STT generator instance")
|
||||||
|
|
||||||
|
.def("set_tree_depth", &PySttGenerator::set_tree_depth,
|
||||||
|
"Set the minimum and maximum tree depth",
|
||||||
|
py::arg("min_depth"), py::arg("max_depth"))
|
||||||
|
|
||||||
|
.def("set_pole_equator_radius", &PySttGenerator::set_pole_equator_radius,
|
||||||
|
"Set the pole and equator radius for the reference system",
|
||||||
|
py::arg("equator_radius"), py::arg("pole_radius"))
|
||||||
|
|
||||||
|
.def("set_icosahedron_orient", &PySttGenerator::set_icosahedron_orient,
|
||||||
|
"Set the orientation of the icosahedron top vertex",
|
||||||
|
py::arg("longitude"), py::arg("latitude"))
|
||||||
|
|
||||||
|
.def("set_reference_system", &PySttGenerator::set_reference_system,
|
||||||
|
"Set the reference system (WGS84, Earth, Moon, or custom)",
|
||||||
|
py::arg("ref_system"))
|
||||||
|
|
||||||
|
.def("set_progress_callback", &PySttGenerator::set_progress_callback,
|
||||||
|
"Set a progress callback function for Jupyter notebook compatibility\n"
|
||||||
|
"Callback function should accept (description, percentage) parameters",
|
||||||
|
py::arg("callback"))
|
||||||
|
|
||||||
|
.def("clear_progress_callback", &PySttGenerator::clear_progress_callback,
|
||||||
|
"Clear the progress callback function")
|
||||||
|
|
||||||
|
.def("run", &PySttGenerator::run,
|
||||||
|
"Run the STT generation with basic parameters",
|
||||||
|
py::arg("output_msh_file") = "")
|
||||||
|
|
||||||
|
.def("run_full", &PySttGenerator::run_full,
|
||||||
|
"Run the STT generation with full parameters",
|
||||||
|
py::arg("params"))
|
||||||
|
|
||||||
|
.def("get_info", &PySttGenerator::get_info,
|
||||||
|
"Get information about the current STT generator configuration");
|
||||||
|
|
||||||
|
// 便利函数
|
||||||
|
m.def("create_stt", &create_stt,
|
||||||
|
"Create STT with simplified interface",
|
||||||
|
py::arg("min_depth"), py::arg("max_depth"),
|
||||||
|
py::arg("reference_system") = "WGS84",
|
||||||
|
py::arg("output_file") = "output.msh");
|
||||||
|
|
||||||
|
// 模块级别的get_info函数
|
||||||
|
m.def("get_info", &module_get_info,
|
||||||
|
"Get module information including version, author, and contact details");
|
||||||
|
|
||||||
|
// 进度回调函数 - 使用py::function
|
||||||
|
m.def("create_simple_callback", [](const std::string& description) {
|
||||||
|
return py::cpp_function([description](const std::string& desc, double percentage) {
|
||||||
|
std::cout << description << " - " << desc << ": " << percentage << "%" << std::endl;
|
||||||
|
});
|
||||||
|
}, "Create a simple progress callback function");
|
||||||
|
|
||||||
|
m.def("create_tqdm_callback", [](const std::string& description) {
|
||||||
|
if (!has_tqdm()) {
|
||||||
|
throw std::runtime_error("tqdm is not available");
|
||||||
|
}
|
||||||
|
// 返回一个简单的回调函数,因为tqdm需要更复杂的设置
|
||||||
|
return py::cpp_function([description](const std::string& desc, double percentage) {
|
||||||
|
std::cout << description << " - " << desc << ": " << percentage << "%" << std::endl;
|
||||||
|
});
|
||||||
|
}, "Create a tqdm-based progress callback function");
|
||||||
|
|
||||||
|
m.def("create_progress_callback", [](const std::string& description) {
|
||||||
|
// 自动选择最适合的进度回调 - 总是返回简单版本
|
||||||
|
return py::cpp_function([description](const std::string& desc, double percentage) {
|
||||||
|
std::cout << description << " - " << desc << ": " << percentage << "%" << std::endl;
|
||||||
|
});
|
||||||
|
}, "Create the best available progress callback function");
|
||||||
|
|
||||||
|
// 参考系统常量
|
||||||
|
m.attr("WGS84") = "WGS84";
|
||||||
|
m.attr("EARTH") = "Earth";
|
||||||
|
m.attr("MOON") = "Moon";
|
||||||
|
|
||||||
|
// 检查tqdm是否可用
|
||||||
|
m.attr("HAS_TQDM") = has_tqdm();
|
||||||
|
|
||||||
|
// 版本信息
|
||||||
|
#ifdef VERSION_INFO
|
||||||
|
m.attr("__version__") = VERSION_INFO;
|
||||||
|
#else
|
||||||
|
m.attr("__version__") = "dev";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
58
pyproject.toml
Normal file
58
pyproject.toml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64", "wheel", "pybind11>=2.6.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pystt"
|
||||||
|
version = "1.4.1"
|
||||||
|
description = "Python binding for Spherical Triangular Tessellation (STT) generator"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.6"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "STT Development Team", email = "yizhang-geo@zju.edu.cn"},
|
||||||
|
]
|
||||||
|
keywords = ["spherical", "triangular", "tessellation", "mesh", "generation", "geography"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Science/Research",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: C++",
|
||||||
|
"Topic :: Scientific/Engineering :: GIS",
|
||||||
|
"Topic :: Scientific/Engineering :: Mathematics",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"pybind11>=2.6.0",
|
||||||
|
"numpy>=1.19.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=6.0",
|
||||||
|
"pytest-cov",
|
||||||
|
"black",
|
||||||
|
"flake8",
|
||||||
|
]
|
||||||
|
progress = [
|
||||||
|
"tqdm>=4.50.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/your-repo/stt"
|
||||||
|
"Bug Reports" = "https://github.com/your-repo/stt/issues"
|
||||||
|
Source = "https://github.com/your-repo/stt"
|
||||||
|
Documentation = "https://stt.readthedocs.io/"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
zip-safe = false
|
||||||
|
|
||||||
|
[tool.setuptools.exclude-package-data]
|
||||||
|
"*" = ["*.cpp", "*.cc", "*.h", "*.hpp", "CMakeLists.txt", "Makefile"]
|
||||||
179
setup.py
Normal file
179
setup.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Try to import pybind11, but don't fail if it's not available during build setup
|
||||||
|
try:
|
||||||
|
import pybind11
|
||||||
|
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
||||||
|
PYBIND11_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
PYBIND11_AVAILABLE = False
|
||||||
|
# Define dummy classes for when pybind11 is not available
|
||||||
|
class Pybind11Extension:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
class build_ext:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from setuptools import setup, Extension
|
||||||
|
|
||||||
|
# 获取当前目录
|
||||||
|
__dir__ = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# 获取所有源文件
|
||||||
|
def get_source_files():
|
||||||
|
"""获取所有STT源文件"""
|
||||||
|
src_files = []
|
||||||
|
|
||||||
|
# 添加主源文件
|
||||||
|
src_dir = os.path.join(__dir__, 'src')
|
||||||
|
for file in os.listdir(src_dir):
|
||||||
|
if file.endswith('.cc'):
|
||||||
|
src_files.append(os.path.join('src', file))
|
||||||
|
|
||||||
|
# 添加绑定文件
|
||||||
|
src_files.append('pybind/stt_binding.cpp')
|
||||||
|
|
||||||
|
return src_files
|
||||||
|
|
||||||
|
# 获取包含目录
|
||||||
|
def get_include_dirs():
|
||||||
|
"""获取包含目录"""
|
||||||
|
includes = [
|
||||||
|
# 当前目录
|
||||||
|
__dir__,
|
||||||
|
# src目录
|
||||||
|
os.path.join(__dir__, 'src'),
|
||||||
|
# pybind目录
|
||||||
|
os.path.join(__dir__, 'pybind'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 只有在pybind11可用时才添加其包含目录
|
||||||
|
if PYBIND11_AVAILABLE:
|
||||||
|
includes.extend([
|
||||||
|
# pybind11包含目录
|
||||||
|
pybind11.get_include(),
|
||||||
|
# Python包含目录
|
||||||
|
pybind11.get_include(True)
|
||||||
|
])
|
||||||
|
|
||||||
|
return includes
|
||||||
|
|
||||||
|
# 定义扩展模块
|
||||||
|
if PYBIND11_AVAILABLE:
|
||||||
|
ext_modules = [
|
||||||
|
Pybind11Extension(
|
||||||
|
"pystt",
|
||||||
|
# 源文件列表
|
||||||
|
get_source_files(),
|
||||||
|
# 包含目录
|
||||||
|
include_dirs=get_include_dirs(),
|
||||||
|
# 编译选项
|
||||||
|
extra_compile_args=[
|
||||||
|
'-O3', # 优化级别
|
||||||
|
'-std=c++11', # C++11标准
|
||||||
|
'-fPIC', # 位置无关代码
|
||||||
|
'-DVERSION_INFO="1.4.1"', # 版本信息
|
||||||
|
'-DPYTHON_BINDING' # Python绑定模式
|
||||||
|
],
|
||||||
|
# 链接选项
|
||||||
|
extra_link_args=[],
|
||||||
|
# 定义宏
|
||||||
|
define_macros=[
|
||||||
|
('VERSION_INFO', '"1.4.1"'),
|
||||||
|
('PYTHON_BINDING', '1'),
|
||||||
|
],
|
||||||
|
# 语言标准
|
||||||
|
cxx_std=11,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# 当pybind11不可用时,使用空列表
|
||||||
|
ext_modules = []
|
||||||
|
|
||||||
|
# 读取README文件
|
||||||
|
def read_readme():
|
||||||
|
"""读取README文件"""
|
||||||
|
readme_path = os.path.join(__dir__, 'README.md')
|
||||||
|
if os.path.exists(readme_path):
|
||||||
|
with open(readme_path, 'r', encoding='utf-8') as f:
|
||||||
|
return f.read()
|
||||||
|
return "STT Python Binding - Spherical Triangular Tessellation Generator"
|
||||||
|
|
||||||
|
# 设置包信息
|
||||||
|
setup(
|
||||||
|
name='pystt',
|
||||||
|
version='1.4.1',
|
||||||
|
author='STT Development Team',
|
||||||
|
author_email='yizhang-geo@zju.edu.cn',
|
||||||
|
description='Python binding for Spherical Triangular Tessellation (STT) generator',
|
||||||
|
long_description=read_readme(),
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://github.com/your-repo/stt',
|
||||||
|
|
||||||
|
# 扩展模块
|
||||||
|
ext_modules=ext_modules,
|
||||||
|
|
||||||
|
# 构建命令
|
||||||
|
cmdclass={"build_ext": build_ext} if PYBIND11_AVAILABLE else {},
|
||||||
|
|
||||||
|
# 依赖
|
||||||
|
install_requires=[
|
||||||
|
'pybind11>=2.6.0',
|
||||||
|
'numpy>=1.19.0',
|
||||||
|
],
|
||||||
|
|
||||||
|
# 可选依赖
|
||||||
|
extras_require={
|
||||||
|
'dev': [
|
||||||
|
'pytest>=6.0',
|
||||||
|
'pytest-cov',
|
||||||
|
'black',
|
||||||
|
'flake8',
|
||||||
|
],
|
||||||
|
'progress': [
|
||||||
|
'tqdm>=4.50.0',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# Python版本要求
|
||||||
|
python_requires='>=3.6',
|
||||||
|
|
||||||
|
# 分类
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Intended Audience :: Science/Research',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
|
'Programming Language :: Python :: 3.11',
|
||||||
|
'Programming Language :: C++',
|
||||||
|
'Topic :: Scientific/Engineering :: GIS',
|
||||||
|
'Topic :: Scientific/Engineering :: Mathematics',
|
||||||
|
],
|
||||||
|
|
||||||
|
# 关键词
|
||||||
|
keywords='spherical triangular tessellation mesh generation geography',
|
||||||
|
|
||||||
|
# 项目URL
|
||||||
|
project_urls={
|
||||||
|
'Bug Reports': 'https://github.com/your-repo/stt/issues',
|
||||||
|
'Source': 'https://github.com/your-repo/stt',
|
||||||
|
'Documentation': 'https://stt.readthedocs.io/',
|
||||||
|
},
|
||||||
|
|
||||||
|
# 包含包数据
|
||||||
|
include_package_data=True,
|
||||||
|
|
||||||
|
# 排除文件
|
||||||
|
exclude_package_data={
|
||||||
|
'': ['*.cpp', '*.cc', '*.h', '*.hpp', 'CMakeLists.txt', 'Makefile'],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ZIP安全
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user