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