403 lines
20 KiB
Python
403 lines
20 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
"""
|
|||
|
Scene地图到SMAP地图转换器
|
|||
|
根据scene地图中的points信息,更新smap地图中对应的坐标位置
|
|||
|
"""
|
|||
|
|
|||
|
import json
|
|||
|
import os
|
|||
|
from typing import Dict, List, Optional, Tuple
|
|||
|
|
|||
|
|
|||
|
class SceneToSmapConverter:
|
|||
|
"""Scene到SMAP转换器"""
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
self.scene_data = None
|
|||
|
self.smap_data = None
|
|||
|
self.point_mapping = {} # scene point name -> scene point data
|
|||
|
self.route_mapping = {} # scene route (from,to) -> scene route data
|
|||
|
self.conversion_log = [] # 转换日志
|
|||
|
|
|||
|
def load_scene_file(self, scene_file_path: str) -> bool:
|
|||
|
"""加载Scene文件"""
|
|||
|
try:
|
|||
|
with open(scene_file_path, 'r', encoding='utf-8') as f:
|
|||
|
self.scene_data = json.load(f)
|
|||
|
|
|||
|
# 建立点位映射
|
|||
|
if 'points' in self.scene_data:
|
|||
|
for point in self.scene_data['points']:
|
|||
|
if 'name' in point:
|
|||
|
self.point_mapping[point['name']] = point
|
|||
|
|
|||
|
# 建立路径映射:基于起点和终点名称
|
|||
|
if 'routes' in self.scene_data:
|
|||
|
for route in self.scene_data['routes']:
|
|||
|
if 'from' in route and 'to' in route:
|
|||
|
# 先根据ID找到对应的点名称
|
|||
|
from_name = None
|
|||
|
to_name = None
|
|||
|
for point in self.scene_data.get('points', []):
|
|||
|
if point.get('id') == route['from']:
|
|||
|
from_name = point.get('name')
|
|||
|
if point.get('id') == route['to']:
|
|||
|
to_name = point.get('name')
|
|||
|
|
|||
|
if from_name and to_name:
|
|||
|
route_key = f"{from_name}-{to_name}"
|
|||
|
self.route_mapping[route_key] = route
|
|||
|
|
|||
|
self.conversion_log.append(f"✓ 成功加载Scene文件,共 {len(self.point_mapping)} 个点位,{len(self.route_mapping)} 条路径")
|
|||
|
return True
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.conversion_log.append(f"✗ 加载Scene文件失败: {str(e)}")
|
|||
|
return False
|
|||
|
|
|||
|
def load_smap_file(self, smap_file_path: str) -> bool:
|
|||
|
"""加载SMAP文件"""
|
|||
|
try:
|
|||
|
with open(smap_file_path, 'r', encoding='utf-8') as f:
|
|||
|
self.smap_data = json.load(f)
|
|||
|
|
|||
|
self.conversion_log.append("✓ 成功加载SMAP文件")
|
|||
|
return True
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.conversion_log.append(f"✗ 加载SMAP文件失败: {str(e)}")
|
|||
|
return False
|
|||
|
|
|||
|
def update_advanced_point_list(self) -> int:
|
|||
|
"""更新SMAP中的advancedPointList"""
|
|||
|
if not self.smap_data or 'advancedPointList' not in self.smap_data:
|
|||
|
self.conversion_log.append("✗ SMAP文件中未找到advancedPointList字段")
|
|||
|
return 0
|
|||
|
|
|||
|
updated_count = 0
|
|||
|
|
|||
|
for advanced_point in self.smap_data['advancedPointList']:
|
|||
|
if 'instanceName' in advanced_point and advanced_point['instanceName'] in self.point_mapping:
|
|||
|
scene_point = self.point_mapping[advanced_point['instanceName']]
|
|||
|
|
|||
|
# 更新pos坐标
|
|||
|
if 'pos' in advanced_point and 'x' in scene_point and 'y' in scene_point:
|
|||
|
old_pos = f"({advanced_point['pos']['x']}, {advanced_point['pos']['y']})"
|
|||
|
advanced_point['pos']['x'] = scene_point['x']
|
|||
|
advanced_point['pos']['y'] = scene_point['y']
|
|||
|
new_pos = f"({scene_point['x']}, {scene_point['y']})"
|
|||
|
|
|||
|
self.conversion_log.append(f" - 更新 {advanced_point['instanceName']}: {old_pos} -> {new_pos}")
|
|||
|
updated_count += 1
|
|||
|
|
|||
|
self.conversion_log.append(f"✓ advancedPointList更新完成,共更新 {updated_count} 个点位")
|
|||
|
return updated_count
|
|||
|
|
|||
|
def update_advanced_curve_list(self) -> int:
|
|||
|
"""更新SMAP中路径的控制点controlPos"""
|
|||
|
if not self.smap_data or 'advancedCurveList' not in self.smap_data:
|
|||
|
self.conversion_log.append("✗ SMAP文件中未找到advancedCurveList字段")
|
|||
|
return 0
|
|||
|
|
|||
|
updated_count = 0
|
|||
|
|
|||
|
# 路径类型映射表
|
|||
|
path_type_map = {
|
|||
|
"StraightPath": "line",
|
|||
|
"BezierPath": "BezierPath",
|
|||
|
"ArcPath": "bezier2",
|
|||
|
"DegenerateBezier": "bezier3",
|
|||
|
"NURBS6": "NURBS6"
|
|||
|
}
|
|||
|
|
|||
|
for curve in self.smap_data['advancedCurveList']:
|
|||
|
curve_updated = False
|
|||
|
|
|||
|
# 更新startPos
|
|||
|
if ('startPos' in curve and 'instanceName' in curve['startPos'] and
|
|||
|
curve['startPos']['instanceName'] in self.point_mapping):
|
|||
|
|
|||
|
scene_point = self.point_mapping[curve['startPos']['instanceName']]
|
|||
|
if 'pos' in curve['startPos'] and 'x' in scene_point and 'y' in scene_point:
|
|||
|
curve['startPos']['pos']['x'] = scene_point['x']
|
|||
|
curve['startPos']['pos']['y'] = scene_point['y']
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
# 更新endPos
|
|||
|
if ('endPos' in curve and 'instanceName' in curve['endPos'] and
|
|||
|
curve['endPos']['instanceName'] in self.point_mapping):
|
|||
|
|
|||
|
scene_point = self.point_mapping[curve['endPos']['instanceName']]
|
|||
|
if 'pos' in curve['endPos'] and 'x' in scene_point and 'y' in scene_point:
|
|||
|
curve['endPos']['pos']['x'] = scene_point['x']
|
|||
|
curve['endPos']['pos']['y'] = scene_point['y']
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
# 根据路径类型更新控制点
|
|||
|
if ('startPos' in curve and 'endPos' in curve and
|
|||
|
'pos' in curve['startPos'] and 'pos' in curve['endPos']):
|
|||
|
|
|||
|
# 尝试从Scene数据中获取对应的路径信息
|
|||
|
start_name = curve['startPos'].get('instanceName', '')
|
|||
|
end_name = curve['endPos'].get('instanceName', '')
|
|||
|
route_key = f"{start_name}-{end_name}"
|
|||
|
scene_route = self.route_mapping.get(route_key)
|
|||
|
|
|||
|
# 获取路径类型
|
|||
|
class_name = curve.get('className', '')
|
|||
|
curve_type = curve.get('type', '')
|
|||
|
mapped_type = path_type_map.get(class_name, path_type_map.get(curve_type, "bezier3"))
|
|||
|
|
|||
|
start_x = curve['startPos']['pos']['x']
|
|||
|
start_y = curve['startPos']['pos']['y']
|
|||
|
end_x = curve['endPos']['pos']['x']
|
|||
|
end_y = curve['endPos']['pos']['y']
|
|||
|
|
|||
|
# 根据路径类型更新不同的控制点
|
|||
|
if mapped_type == "line":
|
|||
|
# 直线不需要控制点,但如果存在则保持原值或删除
|
|||
|
pass
|
|||
|
elif mapped_type == "bezier2":
|
|||
|
# bezier2只需要controlPos1
|
|||
|
if 'controlPos1' in curve:
|
|||
|
if scene_route and 'c1' in scene_route:
|
|||
|
# 使用Scene中的控制点信息
|
|||
|
curve['controlPos1']['x'] = scene_route['c1']['x']
|
|||
|
curve['controlPos1']['y'] = scene_route['c1']['y']
|
|||
|
else:
|
|||
|
# 使用中点作为控制点
|
|||
|
curve['controlPos1']['x'] = (start_x + end_x) / 2
|
|||
|
curve['controlPos1']['y'] = (start_y + end_y) / 2
|
|||
|
curve_updated = True
|
|||
|
elif mapped_type in ["BezierPath", "bezier3"]:
|
|||
|
# BezierPath和bezier3需要controlPos1和controlPos2
|
|||
|
if 'controlPos1' in curve:
|
|||
|
if scene_route and 'c1' in scene_route:
|
|||
|
curve['controlPos1']['x'] = scene_route['c1']['x']
|
|||
|
curve['controlPos1']['y'] = scene_route['c1']['y']
|
|||
|
else:
|
|||
|
curve['controlPos1']['x'] = start_x + (end_x - start_x) / 3
|
|||
|
curve['controlPos1']['y'] = start_y + (end_y - start_y) / 3
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
if 'controlPos2' in curve:
|
|||
|
if scene_route and 'c2' in scene_route:
|
|||
|
curve['controlPos2']['x'] = scene_route['c2']['x']
|
|||
|
curve['controlPos2']['y'] = scene_route['c2']['y']
|
|||
|
else:
|
|||
|
curve['controlPos2']['x'] = start_x + 2 * (end_x - start_x) / 3
|
|||
|
curve['controlPos2']['y'] = start_y + 2 * (end_y - start_y) / 3
|
|||
|
curve_updated = True
|
|||
|
elif mapped_type == "NURBS6":
|
|||
|
# NURBS6需要controlPos1, controlPos2, controlPos3, controlPos4
|
|||
|
if 'controlPos1' in curve:
|
|||
|
if scene_route and 'c1' in scene_route:
|
|||
|
curve['controlPos1']['x'] = scene_route['c1']['x']
|
|||
|
curve['controlPos1']['y'] = scene_route['c1']['y']
|
|||
|
if 'z' in scene_route['c1']:
|
|||
|
curve['controlPos1']['z'] = scene_route['c1']['z']
|
|||
|
else:
|
|||
|
curve['controlPos1']['x'] = start_x + (end_x - start_x) / 5
|
|||
|
curve['controlPos1']['y'] = start_y + (end_y - start_y) / 5
|
|||
|
if 'z' in curve['controlPos1']:
|
|||
|
start_z = curve['startPos']['pos'].get('z', 0)
|
|||
|
end_z = curve['endPos']['pos'].get('z', 0)
|
|||
|
curve['controlPos1']['z'] = start_z + (end_z - start_z) / 5
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
if 'controlPos2' in curve:
|
|||
|
if scene_route and 'c2' in scene_route:
|
|||
|
curve['controlPos2']['x'] = scene_route['c2']['x']
|
|||
|
curve['controlPos2']['y'] = scene_route['c2']['y']
|
|||
|
if 'z' in scene_route['c2']:
|
|||
|
curve['controlPos2']['z'] = scene_route['c2']['z']
|
|||
|
else:
|
|||
|
curve['controlPos2']['x'] = start_x + 2 * (end_x - start_x) / 5
|
|||
|
curve['controlPos2']['y'] = start_y + 2 * (end_y - start_y) / 5
|
|||
|
if 'z' in curve['controlPos2']:
|
|||
|
start_z = curve['startPos']['pos'].get('z', 0)
|
|||
|
end_z = curve['endPos']['pos'].get('z', 0)
|
|||
|
curve['controlPos2']['z'] = start_z + 2 * (end_z - start_z) / 5
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
if 'controlPos3' in curve:
|
|||
|
if scene_route and 'c3' in scene_route:
|
|||
|
curve['controlPos3']['x'] = scene_route['c3']['x']
|
|||
|
curve['controlPos3']['y'] = scene_route['c3']['y']
|
|||
|
if 'z' in scene_route['c3']:
|
|||
|
curve['controlPos3']['z'] = scene_route['c3']['z']
|
|||
|
else:
|
|||
|
curve['controlPos3']['x'] = start_x + 3 * (end_x - start_x) / 5
|
|||
|
curve['controlPos3']['y'] = start_y + 3 * (end_y - start_y) / 5
|
|||
|
if 'z' in curve['controlPos3']:
|
|||
|
start_z = curve['startPos']['pos'].get('z', 0)
|
|||
|
end_z = curve['endPos']['pos'].get('z', 0)
|
|||
|
curve['controlPos3']['z'] = start_z + 3 * (end_z - start_z) / 5
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
if 'controlPos4' in curve:
|
|||
|
if scene_route and 'c4' in scene_route:
|
|||
|
curve['controlPos4']['x'] = scene_route['c4']['x']
|
|||
|
curve['controlPos4']['y'] = scene_route['c4']['y']
|
|||
|
if 'z' in scene_route['c4']:
|
|||
|
curve['controlPos4']['z'] = scene_route['c4']['z']
|
|||
|
else:
|
|||
|
curve['controlPos4']['x'] = start_x + 4 * (end_x - start_x) / 5
|
|||
|
curve['controlPos4']['y'] = start_y + 4 * (end_y - start_y) / 5
|
|||
|
if 'z' in curve['controlPos4']:
|
|||
|
start_z = curve['startPos']['pos'].get('z', 0)
|
|||
|
end_z = curve['endPos']['pos'].get('z', 0)
|
|||
|
curve['controlPos4']['z'] = start_z + 4 * (end_z - start_z) / 5
|
|||
|
curve_updated = True
|
|||
|
|
|||
|
if curve_updated:
|
|||
|
updated_count += 1
|
|||
|
|
|||
|
# 统计不同路径类型的数量
|
|||
|
type_counts = {}
|
|||
|
for curve in self.smap_data['advancedCurveList']:
|
|||
|
class_name = curve.get('className', '')
|
|||
|
curve_type = curve.get('type', '')
|
|||
|
mapped_type = path_type_map.get(class_name, path_type_map.get(curve_type, "bezier3"))
|
|||
|
type_counts[mapped_type] = type_counts.get(mapped_type, 0) + 1
|
|||
|
|
|||
|
type_info = ", ".join([f"{t}: {c}" for t, c in type_counts.items()])
|
|||
|
self.conversion_log.append(f"✓ advancedCurveList更新完成,共更新 {updated_count} 条路径")
|
|||
|
self.conversion_log.append(f" 路径类型分布: {type_info}")
|
|||
|
return updated_count
|
|||
|
|
|||
|
def update_bin_locations_list(self) -> int:
|
|||
|
"""更新SMAP中binLocationsList中点位的stringValue参数"""
|
|||
|
if not self.smap_data or 'binLocationsList' not in self.smap_data:
|
|||
|
self.conversion_log.append("✗ SMAP文件中未找到binLocationsList字段")
|
|||
|
return 0
|
|||
|
|
|||
|
updated_count = 0
|
|||
|
|
|||
|
for bin_location_group in self.smap_data['binLocationsList']:
|
|||
|
if 'binLocationList' not in bin_location_group:
|
|||
|
continue
|
|||
|
|
|||
|
for bin_location in bin_location_group['binLocationList']:
|
|||
|
if ('pointName' in bin_location and
|
|||
|
bin_location['pointName'] in self.point_mapping):
|
|||
|
|
|||
|
scene_point = self.point_mapping[bin_location['pointName']]
|
|||
|
|
|||
|
# 更新pos坐标
|
|||
|
if 'pos' in bin_location and 'x' in scene_point and 'y' in scene_point:
|
|||
|
bin_location['pos']['x'] = scene_point['x']
|
|||
|
bin_location['pos']['y'] = scene_point['y']
|
|||
|
|
|||
|
# 更新property中的points字段(stringValue)
|
|||
|
if 'property' in bin_location:
|
|||
|
for prop in bin_location['property']:
|
|||
|
if (prop.get('key') == 'points' and
|
|||
|
prop.get('type') == 'json' and
|
|||
|
'stringValue' in prop):
|
|||
|
|
|||
|
try:
|
|||
|
# 解析原来的points数据
|
|||
|
points_data = json.loads(prop['stringValue'])
|
|||
|
|
|||
|
if isinstance(points_data, list) and len(points_data) > 0:
|
|||
|
# 计算偏移量(假设第一个点是基准点)
|
|||
|
if len(points_data) > 0 and 'x' in points_data[0] and 'y' in points_data[0]:
|
|||
|
# 计算中心点偏移
|
|||
|
center_x = sum(p['x'] for p in points_data) / len(points_data)
|
|||
|
center_y = sum(p['y'] for p in points_data) / len(points_data)
|
|||
|
|
|||
|
offset_x = scene_point['x'] - center_x
|
|||
|
offset_y = scene_point['y'] - center_y
|
|||
|
|
|||
|
# 更新所有点的坐标
|
|||
|
for point in points_data:
|
|||
|
point['x'] += offset_x
|
|||
|
point['y'] += offset_y
|
|||
|
|
|||
|
# 更新stringValue
|
|||
|
prop['stringValue'] = json.dumps(points_data)
|
|||
|
updated_count += 1
|
|||
|
|
|||
|
self.conversion_log.append(f" - 更新 {bin_location['pointName']} 的区域坐标")
|
|||
|
|
|||
|
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|||
|
self.conversion_log.append(f" - 解析 {bin_location['pointName']} 的points数据失败: {str(e)}")
|
|||
|
|
|||
|
self.conversion_log.append(f"✓ binLocationsList更新完成,共更新 {updated_count} 个点位区域")
|
|||
|
return updated_count
|
|||
|
|
|||
|
def convert(self, scene_file_path: str, smap_file_path: str, output_path: str = None) -> dict:
|
|||
|
"""执行转换"""
|
|||
|
self.conversion_log = []
|
|||
|
|
|||
|
# 加载文件
|
|||
|
if not self.load_scene_file(scene_file_path):
|
|||
|
return {"success": False, "log": self.conversion_log}
|
|||
|
|
|||
|
if not self.load_smap_file(smap_file_path):
|
|||
|
return {"success": False, "log": self.conversion_log}
|
|||
|
|
|||
|
# 执行转换
|
|||
|
total_updated = 0
|
|||
|
total_updated += self.update_advanced_point_list()
|
|||
|
total_updated += self.update_advanced_curve_list()
|
|||
|
total_updated += self.update_bin_locations_list()
|
|||
|
|
|||
|
# 保存结果
|
|||
|
if output_path is None:
|
|||
|
base_name = os.path.splitext(os.path.basename(smap_file_path))[0]
|
|||
|
output_path = f"{base_name}_updated.smap"
|
|||
|
|
|||
|
try:
|
|||
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|||
|
json.dump(self.smap_data, f, ensure_ascii=False, indent=2)
|
|||
|
|
|||
|
self.conversion_log.append(f"✓ 转换完成,共更新 {total_updated} 项")
|
|||
|
self.conversion_log.append(f"✓ 输出文件: {output_path}")
|
|||
|
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"output_file": output_path,
|
|||
|
"updated_count": total_updated,
|
|||
|
"log": self.conversion_log,
|
|||
|
"data": self.smap_data
|
|||
|
}
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.conversion_log.append(f"✗ 保存文件失败: {str(e)}")
|
|||
|
return {"success": False, "log": self.conversion_log}
|
|||
|
|
|||
|
|
|||
|
def convert_scene_to_smap(scene_file_path: str, smap_file_path: str, output_path: str = None) -> dict:
|
|||
|
"""Scene到SMAP转换的主入口函数"""
|
|||
|
converter = SceneToSmapConverter()
|
|||
|
return converter.convert(scene_file_path, smap_file_path, output_path)
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
# 测试转换
|
|||
|
import sys
|
|||
|
|
|||
|
if len(sys.argv) < 3:
|
|||
|
print("使用方法: python scene_to_smap_converter.py <scene_file> <smap_file> [output_file]")
|
|||
|
sys.exit(1)
|
|||
|
|
|||
|
scene_file = sys.argv[1]
|
|||
|
smap_file = sys.argv[2]
|
|||
|
output_file = sys.argv[3] if len(sys.argv) > 3 else None
|
|||
|
|
|||
|
result = convert_scene_to_smap(scene_file, smap_file, output_file)
|
|||
|
|
|||
|
print("\n=== 转换结果 ===")
|
|||
|
for log_entry in result.get('log', []):
|
|||
|
print(log_entry)
|
|||
|
|
|||
|
if result['success']:
|
|||
|
print(f"\n转换成功!输出文件: {result.get('output_file', 'N/A')}")
|
|||
|
else:
|
|||
|
print("\n转换失败!")
|
|||
|
sys.exit(1)
|