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)
|