327 lines
15 KiB
Python
327 lines
15 KiB
Python
import json
|
||
import os
|
||
|
||
class SceneBasicConverter:
|
||
def save_bin_locations_list(self):
|
||
"""
|
||
如果smap文件中有binLocationsList,则单独保存为converted/binLocationsList.json。
|
||
"""
|
||
if hasattr(self, 'map_data') and 'binLocationsList' in self.map_data:
|
||
self.ensure_converted_dir()
|
||
filename = f"{self.converted_dir}/binLocationsList.json"
|
||
with open(filename, 'w', encoding='utf-8') as f:
|
||
json.dump({'binLocationsList': self.map_data['binLocationsList']}, f, indent=2, ensure_ascii=False)
|
||
print(f"已单独保存 binLocationsList 到: {filename}")
|
||
def __init__(self, scene_id, setting, ratio):
|
||
self.scene_data = {}
|
||
self.converted_dir = "converted"
|
||
self.scene_data['id'] = scene_id
|
||
self.scene_data['setting'] = setting
|
||
self.scene_data['ratio'] = ratio
|
||
|
||
def ensure_converted_dir(self):
|
||
if not os.path.exists(self.converted_dir):
|
||
os.makedirs(self.converted_dir)
|
||
print(f"创建转换结果目录: {self.converted_dir}")
|
||
|
||
def save_single_key_json(self, key_name, key_data):
|
||
self.ensure_converted_dir()
|
||
filename = f"{self.converted_dir}/{key_name}.json"
|
||
single_key_data = {key_name: key_data}
|
||
with open(filename, 'w', encoding='utf-8') as f:
|
||
json.dump(single_key_data, f, indent=2, ensure_ascii=False)
|
||
print(f"已保存 {key_name} 的独立JSON文件到: {filename}")
|
||
|
||
def load_source_files(self, smap_file, scene_file):
|
||
self.smap_file = smap_file
|
||
self.scene_file = scene_file
|
||
if os.path.exists(smap_file):
|
||
with open(smap_file, 'r', encoding='utf-8') as f:
|
||
self.map_data = json.load(f)
|
||
else:
|
||
print(f"警告: 地图文件 {smap_file} 不存在")
|
||
self.map_data = {}
|
||
if os.path.exists(scene_file):
|
||
with open(scene_file, 'r', encoding='utf-8') as f:
|
||
self.scene_source = json.load(f)
|
||
else:
|
||
print(f"警告: 场景文件 {scene_file} 不存在")
|
||
self.scene_source = {}
|
||
|
||
def extract_basic_info(self):
|
||
if 'header' in self.map_data:
|
||
header = self.map_data['header']
|
||
self.scene_data['name'] = header.get('mapName', '')
|
||
min_pos = header.get('minPos', {'x': 0, 'y': 0})
|
||
max_pos = header.get('maxPos', {'x': 0, 'y': 0})
|
||
width = max_pos['x'] - min_pos['x']
|
||
height = max_pos['y'] - min_pos['y']
|
||
self.scene_data['width'] = abs(width)
|
||
self.scene_data['height'] = abs(height)
|
||
else:
|
||
raise ValueError("无法计算地图尺寸:map_data中缺少header信息")
|
||
basic_info_data = {
|
||
'id': self.scene_data['id'],
|
||
'name': self.scene_data['name'],
|
||
'width': self.scene_data['width'],
|
||
'height': self.scene_data['height'],
|
||
'setting': self.scene_data['setting'],
|
||
'ratio': self.scene_data['ratio']
|
||
}
|
||
self.save_single_key_json('basic_info', basic_info_data)
|
||
|
||
def extract_coordinate_system(self):
|
||
if self.scene_source:
|
||
self.scene_data['scale'] = self.scene_source.get('scale', 0.6599999999999997)
|
||
self.scene_data['origin'] = self.scene_source.get('origin', {
|
||
'x': -3679.5080777864587,
|
||
'y': -3925.231448210223
|
||
})
|
||
else:
|
||
self.scene_data['scale'] = 0.6599999999999997
|
||
self.scene_data['origin'] = {
|
||
'x': -3679.5080777864587,
|
||
'y': -3925.231448210223
|
||
}
|
||
coordinate_data = {
|
||
'scale': self.scene_data['scale'],
|
||
'origin': self.scene_data['origin']
|
||
}
|
||
self.save_single_key_json('coordinate_system', coordinate_data)
|
||
|
||
def extract_robots(self):
|
||
# 无论源数据如何,robots 字段都输出空列表
|
||
self.scene_data['robots'] = []
|
||
self.save_single_key_json('robots', self.scene_data['robots'])
|
||
|
||
def extract_points(self):
|
||
if 'advancedPointList' in self.map_data:
|
||
self.scene_data['points'] = self.extract_advanced_points()
|
||
elif self.scene_source and 'points' in self.scene_source:
|
||
self.scene_data['points'] = self.scene_source['points']
|
||
else:
|
||
self.scene_data['points'] = []
|
||
self.save_single_key_json('points', self.scene_data['points'])
|
||
|
||
def extract_advanced_points(self):
|
||
points = []
|
||
if 'advancedPointList' in self.map_data:
|
||
advanced_points = self.map_data['advancedPointList']
|
||
for i, point in enumerate(advanced_points):
|
||
point_id = str(10000001 + i)
|
||
name = point.get('instanceName', f'Point{i+1}')
|
||
pos = point.get('pos', {})
|
||
x = pos.get('x', 0)
|
||
y = pos.get('y', 0)
|
||
className = point.get('className', 'ActionPoint')
|
||
point_type = self.get_point_type_by_class(className)
|
||
properties = []
|
||
if 'property' in point:
|
||
for prop in point['property']:
|
||
property_obj = {
|
||
"key": prop.get('key', ''),
|
||
"type": prop.get('type', ''),
|
||
"value": prop.get('value', ''),
|
||
"boolValue": prop.get('boolValue', False)
|
||
}
|
||
prop_type = prop.get('type', '')
|
||
if prop_type == 'bool':
|
||
property_obj["boolValue"] = prop.get('boolValue', False)
|
||
elif prop_type == 'string':
|
||
property_obj["stringValue"] = prop.get('stringValue', '')
|
||
elif prop_type == 'int32':
|
||
property_obj["int32Value"] = prop.get('int32Value', 0)
|
||
elif prop_type == 'float':
|
||
property_obj["floatValue"] = prop.get('floatValue', 0.0)
|
||
properties.append(property_obj)
|
||
point_obj = {
|
||
"id": point_id,
|
||
"name": name,
|
||
"x": x,
|
||
"y": y,
|
||
"type": point_type,
|
||
"config": {},
|
||
"properties": properties,
|
||
"associatedStorageLocations": [],
|
||
"robots": [],
|
||
"enabled": 1
|
||
}
|
||
points.append(point_obj)
|
||
return points
|
||
|
||
def get_point_type_by_class(self, className):
|
||
type_mapping = {
|
||
'LocationMark': 1,
|
||
'ParkPoint': 14,
|
||
'SwitchMap': 5,
|
||
'HomeRegion': 2,
|
||
'ActionPoint': 15,
|
||
'TransferLocation': 14,
|
||
'WorkingLocation': 16,
|
||
'ChargePoint': 13
|
||
}
|
||
if className not in type_mapping:
|
||
if className.startswith('LM'):
|
||
return 1
|
||
elif className.startswith('PP'):
|
||
return 14
|
||
elif className.startswith('SW'):
|
||
return 5
|
||
elif className.startswith('HR'):
|
||
return 2
|
||
elif className.startswith('AP'):
|
||
return 15
|
||
elif className.startswith('TL'):
|
||
return 14
|
||
elif className.startswith('WL'):
|
||
return 16
|
||
elif className.startswith('CP'):
|
||
return 13
|
||
else:
|
||
return 1
|
||
return type_mapping.get(className, 15)
|
||
|
||
def extract_routes(self):
|
||
self.scene_data['routes'] = self.extract_advanced_curves()
|
||
self.save_single_key_json('routes', self.scene_data['routes'])
|
||
|
||
def extract_advanced_curves(self):
|
||
routes = []
|
||
name_to_id_map = {}
|
||
points_file = f"{self.converted_dir}/points.json"
|
||
if os.path.exists(points_file):
|
||
try:
|
||
with open(points_file, 'r', encoding='utf-8') as f:
|
||
points_data = json.load(f)
|
||
if 'points' in points_data:
|
||
for point in points_data['points']:
|
||
name_to_id_map[point['name']] = point['id']
|
||
except Exception as e:
|
||
print(f"警告: 无法读取 {points_file} 文件: {e}")
|
||
else:
|
||
print(f"警告: 站点文件 {points_file} 不存在,无法建立映射关系")
|
||
curve_data = self.map_data
|
||
if 'advancedCurveList' in curve_data:
|
||
advanced_curves = curve_data['advancedCurveList']
|
||
print(f"找到 {len(advanced_curves)} 条路径数据")
|
||
# 路径类型映射表,优先用className判断
|
||
path_type_map = {
|
||
"StraightPath": "line",
|
||
"BezierPath": "BezierPath",
|
||
"ArcPath": "bezier2",
|
||
"DegenerateBezier": "bezier3",
|
||
"NURBS6": "NURBS6"
|
||
}
|
||
for i, curve in enumerate(advanced_curves):
|
||
route_id = str(100000 + i)
|
||
desc = curve.get('instanceName', f'Route{i+1}')
|
||
start_pos = curve.get('startPos', {})
|
||
end_pos = curve.get('endPos', {})
|
||
from_instance_name = start_pos.get('instanceName', '')
|
||
to_instance_name = end_pos.get('instanceName', '')
|
||
from_point = name_to_id_map.get(from_instance_name, from_instance_name)
|
||
to_point = name_to_id_map.get(to_instance_name, to_instance_name)
|
||
if from_instance_name not in name_to_id_map:
|
||
print(f"警告: 找不到起点 '{from_instance_name}' 的映射")
|
||
if to_instance_name not in name_to_id_map:
|
||
print(f"警告: 找不到终点 '{to_instance_name}' 的映射")
|
||
control_pos1 = curve.get('controlPos1', {})
|
||
control_pos2 = curve.get('controlPos2', {})
|
||
control_pos3 = curve.get('controlPos3', {})
|
||
control_pos4 = curve.get('controlPos4', {})
|
||
properties = []
|
||
if 'property' in curve:
|
||
for prop in curve['property']:
|
||
properties.append({
|
||
"key": prop.get('key', ''),
|
||
"type": prop.get('type', ''),
|
||
"value": prop.get('value', ''),
|
||
"int32Value": prop.get('int32Value', 0),
|
||
"boolValue": prop.get('boolValue', False)
|
||
})
|
||
# 优先用className判断路径类型
|
||
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"))
|
||
route_obj = {
|
||
"id": route_id,
|
||
"desc": desc,
|
||
"from": from_point,
|
||
"to": to_point,
|
||
"type": mapped_type,
|
||
"pass": 0,
|
||
"config": {},
|
||
"properties": properties
|
||
}
|
||
# 按类型添加controlPos字段,直线不添加c1/c2
|
||
if mapped_type == "BezierPath" or mapped_type == "bezier3":
|
||
route_obj["c1"] = {"x": control_pos1.get('x', 0), "y": control_pos1.get('y', 0)}
|
||
route_obj["c2"] = {"x": control_pos2.get('x', 0), "y": control_pos2.get('y', 0)}
|
||
elif mapped_type == "bezier2":
|
||
route_obj["c1"] = {"x": control_pos1.get('x', 0), "y": control_pos1.get('y', 0)}
|
||
elif mapped_type == "NURBS6":
|
||
route_obj["c1"] = {"x": control_pos1.get('x', 0), "y": control_pos1.get('y', 0), "z": control_pos1.get('z', 0)}
|
||
route_obj["c2"] = {"x": control_pos2.get('x', 0), "y": control_pos2.get('y', 0), "z": control_pos2.get('z', 0)}
|
||
route_obj["c3"] = {"x": control_pos3.get('x', 0), "y": control_pos3.get('y', 0), "z": control_pos3.get('z', 0)}
|
||
route_obj["c4"] = {"x": control_pos4.get('x', 0), "y": control_pos4.get('y', 0), "z": control_pos4.get('z', 0)}
|
||
# 直线(line)不添加c1/c2等字段
|
||
routes.append(route_obj)
|
||
print(f"成功生成 {len(routes)} 条路径")
|
||
else:
|
||
print("警告: 未找到advancedCurveList数据")
|
||
return routes
|
||
|
||
def extract_bin_locations(self):
|
||
"""
|
||
优先从converted/binLocationsList.json读取binLocationsList,否则从map_data中读取并保存。
|
||
"""
|
||
import os
|
||
filename = f"{self.converted_dir}/binLocationsList.json"
|
||
bin_locations = None
|
||
if os.path.exists(filename):
|
||
with open(filename, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
bin_locations = data.get('binLocationsList', [])
|
||
else:
|
||
bin_locations = self.map_data.get('binLocationsList', [])
|
||
self.scene_data['binLocationsList'] = bin_locations
|
||
self.save_single_key_json('binLocationsList', self.scene_data['binLocationsList'])
|
||
|
||
def convert_basic(self, smap_file, scene_file, output_file="converted_scene.scene"):
|
||
print(f"开始基本信息转换...")
|
||
print(f"源地图文件: {smap_file}")
|
||
print(f"源场景文件: {scene_file}")
|
||
print(f"输出文件: {output_file}")
|
||
print("-" * 50)
|
||
self.load_source_files(smap_file, scene_file)
|
||
self.save_bin_locations_list()
|
||
self.extract_basic_info()
|
||
self.extract_coordinate_system()
|
||
self.extract_robots()
|
||
self.extract_points()
|
||
self.extract_routes()
|
||
self.extract_bin_locations()
|
||
|
||
# 获取所有一级key
|
||
all_keys = [
|
||
'id', 'setting', 'ratio', 'name', 'width', 'height', 'scale', 'origin',
|
||
'robotGroups', 'robots', 'points', 'routes', 'areas', 'blocks', 'binLocationsList'
|
||
]
|
||
# 补全缺失的一级key,值为空(空列表、空字典或空字符串)
|
||
for key in all_keys:
|
||
if key not in self.scene_data:
|
||
if key in ['robotGroups', 'robots', 'points', 'routes', 'areas', 'binLocationsList']:
|
||
self.scene_data[key] = []
|
||
elif key in ['origin', 'scale']:
|
||
self.scene_data[key] = {} if key == 'origin' else 0
|
||
elif key in ['blocks']:
|
||
self.scene_data[key] = ""
|
||
else:
|
||
self.scene_data[key] = ""
|
||
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
json.dump(self.scene_data, f, indent=2, ensure_ascii=False)
|
||
print(f"基本信息转换完成!结果已保存到 {output_file}")
|
||
return self.scene_data
|
||
|