RobotPointParamsViewModel.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. using Prism.Commands;
  2. using Prism.Events;
  3. using Prism.Ioc;
  4. using Prism.Mvvm;
  5. using Prism.Regions;
  6. using Prism.Services.Dialogs;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Collections.ObjectModel;
  10. using System.Linq;
  11. using TeamAAS_VP.Enums;
  12. using TeamAAS_VP.Events;
  13. using TeamAAS_VP.Core;
  14. using TeamAAS_VP.Interfaces;
  15. using TeamAAS_VP.Models;
  16. using System.Windows;
  17. using TeamAAS_VP;
  18. using TeamAAS_VP.Resources.Languages;
  19. namespace TeamAAS_VP.ViewModels.Product
  20. {
  21. public class RobotPointParamsViewModel : BindableBase
  22. {
  23. IRegionManager _regionManager;
  24. IEventAggregator _eventAggregator;
  25. IContainerProvider _container;
  26. IDialogService _dialogService;
  27. ProductsViewModel productsViewModel;
  28. #region 属性
  29. private ProductModel _SelectProduct;
  30. /// <summary>
  31. /// 选中的产品
  32. /// </summary>
  33. public ProductModel SelectProduct
  34. {
  35. get { return _SelectProduct; }
  36. set { SetProperty(ref _SelectProduct, value); }
  37. }
  38. private RPoint _SelectPoint;
  39. public RPoint SelectPoint
  40. {
  41. get { return _SelectPoint; }
  42. set { SetProperty(ref _SelectPoint,value); }
  43. }
  44. private MotionCommand _SelectMotionCommand;
  45. /// <summary>
  46. /// 选中的运动指令
  47. /// </summary>
  48. public MotionCommand SelectMotionCommand
  49. {
  50. get { return _SelectMotionCommand; }
  51. set { SetProperty(ref _SelectMotionCommand,value); }
  52. }
  53. private int _SelectPointIndex=-1;
  54. public int SelectPointIndex
  55. {
  56. get { return _SelectPointIndex; }
  57. set { SetProperty(ref _SelectPointIndex,value); }
  58. }
  59. public Management management { get; set; }
  60. public RobotService robotService { get; set; }
  61. private IRobot _Robot;
  62. public IRobot Robot
  63. {
  64. get { return _Robot; }
  65. set
  66. {
  67. SetProperty(ref _Robot, value);
  68. try
  69. {
  70. var config = FileHelper.ReadApplicationConfiguration();
  71. if (config.RobotJogSetpDistance.TryGetValue(value.Id, out double distance))
  72. {
  73. this.Distance = distance;
  74. }
  75. }
  76. catch (Exception)
  77. {
  78. }
  79. }
  80. }
  81. private double _Distance = 1;
  82. public double Distance
  83. {
  84. get { return _Distance; }
  85. set
  86. {
  87. SetProperty(ref _Distance, value);
  88. try
  89. {
  90. if (Robot == null)
  91. {
  92. return;
  93. }
  94. var config = FileHelper.ReadApplicationConfiguration();
  95. if (config.RobotJogSetpDistance.ContainsKey(Robot.Id))
  96. {
  97. config.RobotJogSetpDistance[Robot.Id] = value;
  98. }
  99. else
  100. {
  101. config.RobotJogSetpDistance.Add(Robot.Id, value);
  102. }
  103. FileHelper.SaveApplicationConfiguration(config);
  104. }
  105. catch (Exception)
  106. {
  107. }
  108. }
  109. }
  110. private ObservableCollection<RobotInfo> _Robots = new ObservableCollection<RobotInfo>();
  111. /// <summary>
  112. /// 机器人参数
  113. /// </summary>
  114. public ObservableCollection<RobotInfo> Robots
  115. {
  116. get { return _Robots; }
  117. set { SetProperty(ref _Robots, value);
  118. if (value!=null)
  119. {
  120. if (value.Count<=0)
  121. {
  122. IsHaveRobot = false;
  123. }
  124. else
  125. {
  126. IsHaveRobot = true;
  127. }
  128. }
  129. }
  130. }
  131. private RobotInfo _RobotInfo = new RobotInfo();
  132. /// <summary>
  133. /// 机器人
  134. /// </summary>
  135. public RobotInfo SelectRobotInfo
  136. {
  137. get { return _RobotInfo; }
  138. set { SetProperty(ref _RobotInfo, value);
  139. if (value != null)
  140. {
  141. Robot = robotService.GetRobot(value.Id);
  142. if (value.RobotBrand == RobotBrand.FANUC)
  143. {
  144. Tools = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
  145. SelectTool = Robot.SelectedTool;
  146. }
  147. else
  148. {
  149. Tools = new ObservableCollection<int> { 0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
  150. SelectTool = Robot.SelectedTool;
  151. }
  152. }
  153. }
  154. }
  155. private ObservableCollection<int> _Tools = new ObservableCollection<int>();
  156. /// <summary>
  157. /// 机器人Tool集合
  158. /// </summary>
  159. public ObservableCollection<int> Tools
  160. {
  161. get { return _Tools; }
  162. set { SetProperty(ref _Tools, value); }
  163. }
  164. private int _SelectTool;
  165. /// <summary>
  166. /// 选中的Tool
  167. /// </summary>
  168. public int SelectTool
  169. {
  170. get { return _SelectTool; }
  171. set { SetProperty(ref _SelectTool, value);
  172. if (value >= 0)
  173. {
  174. if (Robot!=null && Robot.IsConnected)
  175. {
  176. try
  177. {
  178. Robot.SelectToolAsync(value);
  179. }
  180. catch (Exception)
  181. {
  182. }
  183. }
  184. }
  185. }
  186. }
  187. private bool _IsHaveRobot = false;
  188. public bool IsHaveRobot
  189. {
  190. get { return _IsHaveRobot; }
  191. set { SetProperty(ref _IsHaveRobot, value); }
  192. }
  193. #endregion
  194. #region 命令
  195. private DelegateCommand _NextCommand;
  196. public DelegateCommand NextCommand =>
  197. _NextCommand ?? (_NextCommand = new DelegateCommand(ExecuteNextCommand));
  198. private DelegateCommand _BackCommand;
  199. public DelegateCommand BackCommand =>
  200. _BackCommand ?? (_BackCommand = new DelegateCommand(ExecuteBackCommand));
  201. private DelegateCommand _AddPointCommand;
  202. public DelegateCommand AddPointCommand =>
  203. _AddPointCommand ?? (_AddPointCommand = new DelegateCommand(ExecuteAddPointCommand));
  204. private DelegateCommand _RemovePointCommand;
  205. public DelegateCommand RemovePointCommand =>
  206. _RemovePointCommand ?? (_RemovePointCommand = new DelegateCommand(ExecuteRemovePointCommand, () => { return SelectPointIndex >= 0 ? true : false; }).ObservesProperty(()=> SelectPointIndex));
  207. private DelegateCommand _GoPalletCommand;
  208. public DelegateCommand GoPalletCommand =>
  209. _GoPalletCommand ?? (_GoPalletCommand = new DelegateCommand(ExecuteGoPalletCommand));
  210. private DelegateCommand _LoadedCommand;
  211. public DelegateCommand LoadedCommand =>
  212. _LoadedCommand ?? (_LoadedCommand = new DelegateCommand(ExecuteLoadedCommand));
  213. private DelegateCommand<object> _ExecuteMotion;
  214. public DelegateCommand<object> ExecuteMotion =>
  215. _ExecuteMotion ?? (_ExecuteMotion = new DelegateCommand<object>(ExecuteExecuteMotion).ObservesCanExecute(() => CanExecute).ObservesProperty(()=>Robot).ObservesProperty(()=>Robot.CanExecute));
  216. private DelegateCommand<object> _JogCommand;
  217. public DelegateCommand<object> JogCommand =>
  218. _JogCommand ?? (_JogCommand = new DelegateCommand<object>(ExecuteJogCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  219. private DelegateCommand<RPoint> _TechPointCommand;
  220. public DelegateCommand<RPoint> TechPointCommand =>
  221. _TechPointCommand ?? (_TechPointCommand = new DelegateCommand<RPoint>(ExecuteTechPointCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  222. private DelegateCommand<string> _MotorCommand;
  223. public DelegateCommand<string> MotorCommand =>
  224. _MotorCommand ?? (_MotorCommand = new DelegateCommand<string>(ExecuteMotorCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  225. private DelegateCommand _ResetCommand;
  226. public DelegateCommand ResetCommand =>
  227. _ResetCommand ?? (_ResetCommand = new DelegateCommand(ExecuteResetCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  228. private DelegateCommand _sFreeCommand;
  229. public DelegateCommand sFreeCommand =>
  230. _sFreeCommand ?? (_sFreeCommand = new DelegateCommand(ExecutesFreeCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  231. private DelegateCommand _SLockCommand;
  232. public DelegateCommand SLockCommand =>
  233. _SLockCommand ?? (_SLockCommand = new DelegateCommand(ExecuteSLockCommand).ObservesCanExecute(() => CanExecute).ObservesProperty(() => Robot).ObservesProperty(() => Robot.CanExecute));
  234. #endregion
  235. #region 事件
  236. #endregion
  237. public RobotPointParamsViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService)
  238. {
  239. _regionManager = regionManager;
  240. _eventAggregator = ea;
  241. _container = container;
  242. _dialogService = dialogService;
  243. //订阅权限登录事件
  244. _eventAggregator.GetEvent<UserLoginNotification>().Subscribe(LoginChange);
  245. productsViewModel = _container.Resolve<ProductsViewModel>();
  246. SelectProduct = productsViewModel.SelectProduct;
  247. management = _container.Resolve<Management>();
  248. Robots = management.DeviceConfig.Robots;
  249. }
  250. #region 方法
  251. /// <summary>
  252. /// 下一步
  253. /// </summary>
  254. void ExecuteNextCommand()
  255. {
  256. _regionManager.RequestNavigate("ProductRegionContext", "ProductParams");
  257. }
  258. /// <summary>
  259. /// 上一步
  260. /// </summary>
  261. void ExecuteBackCommand()
  262. {
  263. _regionManager.RequestNavigate("ProductRegionContext", "ProcessBasics");
  264. }
  265. /// <summary>
  266. /// 增加点位
  267. /// </summary>
  268. void ExecuteAddPointCommand()
  269. {
  270. try
  271. {
  272. if (SelectProduct.RobotPoint == null)
  273. SelectProduct.RobotPoint = new RobotPoint();
  274. if (SelectProduct.RobotPoint.Points == null)
  275. SelectProduct.RobotPoint.Points = new ObservableCollection<MultipleRobotPoint>();
  276. if (SelectRobotInfo == null)
  277. {
  278. SelectProduct.RobotPoint.Points.Add(new MultipleRobotPoint() { Number = SelectProduct.RobotPoint.Points.Count });
  279. }
  280. else
  281. {
  282. SelectProduct.RobotPoint.Points.Add(new MultipleRobotPoint() { Number = SelectProduct.RobotPoint.Points.Count, RobotId = SelectRobotInfo.Id });
  283. }
  284. }
  285. catch (Exception ex)
  286. {
  287. LogHelper.WriteLogError("机器人点位-新建点时出错", ex);
  288. MessageBox.Show(ex.Message);
  289. }
  290. }
  291. /// <summary>
  292. /// 移除点位
  293. /// </summary>
  294. void ExecuteRemovePointCommand()
  295. {
  296. try
  297. {
  298. if (SelectPointIndex >= 0)
  299. {
  300. SelectProduct.RobotPoint.Points.RemoveAt(SelectPointIndex);
  301. for (int i = 0; i < SelectProduct.RobotPoint.Points.Count; i++)
  302. {
  303. SelectProduct.RobotPoint.Points[i].Number = i;
  304. }
  305. }
  306. }
  307. catch (Exception ex)
  308. {
  309. LogHelper.WriteLogError("机器人点位-移除点时出错", ex);
  310. MessageBox.Show(ex.Message);
  311. }
  312. }
  313. /// <summary>
  314. /// 跳转托盘
  315. /// </summary>
  316. void ExecuteGoPalletCommand()
  317. {
  318. try
  319. {
  320. _dialogService.Show("PalletManage", rst => {
  321. //对话框关闭之后的回调函数,可以在这解析结果。
  322. ButtonResult result1 = rst.Result;
  323. if (result1 == ButtonResult.OK)
  324. {
  325. var param = rst.Parameters.GetValue<ObservableCollection<Pallet>>("Pallets");
  326. SelectProduct.RobotPoint.Pallets = param;
  327. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = Lang.已关闭, Duration = 1 });
  328. }
  329. });
  330. }
  331. catch (Exception ex)
  332. {
  333. LogHelper.WriteLogError("机器人点位-跳转托盘界面时出错", ex);
  334. MessageBox.Show(ex.Message);
  335. }
  336. }
  337. /// <summary>
  338. /// 点动
  339. /// </summary>
  340. /// <param name="obj"></param>
  341. private async void ExecuteJogCommand(object obj)
  342. {
  343. if (Robot == null || !Robot.IsConnected) return;
  344. try
  345. {
  346. Robot.Timeout = 6000000;
  347. var items = ((string)obj).Split(':');
  348. switch (items[0])
  349. {
  350. case "X+":
  351. await Robot.JogAsync("X", double.Parse(items[1]));
  352. break;
  353. case "X-":
  354. await Robot.JogAsync("X", -double.Parse(items[1]));
  355. break;
  356. case "Y+":
  357. await Robot.JogAsync("Y", double.Parse(items[1]));
  358. break;
  359. case "Y-":
  360. await Robot.JogAsync("Y", -double.Parse(items[1]));
  361. break;
  362. case "Z+":
  363. await Robot.JogAsync("Z", double.Parse(items[1]));
  364. break;
  365. case "Z-":
  366. await Robot.JogAsync("Z", -double.Parse(items[1]));
  367. break;
  368. case "U+":
  369. await Robot.JogAsync("U", double.Parse(items[1]));
  370. break;
  371. case "U-":
  372. await Robot.JogAsync("U", -double.Parse(items[1]));
  373. break;
  374. case "V+":
  375. await Robot.JogAsync("V", double.Parse(items[1]));
  376. break;
  377. case "V-":
  378. await Robot.JogAsync("V", -double.Parse(items[1]));
  379. break;
  380. case "W+":
  381. await Robot.JogAsync("W", double.Parse(items[1]));
  382. break;
  383. case "W-":
  384. await Robot.JogAsync("W", -double.Parse(items[1]));
  385. break;
  386. default:
  387. break;
  388. }
  389. Robot.Timeout = 5000;
  390. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  391. }
  392. catch (Exception ex)
  393. {
  394. LogHelper.WriteLogError("机器人点位-机器人点动时出错", ex);
  395. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  396. }
  397. }
  398. /// <summary>
  399. /// 执行运动
  400. /// </summary>
  401. /// <param name="obj"></param>
  402. async void ExecuteExecuteMotion(object parameter)
  403. {
  404. var values = (object[])parameter;
  405. var point = values[0] as RPoint;
  406. MotionCommand motionCmd = (MotionCommand)values[1];
  407. if (Robot == null || !Robot.IsConnected) return;
  408. try
  409. {
  410. Robot.Timeout = 6000000;
  411. switch (motionCmd)
  412. {
  413. case MotionCommand.Go:
  414. await Robot.GoAsync(point);
  415. break;
  416. case MotionCommand.Jump:
  417. await Robot.JumpAsync(point, 0);
  418. break;
  419. case MotionCommand.Move:
  420. await Robot.MoveAsync(point);
  421. break;
  422. }
  423. Robot.Timeout = 5000;
  424. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  425. }
  426. catch (Exception ex)
  427. {
  428. LogHelper.WriteLogError("机器人点位-执行点位时出错", ex);
  429. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  430. }
  431. }
  432. /// <summary>
  433. /// 示教点位
  434. /// </summary>
  435. /// <param name="point"></param>
  436. async void ExecuteTechPointCommand(RPoint point)
  437. {
  438. if (Robot == null || !Robot.IsConnected) return;
  439. try
  440. {
  441. var curpos = await Robot.GetRobotPosAsync();
  442. MultipleRobotPoint p0 = SelectProduct.RobotPoint.Points.FirstOrDefault(x => x.Number == point.Number);
  443. p0.X = curpos.X;
  444. p0.Y = curpos.Y;
  445. p0.Z = curpos.Z;
  446. p0.U = curpos.U;
  447. p0.V = curpos.V;
  448. p0.W = curpos.W;
  449. p0.Hand= curpos.Hand;
  450. p0.Local = curpos.Local;
  451. p0.Tool= curpos.Tool;
  452. p0.RobotId = Robot.Id;
  453. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  454. }
  455. catch (Exception ex)
  456. {
  457. LogHelper.WriteLogError("机器人点位-示教点位时出错", ex);
  458. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  459. }
  460. }
  461. void ExecuteLoadedCommand()
  462. {
  463. if (_container.Resolve<Management>().CurrentUser.userPart == Enums.UserPart.Operator)
  464. {
  465. IsAllowEdit = false;
  466. }
  467. else
  468. {
  469. IsAllowEdit = true;
  470. }
  471. productsViewModel = _container.Resolve<ProductsViewModel>();
  472. SelectProduct = productsViewModel.SelectProduct;
  473. management = _container.Resolve<Management>();
  474. Robots= management.DeviceConfig.Robots;
  475. robotService = management.RobotService;
  476. SelectRobotInfo = null;
  477. if (Robot != null && Robot.IsConnected)
  478. {
  479. if (Tools.Count > 1)
  480. {
  481. SelectTool = Robot.SelectedTool;
  482. }
  483. }
  484. }
  485. /// <summary>
  486. /// 电机
  487. /// </summary>
  488. /// <param name="obj"></param>
  489. private async void ExecuteMotorCommand(string obj)
  490. {
  491. if (Robot == null || !Robot.IsConnected) return;
  492. try
  493. {
  494. await Robot.MotorAsync(Convert.ToBoolean(obj));
  495. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  496. }
  497. catch (Exception ex)
  498. {
  499. LogHelper.WriteLogError("机器人点击操作时出错!", ex);
  500. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  501. }
  502. }
  503. /// <summary>
  504. /// 重置
  505. /// </summary>
  506. private async void ExecuteResetCommand()
  507. {
  508. if (Robot == null || !Robot.IsConnected) return;
  509. try
  510. {
  511. await Robot.ResetAsync();
  512. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  513. }
  514. catch (Exception ex)
  515. {
  516. LogHelper.WriteLogError("重置机器人时出错!", ex);
  517. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  518. }
  519. }
  520. /// <summary>
  521. /// 锁定刹车
  522. /// </summary>
  523. private async void ExecuteSLockCommand()
  524. {
  525. if (Robot == null || !Robot.IsConnected) return;
  526. try
  527. {
  528. await Robot.SLockAsync();
  529. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  530. }
  531. catch (Exception ex)
  532. {
  533. LogHelper.WriteLogError("机器人锁定刹车时出错!", ex);
  534. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  535. }
  536. }
  537. /// <summary>
  538. /// 释放刹车
  539. /// </summary>
  540. private async void ExecutesFreeCommand()
  541. {
  542. if (Robot == null || !Robot.IsConnected) return;
  543. try
  544. {
  545. await Robot.SFreeAsync();
  546. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = $"{Lang.完成}!", Duration = 0.4 });
  547. }
  548. catch (Exception ex)
  549. {
  550. LogHelper.WriteLogError("机器人释放刹车时出错!", ex);
  551. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = ex.Message, Duration = 1 });
  552. }
  553. }
  554. private bool CanExecute
  555. {
  556. get
  557. {
  558. if (Robot != null)
  559. {
  560. return Robot.CanExecute;
  561. }
  562. else
  563. {
  564. return false;
  565. }
  566. }
  567. }
  568. #endregion
  569. private bool _IsAllowEdit = false;
  570. public bool IsAllowEdit
  571. {
  572. get { return _IsAllowEdit; }
  573. set { SetProperty(ref _IsAllowEdit, value); }
  574. }
  575. private void LoginChange(Models.User user)
  576. {
  577. if (user.userPart == Enums.UserPart.Operator)
  578. {
  579. IsAllowEdit = false;
  580. }
  581. else
  582. {
  583. IsAllowEdit = true;
  584. }
  585. }
  586. }
  587. }