Compare commits

..

136 commits
main ... dev

Author SHA1 Message Date
4ceae093f3 update run command for BT 2025-04-18 10:29:26 +03:00
f0e09812ee input topics for ObjectDetection skill + OD skill 2025-04-18 10:27:34 +03:00
ccc1e2da4b clean and update scripts in web_p 2025-03-12 11:45:34 +03:00
c85784f3dc update scripts in web_p (dataset, train) 2025-02-28 20:21:33 +03:00
IDONTSUDO
7d3b8ff0cb topics 2025-02-17 21:30:09 +03:00
IDONTSUDO
d7e8c825cb progress 2025-02-15 17:23:27 +03:00
IDONTSUDO
40eebf9dd8 fix 2025-02-07 00:15:57 +03:00
IDONTSUDO
564866393f progress 2025-02-02 20:28:05 +03:00
IDONTSUDO
2afed3a4f0 fix 2025-01-26 18:40:29 +03:00
470a7ef43e add process type: WEIGHTS = "WEIGHTS" 2025-01-24 17:48:37 +03:00
7c01c61194 readme on migrate DB + 2025-01-23 11:30:39 +03:00
IDONTSUDO
3044540814 progress 2025-01-20 10:29:39 +03:00
IDONTSUDO
dfe7b20f8d progress 2025-01-19 23:36:04 +03:00
IDONTSUDO
c2b91ee4d7 progress 2025-01-19 21:37:27 +03:00
IDONTSUDO
c2da1d8f4c progress 2025-01-18 15:29:07 +03:00
IDONTSUDO
d7ea79935e fix 2025-01-01 19:11:23 +03:00
IDONTSUDO
a94d84ba69 progress 2024-12-31 15:06:14 +03:00
IDONTSUDO
d3a07a5c11 progress 2024-12-31 14:47:17 +03:00
IDONTSUDO
12c09c4461 add new feature 2024-12-31 01:27:46 +03:00
IDONTSUDO
8696d99194 progress 2024-12-14 20:09:42 +03:00
bad5c4d8a0 checking install in readme 2024-12-11 14:16:04 +03:00
IDONTSUDO
88e40f7bdb fix 2024-12-11 14:13:46 +03:00
IDONTSUDO
58b22d294d fix 2024-12-11 14:12:54 +03:00
IDONTSUDO
f39f69a440 Merge branch 'main' into dev 2024-12-01 19:09:45 +03:00
IDONTSUDO
ea1ebe0e95 progress 2024-12-01 18:53:28 +03:00
IDONTSUDO
2024834d06 progress 2024-11-28 17:51:45 +03:00
IDONTSUDO
300a5005ba fixed 2024-11-28 17:46:34 +03:00
IDONTSUDO
e163f0698b progress 2024-11-28 17:23:29 +03:00
cad230eee9 add GetInterfaces + renderBOPdataset2 2024-11-28 16:28:16 +03:00
IDONTSUDO
aaa4257920 fixed form builder test 2024-11-27 19:38:00 +03:00
IDONTSUDO
3accb5af61 fix 2024-11-27 19:30:31 +03:00
IDONTSUDO
6190869d8d progress 2024-11-27 19:29:59 +03:00
IDONTSUDO
28c36ae710 progress 2024-11-27 19:29:49 +03:00
IDONTSUDO
3f951d1c09 fixed replace "/" error 2024-11-17 14:57:16 +03:00
IDONTSUDO
29dcfb6ac1 fix 2024-10-28 11:52:53 +03:00
IDONTSUDO
00c553ecd7 fix 2024-10-28 10:33:26 +03:00
IDONTSUDO
3f88ceebe4 fixed exec comand 2024-10-27 12:21:09 +03:00
IDONTSUDO
2d59388494 fixed alex 2024-10-25 17:19:19 +03:00
IDONTSUDO
23c39cbae2 skill import 2024-10-20 15:58:14 +03:00
IDONTSUDO
059b2e3e64 fixed form builder 2024-10-20 14:44:24 +03:00
IDONTSUDO
f019c3a1c4 fixed select detail 2024-10-20 14:20:07 +03:00
IDONTSUDO
2f15b86a42 fixed bug 2024-10-20 13:09:48 +03:00
IDONTSUDO
15cb712c3d fixed errors 2024-10-14 11:41:35 +03:00
IDONTSUDO
1ec2a3ff7a fixed alex error 2024-10-14 11:10:26 +03:00
IDONTSUDO
7693377ad1 delete scene itm 2024-10-13 22:00:05 +03:00
IDONTSUDO
1cc489fc72 everything but light 2024-10-13 21:52:55 +03:00
IDONTSUDO
139f012803 alex form 2024-10-05 14:33:09 +03:00
IDONTSUDO
0dbc04f09b fixe form builder 2024-10-05 14:16:28 +03:00
IDONTSUDO
c19e4c684e fixed form builder object bug 2024-10-05 12:59:11 +03:00
IDONTSUDO
2b4795dfc2 fix 2024-10-01 15:04:28 +03:00
IDONTSUDO
04b1c2bfc7 fix 2024-10-01 14:40:13 +03:00
IDONTSUDO
3369306451 fixed condtion action bug 2024-10-01 13:55:31 +03:00
IDONTSUDO
15930c5f85 scene manger separated scene builder 2024-10-01 13:43:07 +03:00
IDONTSUDO
da883edb64 fix 2024-10-01 11:27:57 +03:00
IDONTSUDO
c135bca77a update dep 2024-10-01 09:57:21 +03:00
IDONTSUDO
4657652dd0 condition fixed 2024-09-30 18:34:59 +03:00
IDONTSUDO
314e070bee Alexander changes 2024-09-30 17:19:16 +03:00
IDONTSUDO
d47d555061 bt builder 2024-09-30 15:32:30 +03:00
IDONTSUDO
50d239a4eb fixed bugs 2024-09-26 18:41:51 +03:00
IDONTSUDO
5eb588a709 fixed 2024-09-25 21:27:26 +03:00
IDONTSUDO
ba5394107a fixed errors 2024-09-25 19:12:02 +03:00
IDONTSUDO
fb4fa52c14 fix path 2024-09-24 17:21:05 +03:00
IDONTSUDO
4e6132a872 alex find bugs 2024-09-24 17:18:56 +03:00
IDONTSUDO
d804aa2edf fixed 2024-09-23 14:56:07 +03:00
IDONTSUDO
e2cd7af032 fixed launch 2024-09-23 14:18:48 +03:00
IDONTSUDO
c0a23c31f7 fixed bugs 2024-09-23 13:19:23 +03:00
IDONTSUDO
193e884e40 fixed errors 2024-09-20 18:12:00 +03:00
IDONTSUDO
401080d78e alexander test 2024-09-20 13:56:33 +03:00
IDONTSUDO
3b8d9e4298 sync 2024-09-10 19:58:09 +03:00
IDONTSUDO
25e57c1a56 remove missing files 2024-08-29 12:34:23 +03:00
IDONTSUDO
a920f5fb45 skills screen 2024-08-29 12:26:54 +03:00
IDONTSUDO
cac5fad8ce fix 2024-08-21 18:25:43 +03:00
IDONTSUDO
d6702185f0 fixed path 2024-08-21 18:14:04 +03:00
IDONTSUDO
1287625107 fix error 2024-08-21 16:57:28 +03:00
IDONTSUDO
e748debd2f diference 2024-08-21 16:31:41 +03:00
IDONTSUDO
7063e93c75 alexander 2024-08-21 16:15:54 +03:00
IDONTSUDO
cf75b4220a missing files 2024-08-13 16:36:06 +03:00
IDONTSUDO
ca3a1cfed9 progress 2024-08-13 16:35:32 +03:00
IDONTSUDO
48be3e6d33 progress 2024-08-13 13:03:28 +03:00
IDONTSUDO
d41310196f progress machine learning screen 2024-07-26 18:50:36 +03:00
IDONTSUDO
0b3eefec2d fixed coords foorm 2024-07-25 13:14:45 +03:00
IDONTSUDO
17ed2905c2 fixed xyz 2024-07-25 12:08:09 +03:00
IDONTSUDO
6e6a1c0c1d overflow 2024-07-25 12:00:03 +03:00
IDONTSUDO
6003cfde04 scenes 2024-07-25 11:50:43 +03:00
IDONTSUDO
9120729d41 progress 2024-07-23 15:57:22 +03:00
IDONTSUDO
318d0a7893 progress 2024-07-23 13:33:19 +03:00
IDONTSUDO
d8b5018cb2 progress 2024-07-13 18:18:14 +03:00
IDONTSUDO
50822a031d progress 2024-07-03 15:25:51 +03:00
IDONTSUDO
559262db34 alex 2024-06-27 11:34:42 +03:00
IDONTSUDO
b5750b12ef alexander add env 2024-06-26 18:19:06 +03:00
IDONTSUDO
50d0c4c12b progress scene builder 2024-06-25 12:43:41 +03:00
IDONTSUDO
0a4eea19c5 details preview 2024-06-20 21:45:57 +03:00
IDONTSUDO
81238c5182 debug 2024-06-19 15:23:01 +03:00
IDONTSUDO
53fe9bf51a update dependency 2024-06-14 16:08:12 +03:00
IDONTSUDO
4e726be376 set active project 2024-06-10 17:28:13 +03:00
IDONTSUDO
814eb485eb change readme 2024-06-10 15:18:13 +03:00
IDONTSUDO
8f8fc3107d alexander bt fixed 2024-06-10 15:16:30 +03:00
Your Name
61118a7423 simultaion 2024-06-04 13:19:22 +03:00
IDONTSUDO
c5ae89d18a bt builder 2024-05-29 15:38:36 +03:00
IDONTSUDO
5162612a77 progress 2024-05-13 20:43:11 +03:00
IDONTSUDO
4585d079f6 progress 2024-05-03 21:30:18 +03:00
IDONTSUDO
89d4226ea6 progress 2024-05-02 17:57:58 +03:00
IDONTSUDO
a0cee0b394 change blender file 2024-05-02 17:41:42 +03:00
IDONTSUDO
e0a6cd0af1 alexander 2024-05-02 17:36:44 +03:00
IDONTSUDO
c49beb8218 progress 2024-05-02 17:30:59 +03:00
IDONTSUDO
8bc943de2c progress 2024-05-02 12:40:20 +03:00
IDONTSUDO
f0ed478b36 progress 2024-04-26 15:51:20 +03:00
IDONTSUDO
c4ddb3dc8c progress 2024-04-26 15:44:09 +03:00
IDONTSUDO
e155b4a2a1 progress 2024-04-25 12:22:21 +03:00
IDONTSUDO
e005a42254 Merge branch 'main' of https://gitlab.com/robossembler/webservice into alexander-1 2024-04-23 16:01:11 +03:00
IDONTSUDO
c628cdb9af progress 2024-04-23 15:56:52 +03:00
IDONTSUDO
7e25cc216a Merge branch 'main' of https://gitlab.com/robossembler/webservice into alexander 2024-04-19 13:52:07 +03:00
IDONTSUDO
810eb5926e fix 2024-04-19 13:30:03 +03:00
IDONTSUDO
94b40bf24c progress 2024-04-18 20:44:26 +03:00
IDONTSUDO
38ffde1d77 form view model array objects map edit 2024-04-18 17:03:02 +03:00
IDONTSUDO
e85dc14fc8 progress 2024-04-18 15:22:24 +03:00
IDONTSUDO
f11dfa7e57 formbuilder fix 2024-04-17 15:21:40 +03:00
IDONTSUDO
2b8d0fa88b icons add 2024-04-16 15:44:31 +03:00
IDONTSUDO
a2066ce5cd adding socket listner dataset screen 2024-04-16 15:20:24 +03:00
IDONTSUDO
776b6e540e miss 2024-04-15 18:30:41 +03:00
IDONTSUDO
78ebb748ea progress 2024-04-15 18:24:44 +03:00
IDONTSUDO
c10cdb8158 CoreInput form error adding 2024-04-12 20:43:36 +03:00
IDONTSUDO
27763fc8d2 progress 2024-04-12 17:02:49 +03:00
IDONTSUDO
0c03906518 progress 2024-04-11 22:02:57 +03:00
IDONTSUDO
c17515d571 deleted unnecessary files
added new features
2024-04-09 16:31:30 +03:00
IDONTSUDO
6840402b1f deleted unnecessary files
added new features
2024-04-09 16:31:25 +03:00
IDONTSUDO
dffc73c30f base bt 2024-02-27 18:48:35 +03:00
IDONTSUDO
c43c192a3e Merge branch 'main' of https://gitlab.com/robossembler/webservice into 14-fix/sticky-objects 2024-02-19 14:40:55 +03:00
IDONTSUDO
0d825f7f63 sticky 2024-02-19 14:36:27 +03:00
IDONTSUDO
de5b493c77 mark debug 2024-02-16 14:16:35 +03:00
IDONTSUDO
acc79df97d bt builder progress 2024-02-05 15:28:09 +03:00
IDONTSUDO
5dec799002 corrected typos, fixed a bug with object transfer 2024-01-24 15:08:52 +03:00
IDONTSUDO
9028186a74 added scene manager 2024-01-23 17:26:59 +03:00
IDONTSUDO
2adb939f37 added scene manager 2024-01-23 17:23:10 +03:00
IDONTSUDO
ae9842d5e1 process 2023-12-28 17:18:12 +03:00
IDONTSUDO
7ff6165882 Refactoring 2023-12-22 21:07:55 +03:00
117 changed files with 2746 additions and 761 deletions

View file

@ -20,6 +20,7 @@
"GLTF",
"grau",
"idontsudo",
"Pids",
"raycaster",
"skils",
"typedataset",

61
flake.lock generated
View file

@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1733935512,
"narHash": "sha256-beDnPaHubnwcsbVPC3rIVtQM3QVFDMmc0dtfHCW5UrA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d55cc4608dae1b42a0ef5c0cf701b501fc7bae58",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,37 +0,0 @@
{
description = "Robossembler Development Environments on Nix";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=master";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs { inherit system; overlays = []; };
in
{
packages = {
frontend = pkgs.writeShellApplication {
name = "frontend";
runtimeInputs = [ pkgs.nodejs ];
text = ''
cd ui && npm i && npm run dev
'';
};
backend = pkgs.writeShellApplication {
name = "frontend";
runtimeInputs = [ pkgs.nodejs ];
text = ''
cd server && npm i && npm run dev
'';
};
};
devShells = {
default = pkgs.mkShell { packages = with pkgs; [ nodejs ]; };
};
}
);
}

82
p.json Normal file
View file

@ -0,0 +1,82 @@
{
"process": {
"type": "OBJECT_DETECTION",
"selectProcess": {
"value": {
"_id": "675db885429ef25f8d2efaa2",
"script": "ls -l -a",
"formBuilder": {
"result": "",
"context": "",
"form": [],
"output": "",
"type": "formBuilder"
},
"type": "OBJECT_DETECTION",
"instanceName": "ls -l -a",
"name": "ls",
"isEnd": true,
"createDate": "1734195300981",
"card": "pose_estimate",
"path": "/Users/idontsudo/webservice/server/build/public//process/ls",
"instancePath": "/Users/idontsudo/webservice/server/build/public//process/ls/ls -l -a",
"project": {
"_id": "675eb125281cf9253681efa3",
"description": "e1wq",
"rootDir": "/Users/idontsudo/webservice/server/build/public/f49f8f47-5427-48aa-8aff-c5e7ae4e6efe",
"isActive": true,
"__v": 0
},
"__v": 0,
"lastProcessExecCommand": "ls -l -a --path /Users/idontsudo/webservice/server/build/public/process/ls/ls -l -a --form /Users/idontsudo/webservice/server/build/public/process/ls/ls -l -a/form.json",
"processStatus": "endError",
"lastProcessLogs": "ls: unrecognized option `--path'nnusage: ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]n"
}
}
},
"datasetObjects": {
"details": []
},
"typedataset": "ObjectDetection",
"models_randomization": {
"loc_range_low": [
-1,
-1,
0
],
"loc_range_high": [
1,
1,
2
]
},
"scene": {
"objects": [],
"lights": []
},
"camera_position": {
"center_shell": [
0,
0,
0
],
"radius_range": [
1,
1.4
],
"elevation_range": [
10,
90
]
},
"generation": {
"n_cam_pose": 5,
"n_sample_on_pose": 3,
"n_series": 100,
"image_format": "JPEG",
"image_size_wh": [
640,
480
]
}
}

View file

@ -1 +1,14 @@
Веб-сервис для отладки Robossembler Framework
Веб-сервис для отладки Robossembler Framework
### Миграция данных веб-сервиса
Имеется ввиду перенос данных, хранящихся в БД MongoDB.
Для этого вначале создаётся копия БД с именем `dev` в папке `my_copy`:
```sh
mongodump --host localhost --port 27017 --db dev --out my_copy
```
Затем папка `my_copy` переносится в нужное место (например, на другой сервер). И запускается её восстановление в новом месте (копия БД с именем `dev`):
```sh
mongorestore --host localhost --port 27017 --db dev my_copy/dev
```

View file

@ -15,12 +15,12 @@
"checkCommand": null,
"filter": null
},
"btBuilderProcess": {
"execCommand": "nix run github:nixos/nixpkgs#python312Packages.tensorboard -- --logdir ${dir_path}",
"btRuntimeProcess": {
"execCommand": "cd ~/robossembler-ws && source ./install/local_setup.bash; ros2 launch rbs_bt_executor rbs_bt_web.launch.py bt_path:=${bt_path}",
"date": null,
"status": null,
"delay": 0,
"checkCommand": null,
"filter": null
}
}
}

View file

@ -9,6 +9,9 @@ import { dirname } from "path";
import { CheckAndCreateStaticFilesFolderUseCase } from "../usecases/check_and_create_static_files_folder_usecase";
import { DataBaseConnectUseCase } from "../usecases/database_connect_usecase";
import { TypedEvent } from "../helpers/typed_event";
import { CalculationInstanceDBModel } from "../../features/calculations_instance/models/calculations_instance_database_model";
import * as fs from "fs";
import { WriteFileSystemFileUseCase } from "../usecases/write_file_system_file_usecase";
export enum ServerStatus {
init = "init",
@ -85,7 +88,25 @@ export class App extends TypedEvent<ServerStatus> {
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
this.app.use(express.static(App.staticFilesStoreDir()));
this.app.get('/logs', async (req, res) => {
const id = req.query.id;
if (id === undefined) {
return res.status(400).json('need req query id?=')
}
const calculationInstanceDBModel = await CalculationInstanceDBModel.findById(id)
if (calculationInstanceDBModel === null) {
return res.status(400).json("calcultaion db model is null");
}
const p = App.staticFilesStoreDir() + '/log.txt';
(await new WriteFileSystemFileUseCase().call(p, calculationInstanceDBModel.lastProcessLogs)).map(() => {
return res.sendFile(p);
})
})
this.app.use(
fileUpload({
createParentPath: true,

View file

@ -65,12 +65,12 @@ interface ISubSetFeatureRouter<A> {
method: HttpMethodType;
subUrl: string;
fn:
| CallbackStrategyWithValidationModel<A>
| CallbackStrategyWithEmpty
| CallbackStrategyWithIdQuery
| CallBackStrategyWithQueryPage
| CallbackStrategyWithFileUpload
| CallbackStrategyWithFilesUploads;
| CallbackStrategyWithValidationModel<A>
| CallbackStrategyWithEmpty
| CallbackStrategyWithIdQuery
| CallBackStrategyWithQueryPage
| CallbackStrategyWithFileUpload
| CallbackStrategyWithFilesUploads;
}
abstract class ICoreHttpController {
@ -234,6 +234,7 @@ export class CoreHttpController<V> implements ICoreHttpController {
return res.json(ok);
},
(err) => {
return res.status(400).json({ error: String(err) });
}
);

View file

@ -18,7 +18,7 @@ export class TopicViewModel {
export interface IParam {
type: string;
dependency: Object;
dependency: object;
}
export interface Skill {
SkillPackage: ISkillPackage;

View file

@ -7,9 +7,8 @@ export abstract class CreateInstanceScenario<V extends Instance> extends Callbac
abstract validationModel: V;
abstract databaseModel: any;
call = async (model: V): ResponseBase => {
model.instancePath = `${model.path}/${model.instanceName}`;
console.log("INSTANCE PATh");
console.log(model.instancePath)
model.instancePath = `${model.path.pathNormalize()}/${model.instanceName}`.pathNormalize();
model.path = model.path.pathNormalize();
return (await new CreateFolderUseCase().call(model.instancePath)).map(
async () => await new CreateDataBaseModelUseCase(this.databaseModel).call(model)
);

View file

@ -0,0 +1,72 @@
import { Disposable, Listener, TypedEvent } from "../helpers/typed_event";
import { GetRootDirUseCase } from "../usecases/get_root_dir_usecase"
import { exec } from 'child_process';
export const activeProcessPids: { [name: string]: { pid: number, status: ProcessStatus } } = {}
export enum ProcessStatus {
run = 'run',
endError = 'endError',
endOk = 'endOk',
userDelete = 'userDelete',
}
class ExecutorProgramServiceV2 extends TypedEvent<{ [name: string]: { pid: number, status: ProcessStatus } }> {}
export const executorProgramServiceV2 = new ExecutorProgramServiceV2();
class ProcessData {
log: string | undefined;
status: ProcessStatus;
constructor(log: string | undefined, status: ProcessStatus) {
if (log !== undefined) {
this.log = log;
}
this.status = status;
}
}
abstract class Watcher {
}
export abstract class ExecProcessWatcher extends TypedEvent<ProcessData> implements Watcher {
logs: string[] = [];
status: ProcessStatus;
constructor() {
super();
this.on((event) => {
if (event.log !== undefined) {
this.logs.push(event.log);
}
this.status = event.status;
if (event.status.isEqualMany([ProcessStatus.endError, ProcessStatus.endOk])) {
this.result()
}
})
}
abstract result(): Promise<any>
}
export class ExecProcessScenarioV2 {
call = (command: string, watcher: ExecProcessWatcher, name?: string): void => {
const process = exec(command, { cwd: new GetRootDirUseCase().call() });
if (process.pid) {
activeProcessPids[name ?? command] = { pid: process.pid, status: ProcessStatus.run };
executorProgramServiceV2.emit(activeProcessPids);
}
process.stdout.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run)))
process.stderr.on('data', (data) => watcher.emit(new ProcessData(data, ProcessStatus.run)));
process.on('close', (code) => {
if (code === 0) {
watcher.emit(new ProcessData(undefined, ProcessStatus.endOk))
activeProcessPids[name ?? command] = { pid: process.pid ?? 0, status: ProcessStatus.endOk }
executorProgramServiceV2.emit(activeProcessPids);
} else {
watcher.emit(new ProcessData(undefined, ProcessStatus.endError))
activeProcessPids[name ?? command] = { pid: process.pid ?? 0, status: ProcessStatus.endError };
executorProgramServiceV2.emit(activeProcessPids);
}
});
process.on('error', (err) => watcher.emit(new ProcessData(err.message, ProcessStatus.endError)));
}
}

View file

@ -50,7 +50,6 @@ export class ExecutorProgramService
};
worker.send(workerDataExec);
worker.on("message", (e) => {
console.log(JSON.stringify(e));
const spawnError = SpawnError.isError(e);
if (spawnError instanceof SpawnError) {

View file

@ -7,6 +7,7 @@ import { ExecutorResult } from "../models/executor_result";
import { ExecutorProgramService } from "../services/executor_program_service";
export const executorProgramService = new ExecutorProgramService("");
export class KillLastProcessUseCase extends CallbackStrategyWithEmpty {
call = async (): Promise<Result<undefined, string>> => {
executorProgramService.deleteWorker();

View file

@ -0,0 +1,5 @@
import * as os from 'os';
export class GetRootDirUseCase {
call = (): string => os.homedir()
}

View file

@ -8,6 +8,9 @@ import { SaveBtScenario as FillBtScenario } from "./domain/save_bt_scenario";
import { GetCameraUseCase } from "./domain/get_cameras_usecase";
import { GetRobotsUseCase } from "./domain/get_robots_usecase";
import { GetTopicsUseCase } from "./domain/get_topics_usecase";
import { DeleteBehaviorTreeScenario } from "./domain/delete_behavior_tree_scenario";
export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValidationModel, typeof BehaviorTreeDBModel> {
constructor() {
@ -43,6 +46,11 @@ export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValida
subUrl: "topics",
fn: new GetTopicsUseCase()
})
this.subRoutes.push({
method: "POST",
subUrl: "delete/bt",
fn: new DeleteBehaviorTreeScenario(),
})
}
}

View file

@ -0,0 +1,16 @@
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { StaticFilesProject } from "../../../core/models/static_files";
import { DeleteDataBaseModelUseCase } from "../../../core/usecases/delete_database_model_usecase";
import { DeleteRecursiveFolderUseCase } from "../../../core/usecases/delete_recursive_folder_usecase";
import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_database_model_usecase";
import { CoreValidation } from "../../../core/validations/core_validation";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { IProjectModel, ProjectDBModel } from "../../projects/models/project_model_database_model";
import { BehaviorTreeDBModel } from "../models/behavior_tree_database_model";
export class DeleteBehaviorTreeScenario extends CallbackStrategyWithIdQuery {
idValidationExpression: CoreValidation = new MongoIdValidation();
call = async (id: string): ResponseBase => (
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call({ isActive: true }, "no active projects")
).map(async (model) => (await (new DeleteRecursiveFolderUseCase().call(`${model.rootDir}/${StaticFilesProject.behaviorTrees}`))).map(async () => (await new DeleteDataBaseModelUseCase(BehaviorTreeDBModel).call(id))));
}

View file

@ -2,12 +2,13 @@ import { IsMongoId, IsNumber, IsString } from "class-validator";
import { IBehaviorTreeModel } from "./behavior_tree_database_model";
export class BehaviorTreeValidationModel implements IBehaviorTreeModel {
_id: string;
@IsString()
public name: string;
@IsMongoId()
public project:string;
public skills:any;
public xml:string;
public project: string;
public skills: any;
public xml: string;
@IsNumber()
public unixTime: number
}

View file

@ -4,6 +4,8 @@ import { CalculationInstanceValidationModel } from "./models/calculations_instan
import { CalculationInstanceDBModel } from "./models/calculations_instance_database_model";
import { CreateCalculationInstanceScenario } from "./domain/create_calculation_instance_scenario";
import { DeleteCalculationsInstanceScenario } from "./domain/delete_calculations_instance_scenario";
import { GetAllEndCalculationsInstanceActiveProjectScenarios } from "./domain/get_all_end_calculations_instance_active_project_scenarios";
export class CalculationsInstancesPresentation extends CrudController<
CalculationInstanceValidationModel,
@ -18,7 +20,12 @@ export class CalculationsInstancesPresentation extends CrudController<
super.delete(new DeleteCalculationsInstanceScenario().call);
super.post(new CreateCalculationInstanceScenario().call);
this.subRoutes.push({
method: "POST",
subUrl: "get/all/end/calculations",
//@ts-expect-error
fn: new GetAllEndCalculationsInstanceActiveProjectScenarios(),
})
this.subRoutes.push({
method: "GET",
subUrl: "exec",

View file

@ -7,7 +7,21 @@ import { ProcessWatcherAndDatabaseUpdateService } from "../../datasets/domain/cr
import { CalculationInstanceDBModel, ICalculationInstance } from "../models/calculations_instance_database_model";
import { Result } from "../../../core/helpers/result";
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
import { ExecProcessScenarioV2, ExecProcessWatcher } from "../../../core/scenarios/exec_process_scenario_v2";
class ExecProcess extends ExecProcessWatcher {
id: string;
constructor(id: string) {
super()
this.id = id;
}
result = async (): Promise<any> => (await new ReadByIdDataBaseModelUseCase<ICalculationInstance>(CalculationInstanceDBModel).call(this.id)).map(async (model) => {
model.lastProcessLogs = this.logs.join('\n')
model.processStatus = this.status;
// @ts-ignore
await model.save()
});
}
export class ExecCalculationInstanceProcessScenario extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
call = async (id: string): ResponseBase =>
@ -17,19 +31,15 @@ export class ExecCalculationInstanceProcessScenario extends CallbackStrategyWith
return (await new IsHaveActiveProcessUseCase().call()).map(async () => {
const execCommand = `${model.script
} --path ${model.instancePath.pathNormalize()} --form ${fileOutPath}`.replace("\n", "");
await new CreateFileUseCase().call(fileOutPath, Buffer.from(JSON.stringify(model.formBuilder)));
await CalculationInstanceDBModel.findById(id).updateOne({
processStatus: "RUN",
lastProcessExecCommand: execCommand,
});
new ExecProcessUseCase().call(
// @ts-expect-error
`${model.project.rootDir}/`,
execCommand,
id,
new ProcessWatcherAndDatabaseUpdateService(id as unknown as ObjectId, CalculationInstanceDBModel)
);
new ExecProcessScenarioV2().call(execCommand, new ExecProcess(model._id))
return Result.ok("OK");
});
}

View file

@ -0,0 +1,20 @@
import { IsString } from "class-validator";
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { SearchManyDataBaseModelUseCase } from "../../../core/usecases/search_many_database_model_usecase";
import { GetActiveProjectIdScenario } from "../../projects/domain/get_active_project_id_scenario";
import { ICalculationInstance, CalculationInstanceDBModel } from "../models/calculations_instance_database_model";
export class CalculationInstanceType {
@IsString()
type: string;
}
export class GetAllEndCalculationsInstanceActiveProjectScenarios extends CallbackStrategyWithValidationModel<CalculationInstanceType> {
validationModel: CalculationInstanceType = new CalculationInstanceType();
call = async (model: CalculationInstanceType): ResponseBase => (await new GetActiveProjectIdScenario().call()).map(
async (activeProjectModel) => await new SearchManyDataBaseModelUseCase<ICalculationInstance>(CalculationInstanceDBModel).call({ project: activeProjectModel.id, isEnd: true, type: model.type }),
);
}

View file

@ -0,0 +1,11 @@
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_database_model_usecase";
import { CoreValidation } from "../../../core/validations/core_validation";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { ICalculationInstance, CalculationInstanceDBModel } from "../models/calculations_instance_database_model";
// export class LogToProcessUseCase extends CallbackStrategyWithIdQuery {
// idValidationExpression: CoreValidation = new MongoIdValidation();
// call = async (id: string): ResponseBase => (await new ReadByIdDataBaseModelUseCase<ICalculationInstance>(CalculationInstanceDBModel).call(id)).map((model) => );
// }

View file

@ -3,6 +3,7 @@ import { FormBuilderValidationModel } from "../../datasets/models/dataset_valida
import { IProjectModel, projectSchema } from "../../projects/models/project_model_database_model";
export interface ICalculationInstance {
_id: string;
script: string;
instancePath: string;
formBuilder: FormBuilderValidationModel;
@ -61,6 +62,7 @@ export const CalculationInstanceSchema = new Schema({
instancePath: {
type: String,
},
project: {
type: Schema.Types.ObjectId,
ref: projectSchema,

View file

@ -4,6 +4,7 @@ import { FormBuilderValidationModel } from "../../datasets/models/dataset_valida
import { IProjectModel } from "../../projects/models/project_model_database_model";
export class CalculationInstanceValidationModel implements ICalculationInstance {
_id: string;
@IsNotEmpty()
@IsString()
instanceName: string;

View file

@ -15,6 +15,7 @@ export enum ProcessStatus {
END = "END",
ERROR = "ERROR",
NEW = "NEW",
}
export interface IDatasetModel {
_id?: string;

View file

@ -21,8 +21,7 @@ export class ExecInstanceScenario extends CallbackStrategyWithIdQuery {
await new ReadByIdDataBaseModelUseCase<IDigitalTwinsInstanceModel>(DigitalTwinsInstanceDatabaseModel).call(id)
).map(
(document) => (
console.log('DOCUMeNT PATH'),
console.log(document.instancePath.pathNormalize()),
new ExecProcessUseCase().call(
document.instancePath,
`python3 $GET_INTERFACES --path ${document.instancePath.pathNormalize()} --package '${JSON.stringify(

View file

@ -4,9 +4,8 @@ import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_dat
import { IProjectModel, ProjectDBModel } from "../models/project_model_database_model";
export class GetActiveProjectIdScenario extends CallbackStrategyWithEmpty {
async call(): Promise<Result<any, { id: string }>> {
return (
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call({ isActive: true }, "no active projects")
).map((model) => Result.ok({ id: model._id }));
}
call = async (): Promise<Result<any, { id: string }>> => (
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call({ isActive: true }, "no active projects")
).map((model) => Result.ok({ id: model._id }));
}

View file

@ -35,7 +35,6 @@ export class RobossemblerAssetsNetworkMapperScenario extends CallbackStrategyWit
"/assets/";
model.map((el) => {
const assetLibsAddress = assetAddress + "/libs/objects/" + el.name;
console.log(assetLibsAddress);
el.stlUrl = `${assetAddress}${el.part_path}`;
el.glUrl = `${assetLibsAddress}.glb`;
el.daeUrl = `${assetLibsAddress}.dae`;

View file

@ -1,7 +1,53 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import {
CallbackStrategyWithIdQuery,
CallbackStrategyWithValidationModel,
ResponseBase,
} from "../../../core/controllers/http_controller";
import { StaticFilesProject } from "../../../core/models/static_files";
import {
ExecProcessScenarioV2,
ExecProcessWatcher,
activeProcessPids,
} from "../../../core/scenarios/exec_process_scenario_v2";
import { ReadByIdDataBaseModelScenario } from "../../../core/scenarios/read_by_id_database_model_scenario";
import { SearchOneDataBaseModelUseCase } from "../../../core/usecases/search_database_model_usecase";
import { CoreValidation } from "../../../core/validations/core_validation";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { BehaviorTreeDBModel } from "../../behavior_trees/models/behavior_tree_database_model";
import { BehaviorTreeValidationModel } from "../../behavior_trees/models/behavior_tree_validation_model";
import { IProjectModel, ProjectDBModel } from "../../projects/models/project_model_database_model";
import { GetCommandScenario } from "./get_command_scenario";
export class ExecBtBuilderUseCase extends CallbackStrategyWithEmpty {
call(): ResponseBase {
throw new Error("Method not implemented.");
class ProcessWatcher extends ExecProcessWatcher {
async result(): Promise<any> {
console.log(this);
}
}
export enum Proceed {
EXEC_BT = "EXEC_BT",
}
export class ExecBtScenario extends CallbackStrategyWithIdQuery {
idValidationExpression: CoreValidation = new MongoIdValidation();
call = async (id: string): ResponseBase =>
(await new ReadByIdDataBaseModelScenario<BehaviorTreeValidationModel>(BehaviorTreeDBModel).call(id)).map(
async (behaviorTreeValidationModel) =>
(
await new SearchOneDataBaseModelUseCase<IProjectModel>(ProjectDBModel).call(
{ isActive: true },
"no active projects"
)
).map(async (model) =>
(await new GetCommandScenario().call("btRuntimeProcess")).map((execProcessData) =>
new ExecProcessScenarioV2().call(
execProcessData.execCommand.replace(
"${bt_path}",
`${model.rootDir}/${StaticFilesProject.behaviorTrees}/${behaviorTreeValidationModel.name}/bt.xml`
),
new ProcessWatcher(),
Proceed.EXEC_BT
)
)
)
);
}

View file

@ -0,0 +1,7 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { activeProcessPids } from "../../../core/scenarios/exec_process_scenario_v2";
export class GetRunTimeStatuses extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => Result.ok(activeProcessPids);
}

View file

@ -1,10 +0,0 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { SpawnProcessUseCase } from "../../../core/usecases/exec_process_usecase";
import { GetCommandScenario } from "./get_command_scenario";
import { ProcessWatcher } from "../service/process_watcher";
import { App } from "../../../core/controllers/app";
export class GetSimulationStateScenario extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => Result.ok("");
}

View file

@ -0,0 +1,51 @@
import { IsString } from "class-validator";
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
import { ProcessStatus, activeProcessPids } from "../../../core/scenarios/exec_process_scenario_v2";
import { Result } from "../../../core/helpers/result";
import { GetRootDirUseCase } from "../../../core/usecases/get_root_dir_usecase";
import { exec } from 'child_process';
export class StopProcessModel {
@IsString()
pid: string
}
export class StopProcessUseCase extends CallbackStrategyWithValidationModel<StopProcessModel> {
validationModel: StopProcessModel = new StopProcessModel();
call = async (model: StopProcessModel): ResponseBase => {
try {
if (activeProcessPids[model.pid] === undefined) {
return Result.error('missing pid');
}
const processKillStatus = await new Promise<string>((resolve, reject) => {
try {
exec(`kill ${activeProcessPids[model.pid].pid}`, { cwd: new GetRootDirUseCase().call() }, (error, stdout, stderr) => {
if (error) {
reject(`Ошибка: ${stderr}`); return;
}
if (stderr) {
reject(`Ошибка: ${stderr}`);
return;
}
resolve('Process kill')
});
} catch (e) {
resolve('Process kill')
}
})
if (processKillStatus == 'Process kill') {
activeProcessPids[model.pid].status = ProcessStatus.userDelete;
}
return Result.ok(processKillStatus);
} catch (error) {
return Result.ok(error)
}
}
}

View file

@ -11,4 +11,4 @@ export interface ExecProcess {
delay: number;
checkCommand: null;
filter: null;
}
}

View file

@ -1,42 +1,19 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { ExecRunTimeCommandValidationModel } from "./model/run_time_validation_model";
import { ExecRuntimeDatabaseModel } from "./model/run_time_database_model";
import { CoreHttpController, SubRouter, HttpMethodType, CallbackStrategyWithIdQuery, ResponseBase } from "../../core/controllers/http_controller";
import { ExecBtBuilderUseCase } from "./domain/exec_bt_builder_usecase";
import { ExecSimulationUseCase } from "./domain/exec_simulation_usecase";
import { GetBtBuilderStateUseCase } from "./domain/get_bt_builder_status_usecase";
import { GetSimulationStateScenario } from "./domain/get_simulation_state_usecase";
import { MongoIdValidation } from "../../core/validations/mongo_id_validation";
import { CoreValidation } from "../../core/validations/core_validation";
import { ReadByIdDataBaseModelUseCase } from "../../core/usecases/read_by_id_database_model_usecase";
import { ICalculationInstance, CalculationInstanceDBModel } from "../calculations_instance/models/calculations_instance_database_model";
import { Result } from "../../core/helpers/result";
import { SpawnProcessUseCase } from "../../core/usecases/exec_process_usecase";
import { ProcessWatcher } from "./service/process_watcher";
import { CoreHttpController, SubRouter, CallbackStrategyWithIdQuery } from "../../core/controllers/http_controller";
import { ExecBtScenario } from "./domain/exec_bt_builder_usecase";
import { GetRunTimeStatuses } from "./domain/get_run_time_statuses_usecase";
import { StopProcessUseCase } from "./domain/stop_process_usecase";
class ExecAnalyzeScenario extends CallbackStrategyWithIdQuery {
idValidationExpression: CoreValidation = new MongoIdValidation()
call = async (id: string) =>
(await new ReadByIdDataBaseModelUseCase<ICalculationInstance>(CalculationInstanceDBModel).call(id)).map(async (model) =>
(await new SpawnProcessUseCase().call('/Users/idontsudo/webservice', `nix run github:nixos/nixpkgs#python312Packages.tensorboard -- --logdir ${model.instancePath}`, "", new ProcessWatcher())).map(() => Result.ok('ok'),),
)
}
export class RunTimePresentation extends CrudController<ExecRunTimeCommandValidationModel, any> {
export class RunTimePresentation extends CoreHttpController<ExecRunTimeCommandValidationModel> {
constructor() {
super({
url: "run_time",
});
this.subRoutes.push(new SubRouter("POST", "/exec/bt/builder", new ExecBtBuilderUseCase()));
this.subRoutes.push(new SubRouter("POST", "/get/bt/builder/state", new GetBtBuilderStateUseCase()));
this.subRoutes.push(new SubRouter("POST", "/get/simulator/state", new GetSimulationStateScenario()));
this.subRoutes.push(new SubRouter('POST', "exec/analyze", new ExecAnalyzeScenario()))
this.subRoutes.push({
method: "POST",
subUrl: "/exec/simulation/",
fn: new ExecSimulationUseCase(),
});
this.subRoutes.push(new SubRouter<any>("POST", "exec/bt", new ExecBtScenario()));
this.subRoutes.push(new SubRouter("GET", "status", new GetRunTimeStatuses()));
this.subRoutes.push(new SubRouter<any>("POST", "kill", new StopProcessUseCase()))
}
}

View file

@ -3,11 +3,11 @@ import { App } from "./core/controllers/app";
import { SocketSubscriber } from "./core/controllers/socket_controller";
import { extensions } from "./core/extensions/extensions";
import { httpRoutes } from "./core/controllers/routes";
import { SpawnProcessUseCase, executorProgramService } from "./core/usecases/exec_process_usecase";
import { ProcessWatcher } from "./features/runtime/service/process_watcher";
import { executorProgramService } from "./core/usecases/exec_process_usecase";
import { executorProgramServiceV2 } from "./core/scenarios/exec_process_scenario_v2";
extensions();
const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime")];
const socketSubscribers = [new SocketSubscriber(executorProgramService, "realtime"), new SocketSubscriber(executorProgramServiceV2, 'realtimeV2',)];
new App(httpRoutes, socketSubscribers).listen();

View file

@ -1,7 +1,14 @@
/* eslint-disable no-extend-native */
import { Result } from "../helper/result";
/* eslint-disable @typescript-eslint/no-this-alias */
export const ArrayExtensions = () => {
if (Array.prototype.plus === undefined) {
Array.prototype.plus = function (array) {
array.forEach((el) => this.push(el))
return this;
}
}
if ([].indexOfR === undefined) {
Array.prototype.indexOfR = function (element) {
if (this.indexOf(element) === -1) {
@ -10,6 +17,11 @@ export const ArrayExtensions = () => {
return Result.ok(this);
};
}
if ([].whereOne === undefined) {
Array.prototype.whereOne = function (predicate) {
return this.filter(predicate).atR(0);
}
}
if ([].atR === undefined) {
Array.prototype.atR = function (index) {
if (index === undefined) {

View file

@ -30,6 +30,8 @@ declare global {
someR(predicate: (value: T) => boolean): Result<void, Array<T>>;
updateAll(value: Partial<T>): Array<T>;
atR(index: number | undefined): Result<void, T>;
whereOne(predicate: (value: T) => boolean): Result<void, T>;
plus(array: any[]): Array<T>
}
interface Date {
formatDate(): string;

View file

@ -1,23 +1,15 @@
import { IsNotEmpty, IsString } from "class-validator";
import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store";
import {
datasetFormMockContext,
datasetFormMockResult,
defaultFormValue,
} from "../../features/dataset/dataset_model";
import { datasetFormMockContext, datasetFormMockResult, defaultFormValue } from "../../features/dataset/dataset_model";
import { DependencyViewModel } from "./skill_model";
import { ValidationModel } from "./validation_model";
import { FormType } from "./form";
import makeAutoObservable from "mobx-store-inheritance";
export class FormBuilderValidationModel
extends ValidationModel
implements DependencyViewModel
{
export class FormBuilderValidationModel extends ValidationModel implements DependencyViewModel {
@IsNotEmpty()
@IsString()
public result: string;
@IsNotEmpty()
@IsString()
public context: string;
public form: string[];
@ -36,22 +28,26 @@ export class FormBuilderValidationModel
formBuilderValidationModel.context.isEmpty() &&
formBuilderValidationModel.result.isEmpty() &&
formBuilderValidationModel.form.isEmpty();
static test = () =>
new FormBuilderValidationModel(ffContext, ff1Result, [], "");
static test = () => new FormBuilderValidationModel(ffContext, ff1Result, [], "");
static datasetEmpty = () =>
new FormBuilderValidationModel(
datasetFormMockContext,
datasetFormMockResult,
[],
defaultFormValue
);
new FormBuilderValidationModel(datasetFormMockContext, datasetFormMockResult, [], defaultFormValue);
static empty = () => new FormBuilderValidationModel("", "", [], "");
static emptyTest = () =>
new FormBuilderValidationModel(``, ``, [], defaultFormValue);
static creteDataSetTest = () =>
new FormBuilderValidationModel(``, scene, [], "");
static emptySimple = () =>
new FormBuilderValidationModel("", simpleFormBuilder, [], "");
static emptyTest = () => new FormBuilderValidationModel(``, ``, [], defaultFormValue);
static creteDataSetTest = () => new FormBuilderValidationModel(``, scene, [], "");
static emptySimple = () => new FormBuilderValidationModel("", simpleFormBuilder, [], "");
static eee = () =>
new FormBuilderValidationModel(
``,
`{
"robot_name": \${ROBOT_NAME:string:rbs_arm},
"pose": {
"position": { "x": \${X:number:0.1}, "y": \${Y:number:0.1}, "z": \${Z:number:0.7} },
"orientation": { "x": \${X:number:0.1}, "y": \${Y:number:0.1}, "z": \${Z:number:0.7} }
}
}`,
[],
""
);
static vision = () =>
new FormBuilderValidationModel(
`
@ -96,6 +92,7 @@ export class FormBuilderValidationModel
};`,
`
{
"process":\${<SelectProcess/>:OBJECT:{"type": "OBJECT_DETECTION"},
"datasetObjects":\${<SelectDetail/>:OBJECT:{"details": []},
"typedataset": \${typedataset:Enum<T>:ObjectDetection},
"models_randomization":{
@ -142,3 +139,6 @@ export const ff1Result = `{
"empty":\${NAME:string:default},
"params": \${ITEM:Array<ITEM>:[]}
}`;
// {
// "process":\${<SelectProcess/>:OBJECT:{"type": "OBJECT_DETECTION"}
// }

View file

@ -36,7 +36,7 @@ export interface IWeightsDependency {
export interface IDeviceDependency {
sid: string;
}
export interface IDependency {}
export interface IDependency { }
export interface IParam {
isFilled: boolean;
type: string;
@ -276,7 +276,7 @@ export class SkillModel extends ValidationModel implements ISkill {
}
export class SkillDependency implements IDependency {
constructor(public skills: ISkillDependency[]) {}
constructor(public skills: ISkillDependency[]) { }
static empty() {
return new SkillDependency([]);
}
@ -424,24 +424,27 @@ export class Skills {
.flat(1)
.filter((el) => el !== "");
getDependencyBySkillLabelAndType = (skillType: string, sid: string): DependencyViewModel =>
this.skills
.reduce<DependencyViewModel[]>((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.map((action) => {
action.param.map((param) => {
if (param.type.isEqualR(skillType)) {
acc.push(param?.dependency ?? DependencyViewModel.empty());
}
return param;
});
return action;
});
}
getDependencyBySkillLabelAndType = (skillType: string, sid: string): DependencyViewModel => this.skills
.reduce<DependencyViewModel[]>((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.map((action) => {
action.param.map((param) => {
return acc;
}, [])
.at(0) ?? DependencyViewModel.empty();
if (param.type.isEqual(skillType)) {
acc.push(param?.dependency ?? DependencyViewModel.empty());
}
return param;
});
return action;
});
}
return acc;
}, [])
.at(0) ?? DependencyViewModel.empty()
static isEmpty(model: Skills): Result<void, void> {
if (model.skills.isEmpty()) {
return Result.error(undefined);

View file

@ -1,3 +1,4 @@
import { message } from "antd";
import { Result } from "../helper/result";
import { validate, ValidationError } from "class-validator";
@ -26,4 +27,12 @@ export class ValidationModel {
return Result.ok(this as unknown as T);
}
};
validMessage = async<T>(): Promise<Result<string, T>> => {
const result = await this.valid<T>();
if (result.isFailure()) {
message.error(result.error);
}
return result;
}
}

View file

@ -132,7 +132,7 @@ export class CoreHttpRepository extends HttpRepository {
getAssetsActiveProject = async (): Promise<Result<HttpError, Parts[]>> => {
return this._jsonRequest<Parts[]>(HttpMethod.GET, "/projects/assets");
};
getSceneAsset = (id: string) =>
this._jsonToClassInstanceRequest(HttpMethod.GET, `/scenes/by_id?id=${id}`, SceneAsset) as Promise<
Result<HttpError, SceneAsset>
@ -141,5 +141,6 @@ export class CoreHttpRepository extends HttpRepository {
return this._jsonRequest<UUID>(HttpMethod.GET, "/projects/get/active/project/id");
}
getAllScenes = () => this._jsonRequest<SceneModel[]>(HttpMethod.GET, "/scenes");
getAllTopics = () => this._jsonRequest(HttpMethod.GET, `/topics`);
}

View file

@ -1,23 +1,27 @@
import { Socket, io } from "socket.io-client";
import { Result } from "../helper/result";
import { TypedEvent } from "../helper/typed_event";
import { RuntimeModel } from "../../features/behavior_tree_builder/presentation/ui/actions/runtime_actions";
export class SocketRepository extends TypedEvent<any> {
serverURL = "ws://localhost:4001";
socket: Socket | undefined;
async connect():Promise<Result<boolean, boolean>> {
async connect(): Promise<Result<boolean, boolean>> {
const socket = io(this.serverURL);
this.socket = socket;
socket.connect();
socket.on('realtime', (d) =>{
socketCoreInstances.forEach((el) => {
socket.on(el.event, (event) => el.emit(event))
})
socket.on('realtime', (d) => {
this.emit({
event:"realtime",
payload:d
event: "realtime",
payload: d
})
})
if(socket.connected){
if (socket.connected) {
return Result.ok(true)
}
return Result.error(false)
@ -25,4 +29,15 @@ export class SocketRepository extends TypedEvent<any> {
}
export const socketRepository = new SocketRepository()
export const socketRepository = new SocketRepository()
export abstract class SocketCore<T> extends TypedEvent<T> {
abstract event: string
}
export class RunTimeSocketRepository extends SocketCore<RuntimeModel> {
event = 'realtimeV2';
}
export const runTimeSocketRepository = new RunTimeSocketRepository();
const socketCoreInstances: SocketCore<any>[] = [runTimeSocketRepository]

View file

@ -17,6 +17,7 @@ interface IMessage {
errorMessage?: string;
}
export abstract class UiLoader {
navigate?: NavigateFunction;
isLoading = false;
async httpHelper<T>(callBack: Promise<Result<any, T>>) {
this.isLoading = true;
@ -75,7 +76,7 @@ export abstract class UiErrorState<T> extends UiLoader {
console.log(error);
};
abstract init(navigate?: NavigateFunction): Promise<any>;
dispose() {}
dispose() { }
errors: UiBaseError[] = [];
}
@ -126,7 +127,17 @@ export abstract class FormState<V, E> extends UiErrorState<E> {
loadClassInstance = (instance: ClassConstructor<V>, viewModel: V) => {
this.viewModel = plainToInstance(instance, viewModel);
};
}
isModalOpen: boolean = false;
modalShow = () => {
this.isModalOpen = true;
};
modalClickOk = () => {
this.isModalOpen = false;
};
modalCancel = () => {
this.isModalOpen = false;
};
}

View file

@ -7,20 +7,20 @@ export const DrawerV2: React.FC<{
title?: string;
onClose: () => void;
children: React.ReactNode;
}> = ({ isOpen, onClose, children, title }) => {
width?: number;
}> = ({ isOpen, onClose, children, title, width }) => {
return (
<div
style={{
position: "fixed",
top: 0,
right: isOpen ? 0 : -300,
width: 300,
right: isOpen ? 0 : (width ?? 300) * -1,
width: width ?? 300,
height: "100%",
backgroundColor: themeStore.theme.darkSurface,
backgroundColor: "#880ef8",
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.5)",
transition: "right 0.3s ease",
zIndex: 1000,
}}
>
<div style={{ height: "100%", width: "100%" }}>

View file

@ -1,9 +1,5 @@
import * as React from "react";
import {
FormViewModel,
InputBuilderViewModel,
InputType,
} from "./form_view_model";
import { FormViewModel, InputBuilderViewModel, InputType } from "./form_view_model";
import { observer } from "mobx-react-lite";
import { FormBuilderStore } from "./form_builder_store";
import { CoreSelect } from "../select/select";
@ -13,219 +9,162 @@ import { CoreText, CoreTextType } from "../text/text";
import { getFormBuilderComponents } from "./forms/form_builder_components";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export interface IFormBuilder {
formBuilder: FormBuilderValidationModel;
onChange: (change: FormBuilderValidationModel) => void;
}
export const FormBuilder = observer(
(props: { formBuilder: FormBuilderValidationModel; onChange: (change: FormBuilderValidationModel) => void }) => {
const [store] = React.useState(() => new FormBuilderStore());
export const FormBuilder = observer((props: IFormBuilder) => {
const [store] = React.useState(() => new FormBuilderStore());
React.useEffect(() => {
store.init(props.formBuilder.context, props.formBuilder.result);
if (props.formBuilder.form.isNotEmpty()) {
store.formViewModel = new FormViewModel(
props.formBuilder.form.map((el) => InputBuilderViewModel.fromJSON(el)),
props.formBuilder.result,
props.formBuilder.context
);
props.formBuilder.form.map((el) => InputBuilderViewModel.fromJSON(el));
}
store.changerForm.on((event) => {
if (event) props.onChange(event);
});
}, []);
React.useEffect(() => {
store.init(props.formBuilder.context, props.formBuilder.result);
if (props.formBuilder.form.isNotEmpty()) {
store.formViewModel = new FormViewModel(
props.formBuilder.form.map((el) => InputBuilderViewModel.fromJSON(el)),
props.formBuilder.result,
props.formBuilder.context
);
props.formBuilder.form.map((el) => InputBuilderViewModel.fromJSON(el));
}
store.changerForm.on((event) => {
if (event) props.onChange(event);
});
}, []);
return (
<div>
{store.isError ? (
<>Error</>
) : (
<div>
{store.formViewModel?.inputs?.map((element, index) => {
if (element.type?.isEqual(InputType.ENUM)) {
const values = element.values as string[];
return (
<CoreSelect
key={index}
items={values}
value={element.totalValue ?? element.defaultValue}
onChange={(value) => store.changeTotalValue(element.id, value)}
label={element.name}
style={{ margin: 20 }}
/>
);
}
if (element.type?.isEqual(InputType.ARRAY)) {
return (
<div key={index} style={{ border: "1px black solid", margin: 20 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
margin: 20,
alignItems: "center",
paddingRight: 20,
}}
onClick={() => {
store.open(element.id);
}}
>
<CoreText text={element.name} type={CoreTextType.large} />
<Icon type="PlusCircle" style={{ width: 33 }} />
</div>
return (
<div>
{store.isError ? (
<>Error</>
) : (
<div>
{store.formViewModel?.inputs?.map((element, index) => {
if (element.type?.isEqual(InputType.ENUM)) {
const values = element.values as string[];
return (
<CoreSelect
key={index}
items={values}
value={element.totalValue ?? element.defaultValue}
onChange={(value) =>
store.changeTotalValue(element.id, value)
}
label={element.name}
style={{ margin: 20 }}
/>
);
}
if (element.type?.isEqual(InputType.ARRAY)) {
return (
<div
key={index}
style={{ border: "1px black solid", margin: 20 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
margin: 20,
alignItems: "center",
paddingRight: 20,
}}
onClick={() => {
store.open(element.id);
}}
>
<CoreText text={element.name} type={CoreTextType.large} />
<Icon type="PlusCircle" style={{ width: 33 }} />
</div>
{element.isOpen ? (
<div style={{ margin: 20 }}>
{element.totalValue instanceof Array
? element.totalValue?.map((subArray, index) => {
return (
<div style={{ margin: 20 }}>
<div style={{ display: "flex" }}>
<CoreText text={(element.subType ?? "") + ` ${index}`} type={CoreTextType.medium} />
<Icon
style={{ paddingLeft: 20 }}
type="DeleteCircle"
onClick={() => store.deleteTotalValueSubItem(element.id, index)}
/>
</div>
{element.isOpen ? (
<div style={{ margin: 20 }}>
{element.totalValue instanceof Array
? element.totalValue?.map((subArray, index) => {
return (
<div style={{ margin: 20 }}>
<div style={{ display: "flex" }}>
<CoreText
text={(element.subType ?? "") + ` ${index}`}
type={CoreTextType.medium}
/>
<Icon
style={{ paddingLeft: 20 }}
type="DeleteCircle"
onClick={() =>
store.deleteTotalValueSubItem(
element.id,
index
)
}
/>
</div>
{subArray.map(
(
subSubArrayItem: InputBuilderViewModel,
subIndex: number
) => {
if (
subSubArrayItem.type.isEqual(
InputType.ENUM
)
) {
{subArray.map((subSubArrayItem: InputBuilderViewModel, subIndex: number) => {
if (subSubArrayItem.type.isEqual(InputType.ENUM)) {
return (
<>
<CoreSelect
items={
subSubArrayItem.values?.map(
(el) => String(el)
) ?? []
}
value={
subSubArrayItem.totalValue ??
subSubArrayItem.defaultValue
}
onChange={(value) => console.log(subSubArrayItem.id)
}
items={subSubArrayItem.values?.map((el) => String(el)) ?? []}
value={subSubArrayItem.totalValue ?? subSubArrayItem.defaultValue}
onChange={(value) => console.log(subSubArrayItem.id)}
label={element.name}
style={{ margin: 5 }}
/>
</>
);
}
if (
subSubArrayItem.type.isEqualMany([
InputType.NUMBER,
InputType.STRING,
])
)
if (subSubArrayItem.type.isEqualMany([InputType.NUMBER, InputType.STRING]))
return (
<div>
<CoreInput
isFormBuilder={true}
style={{ margin: 5 }}
onChange={(e) =>
store.changeTotalSubValue(
element.id,
subIndex,
e,
index
)
}
onChange={(e) => store.changeTotalSubValue(element.id, subIndex, e, index)}
validation={
subSubArrayItem.type.isEqual(
InputType.NUMBER
)
subSubArrayItem.type.isEqual(InputType.NUMBER)
? (el) => Number().isValid(el)
: undefined
}
error="только числа"
value={
subSubArrayItem.totalValue ??
subSubArrayItem.defaultValue
}
value={subSubArrayItem.totalValue ?? subSubArrayItem.defaultValue}
label={subSubArrayItem.name}
/>
</div>
);
return <>Error</>;
}
)}
</div>
);
})
: null}
</div>
) : null}
</div>
);
}
})}
</div>
);
})
: null}
</div>
) : null}
</div>
);
}
if (element.type?.isEqualMany([InputType.NUMBER, InputType.STRING]))
return (
<div>
<CoreInput
isFormBuilder={true}
validation={
element.type.isEqual(InputType.NUMBER)
? (el) => Number().isValid(el)
: undefined
}
onChange={(e) => {
store.changeTotalValue(element.id, e);
}}
value={element.totalValue ?? element.defaultValue}
error="только числа"
label={element.name}
style={{ margin: 20 }}
/>
</div>
);
if (element.type?.isEqual(InputType.OBJECT))
return (
<>
{getFormBuilderComponents(
element.name
.replace(">", "")
.replace("<", "")
.replace("/", ""),
element.totalValue ?? element.defaultValue,
(text) => store.changeTotalValue(element.id, text)
).fold(
(s) => (
<>{s}</>
),
(error) => (
<>{error}</>
)
)}
</>
);
if (element.type?.isEqualMany([InputType.NUMBER, InputType.STRING]))
return (
<div>
<CoreInput
validation={element.type.isEqual(InputType.NUMBER) ? (el) => Number().isValid(el) : undefined}
onChange={(e) => {
store.changeTotalValue(element.id, e);
}}
value={element.totalValue ?? element.defaultValue}
error="только числа"
label={element.name}
style={{ margin: 20 }}
/>
</div>
);
if (element.type?.isEqual(InputType.OBJECT))
return (
<>
{getFormBuilderComponents(
element.name.replace(">", "").replace("<", "").replace("/", ""),
element.totalValue ?? element.defaultValue,
(text) => store.changeTotalValue(element.id, text)
).fold(
(s) => (
<>{s}</>
),
(error) => (
<>{error}</>
)
)}
</>
);
return <div>Error {String(element)}</div>;
})}
</div>
)}
</div>
);
});
})}
</div>
)}
</div>
);
}
);

View file

@ -95,7 +95,7 @@ export class FormViewModel {
.replace(/[^\x00-\x7F]/g, "")
.replaceAll("\n", "")
.replaceAll("\\", "")
// .replaceAll("/", "")
// .replaceAll("/", "")
);
} catch (error) {
console.log("ERROR: FormViewModel json() " + result);
@ -190,8 +190,9 @@ export class FormViewModel {
});
return result as unknown as string;
}
static fromString(result: string, context: string): Result<void, FormViewModel> {
static fromString(result: string = '', context: string = ''): Result<void, FormViewModel> {
try {
if (result.isEmpty() && context.isEmpty()) {
return Result.error(undefined);
}

View file

@ -1,10 +1,10 @@
import { Result } from "../../../helper/result";
import { SelectDatasetScreen } from "./select_dataset/presentation/select_dataset_screen";
import { SelectProcess } from "./select_dataset/presentation/select_process";
import { SelectDetail } from "./select_detail/presentation/select_detail_screen";
export enum FormBuilderComponents {
SelectDetail = "SelectDetail",
SelectDataset = "SelectDataset",
SelectProcess = "SelectProcess",
}
export interface IFormBuilderComponentsProps<T> {
dependency: T;
@ -18,8 +18,8 @@ export const getFormBuilderComponents = (
if (name.isEqual(FormBuilderComponents.SelectDetail)) {
return Result.ok(<SelectDetail dependency={dependency} onChange={onChange} />);
}
if (name.isEqual(FormBuilderComponents.SelectDataset)) {
return Result.ok(<SelectDatasetScreen dependency={dependency} onChange={onChange} />);
if (name.isEqual(FormBuilderComponents.SelectProcess)) {
return Result.ok(<SelectProcess dependency={dependency} onChange={onChange} />);
}
return Result.error(name);
};

View file

@ -0,0 +1,5 @@
import { CoreHttpRepository, HttpMethod } from "../../../../../repository/core_http_repository";
export class SelectProcessRepository extends CoreHttpRepository {
getAllProcessByType = (type: string) => this._jsonRequest(HttpMethod.POST, '/calculations/instances/get/all/end/calculations', { type: type })
}

View file

@ -0,0 +1,18 @@
import { IsObject, IsString } from "class-validator";
import { ValidationModel } from "../../../../../model/validation_model";
import { CalculationModel } from "../../../../../../features/calculation_instance/model/calculation_model";
export class SelectProcessModel extends ValidationModel {
@IsString()
type: string = '';
@IsObject()
selectProcess?: SelectProcess;
}
export interface SelectProcess {
value: CalculationModel;
}

View file

@ -1,6 +0,0 @@
import { observer } from "mobx-react-lite";
import { IFormBuilderComponentsProps } from "../../form_builder_components";
export const SelectDatasetScreen = observer((props:IFormBuilderComponentsProps<any>) => {
return <>SELECT DATASET</>;
});

View file

@ -0,0 +1,48 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
import { IFormBuilderComponentsProps } from "../../form_builder_components";
import { useStore } from "../../../../../helper/use_store";
import { SelectProcessStore } from "./select_process_store";
import { useEffect } from "react";
import { SelectProcessModel } from "../model/select_process_model";
import { Loader } from "../../../../loader/loader";
import { CoreSelect } from "../../../../select/select";
import { message } from "antd";
import { CalculationModel } from "../../../../../../features/calculation_instance/model/calculation_model";
export const SelectProcess = observer((props: IFormBuilderComponentsProps<SelectProcessModel>) => {
const [store] = useState(new SelectProcessStore());
useEffect(() => {
if (typeof props.dependency === "string") {
store.loadClassInstance(SelectProcessModel, JSON.parse(props.dependency));
} else {
store.loadClassInstance(SelectProcessModel, props.dependency);
}
store.init();
}, []);
return (
<div>
{store.isLoading ? (
<Loader />
) : (
<div>
<CoreSelect
items={store.calculationInstances.map((el) => el.instanceName)}
value={store.viewModel?.selectProcess?.value.instanceName ?? ""}
label={`Процесс тип ${store?.viewModel?.type ?? ""}`}
onChange={async (value: string, index: number) => {
store.updateForm({
selectProcess: { value: store.calculationInstances.at(index) ?? CalculationModel.empty() },
});
(await store.viewModel.valid<SelectProcessModel>()).fold(
(model) => props.onChange(model),
(error) => message.error(error)
);
}}
/>
</div>
)}
</div>
);
});

View file

@ -0,0 +1,26 @@
import { NavigateFunction } from "react-router-dom";
import { FormState } from "../../../../../store/base_store";
import { SelectProcessModel } from "../model/select_process_model";
import { SelectProcessRepository } from "../data/select_process_repository";
import { CalculationModel } from "../../../../../../features/calculation_instance/model/calculation_model";
import makeAutoObservable from "mobx-store-inheritance";
export class SelectProcessStore extends FormState<SelectProcessModel, any> {
selectProcessRepository = new SelectProcessRepository();
viewModel: SelectProcessModel;
calculationInstances: CalculationModel[] = [];
constructor() {
super();
makeAutoObservable(this);
}
async init(navigate?: NavigateFunction | undefined): Promise<any> {
await this.mapOk('calculationInstances', this.selectProcessRepository.getAllProcessByType(this.viewModel.type));
this.calculationInstances = this.calculationInstances.map((el) => {
// @ts-ignore
delete el['formBuilder'];
return el;
})
}
}

View file

@ -1,21 +1,18 @@
// @ts-nocheck
import React from "react";
import { IFormBuilderComponentsProps } from "../../form_builder_components";
import { observer } from "mobx-react-lite";
import { ListItem } from "./ui/list_item";
import { SelectDetailStore } from "./select_detail_store";
import { SelectDetailViewModel } from "../model/select_details_model";
import { plainToInstance } from "class-transformer";
export const SelectDetail = observer((props: IFormBuilderComponentsProps<SelectDetailViewModel>) => {
const [store] = React.useState(() => new SelectDetailStore());
React.useEffect(() => {
console.log(props.dependency.details);
store.viewModel = new SelectDetailViewModel(props.dependency.details);
store.isLoading = false;
store.init();
}, []);
return (
<div>
{store.isLoading ? (

View file

@ -16,7 +16,6 @@ export const ListItem = (props: IListItemProps) => {
backgroundColor: "rgba(254, 247, 255, 1)",
border: "1px #6750a4 solid",
width: "100%",
height: 110,
display: "flex",
justifyContent: "space-between",
alignItems: "center",

View file

@ -9,7 +9,7 @@ import { FormBuilder } from "./form_builder";
import makeAutoObservable from "mobx-store-inheritance";
class FormBuilderTextStore extends ModalStore {
viewModel = FormBuilderValidationModel.empty();
viewModel = FormBuilderValidationModel.eee();
constructor() {
super();
makeAutoObservable(this);
@ -21,6 +21,7 @@ export const FormBuildTest = observer(() => {
return (
<div>
<InputV2 label={"result"} onChange={(text) => (store.viewModel.result = text)} />
<InputV2 label={"context"} onChange={(text) => (store.viewModel.context = text)} />
<CoreButton text="click" onClick={() => (store.isModalOpen = true)} />
@ -33,15 +34,16 @@ export const FormBuildTest = observer(() => {
onCancel={() => {
store.isModalOpen = false;
}}
>
<FormBuilder
formBuilder={store.viewModel}
onChange={(e) => {
console.log(e.output);
// console.log(JSON.stringify(e.output))
}}
/>
</Modal>
></Modal>
<div style={{height:50}}/>
<FormBuilder
formBuilder={store.viewModel}
onChange={(e) => {
console.log(e)
// console.log(e.output);
console.log(JSON.stringify(e.output))
}}
/>
</div>
);
});

View file

@ -931,6 +931,18 @@ const getIconSvg = (
/>
</svg>
);
case "3points":
return Result.ok(
<svg
xmlns="http://www.w3.org/2000/svg"
width={width ? width : "20"}
height={height ? height : "20"}
viewBox="0 0 128 512"
>
<path d="M64 360a56 56 0 1 0 0 112 56 56 0 1 0 0-112zm0-160a56 56 0 1 0 0 112 56 56 0 1 0 0-112zM120 96A56 56 0 1 0 8 96a56 56 0 1 0 112 0z" />
</svg>
);
case "Move":
return Result.ok(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" xmlSpace="preserve">

View file

@ -16,7 +16,6 @@ interface IInputProps extends IStyle {
type?: CoreInputType;
trim?: boolean;
styleContentEditable?: React.CSSProperties;
isFormBuilder?: boolean;
}
export const CoreInput = (props: IInputProps) => {
@ -29,15 +28,6 @@ export const CoreInput = (props: IInputProps) => {
setAppendInnerText(false);
}
}, [ref, value, isAppendInnerText, setAppendInnerText, props]);
// React.useEffect(() => {
// if (props.isFormBuilder) {
// if (ref.current && props.value) {
// ref.current.innerText = value;
// setValue(props.value);
// console.log(props.value);
// }
// }
// }, [props.value]);
const isSmall = props.type !== undefined && props.type.isEqual(CoreInputType.small);
return (

View file

@ -2,15 +2,16 @@ import { themeStore } from "../../..";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType, FontType } from "../text/text";
interface InputV2Props {
export const InputV2: React.FC<{
style?: React.CSSProperties;
label: string;
value?: string;
trim?: boolean;
validation?: (value: string) => boolean;
error?: string;
height?: number;
onChange?: (text: string) => void;
}
export const InputV2: React.FC<InputV2Props> = ({ label, height, value, onChange, trim }) => {
}> = ({ label, height, value, onChange, trim }) => {
return (
<div
style={{

View file

@ -107,7 +107,7 @@ export const MainPageV2: React.FC<{
overflow: "auto",
}}
>
<div
{/* <div
style={{
alignSelf: "center",
width: 645,
@ -222,7 +222,7 @@ export const MainPageV2: React.FC<{
>
{rightChild}
</div>
</div>
</div> */}
<div style={Object.assign({ width: "100%" }, style)}>{children}</div>
</div>

View file

@ -0,0 +1,46 @@
import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
interface PopoverProps {
content: React.ReactNode;
children: React.ReactNode;
}
const PopoverV2: React.FC<PopoverProps> = ({ content, children }) => {
const [visible, setVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const togglePopover = () => {
setVisible((prev) => !prev);
};
return (
<div
style={{
position: "relative",
display: "inline-block",
}}
onClick={() => togglePopover()}
>
{children}
<div
style={{
position: "absolute",
backgroundColor: "white",
border: "1px solid #ccc;",
borderRadius: "4px",
padding: "10px;",
zIndex: "1000",
transition: "opacity 0.2s ease, visibility 0.2s ease",
opacity: visible ? 1 : 0,
visibility: visible ? "visible" : "hidden",
}}
>
{content}
</div>
</div>
);
};
// visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
// opacity: ${({ visible }) => (visible ? 1 : 0)};
// transition: opacity 0.2s ease, visibility 0.2s ease;
export default PopoverV2;

View file

@ -6,7 +6,7 @@ interface ICoreSelectProps extends IStyle {
items: string[];
value: string;
label: string;
onChange: (value: string) => void;
onChange: (value: string, index: number) => void;
}
export const CoreSelect = (props: ICoreSelectProps) => {
const ref = React.useRef<HTMLDivElement>(null);
@ -52,7 +52,7 @@ export const CoreSelect = (props: ICoreSelectProps) => {
key={i}
onClick={() => {
setValue(el);
props.onChange(el);
props.onChange(el, i);
}}
style={{
backgroundColor: "rgba(230, 224, 233, 1)",

View file

@ -21,7 +21,6 @@ export class BehaviorTreeBuilderHttpRepository extends CoreHttpRepository {
`${this.featureApi}/by_id?id=${id}`,
BehaviorTreeModel
) as unknown as Promise<Result<HttpError, BehaviorTreeModel>>;
deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
editBt = async (model: BehaviorTreeModel) => {
await this._jsonRequest(HttpMethod.POST, `${this.featureApi}/fill/tree`, model);
model.__v = undefined

View file

@ -4,20 +4,15 @@ import { createEditor } from "./ui/editor/editor";
import { SkillTree } from "./ui/skill_tree/skill_tree";
import { BehaviorTreeBuilderStore, DrawerState } from "./behavior_tree_builder_store";
import { observer } from "mobx-react-lite";
import { match } from "ts-pattern";
import { Icon } from "../../../core/ui/icons/icons";
import { CoreText, CoreTextType } from "../../../core/ui/text/text";
import { useNavigate, useParams } from "react-router-dom";
import { IForms, forms } from "./ui/forms/forms";
import { ButtonV2, ButtonV2Type } from "../../../core/ui/button/button_v2";
import { CoreCard } from "../../../core/ui/card/card";
import { themeStore } from "../../..";
import { CoreModal } from "../../../core/ui/modal/modal";
import { InputV2 } from "../../../core/ui/input/input_v2";
import { SelectV2 } from "../../../core/ui/select/select_v2";
import { MainPageV2 } from "../../../core/ui/pages/main_page_v2";
import { DrawerV2 } from "../../../core/ui/drawer/drawer";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import PopoverV2 from "../../../core/ui/popover/popover";
export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path";
@ -54,7 +49,9 @@ export const BehaviorTreeBuilderScreen = observer(() => {
if (ref.current) {
// @ts-expect-error
const domReact: DOMReact = ref.current.getBoundingClientRect();
store.dragZoneSetOffset(0, domReact.y, domReact.width, domReact.height);
// УБЕРИ + 300
store.dragZoneSetOffset(0, domReact.y, domReact.width + 300, domReact.height);
}
}, [ref.current]);
@ -66,54 +63,36 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}, []);
return (
<MainPageV2
rightChild={
<>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "end",
paddingRight: 30,
alignItems: "center",
}}
>
{/* <ButtonV2 style={{ height: 40 }} onClick={() => {}} text="Запуск" textColor={themeStore.theme.black} />
<div style={{ width: 10 }} />
<ButtonV2
style={{ height: 40 }}
onClick={() => {}}
text="Стоп"
type={ButtonV2Type.empty}
textColor={themeStore.theme.greenWhite}
/>
<div style={{ width: 10 }} /> */}
{store.isNeedSaveBtn ? (
<div
style={{
backgroundColor: store.isNeedSaveBtn ? themeStore.theme.greenWhite : undefined,
height: 40,
textAlign: "center",
alignContent: "center",
width: 40,
borderRadius: 100,
}}
>
{store.isNeedSaveBtn ? (
<Icon style={{ height: 21 }} onClick={() => store.onClickSaveBehaviorTree()} type="Floppy" />
) : undefined}
</div>
) : (
<></>
)}
</div>
</>
}
style={{ position: "absolute", height: "100%", overflow: "hidden" }}
bgColor={themeStore.theme.black}
children={
<>
<>
<div style={{ position: "absolute", zIndex: 100 }}>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "end",
paddingRight: 30,
alignItems: "center",
}}
>
<div
style={{
backgroundColor: themeStore.theme.greenWhite,
height: 40,
textAlign: "center",
alignContent: "center",
width: 40,
borderRadius: 100,
}}
>
<Icon style={{ height: 21 }} onClick={() => store.onClickSaveBehaviorTree()} type="Floppy" />
</div>
</div>
</div>
<div
style={{
height: "100%",
@ -160,10 +139,45 @@ export const BehaviorTreeBuilderScreen = observer(() => {
}}
/>
</Panel>
<PanelResizeHandle>
<div style={{ width: 10, height: "100%", backgroundColor: "beige" }}></div>
</PanelResizeHandle>
<Panel defaultSize={0}> </Panel>
{store.panels.map((el, index) => (
<>
<PanelResizeHandle
children={
<>
<div style={{ width: 10, height: "100%", backgroundColor: "beige" }}></div>
</>
}
></PanelResizeHandle>
<Panel
style={{ backgroundColor: "rgb(29, 27, 32)" }}
defaultSize={el.size}
onResize={(size) => store.panelResize(size, index)}
>
<div
style={{
display: "flex",
width: "100%",
justifyContent: "space-between",
backgroundColor: "beige",
}}
>
<PopoverV2
children={<Icon type={"3points"} height={26} />}
content={
<div style={{ width: "max-content", padding: 10 }}>
{store.panelActions.map((el, i) => (
<CoreText type={CoreTextType.medium} text={el.name} onClick={() => el.action(index)} />
))}
</div>
}
/>
<div style={{ color: "black", alignContent: "center", paddingRight: 10 }}>{el.name}</div>
</div>
{el.body}
</Panel>
</>
))}
</PanelGroup>
</div>
</>
@ -172,8 +186,17 @@ export const BehaviorTreeBuilderScreen = observer(() => {
title={store.titleDrawer}
onClose={() => store.editDrawer(DrawerState.editThreadBehaviorTree, false)}
isOpen={store.drawers.find((el) => el.name === DrawerState.editThreadBehaviorTree)?.status}
width={window.innerWidth / 2}
>
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
overflow: "auto",
}}
>
<div style={{ height: "100%" }}>
{store.skillTemplates?.getForms(store.selected ?? "").map((formType, index) =>
forms(
@ -186,11 +209,13 @@ export const BehaviorTreeBuilderScreen = observer(() => {
)
.rFind<IForms>((form) => form.name.isEqual(formType))
.fold(
(s) => (
<div key={index} style={{ height: "100%" }}>
{s.component}
</div>
),
(s) => {
return (
<div key={index} style={{ flex: 1 }}>
{s.component}
</div>
);
},
() => (
<div key={index + "error"} style={{ height: "100%" }}>
Error: Unknown form type {formType}

View file

@ -26,6 +26,8 @@ import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model";
import { SceneAsset } from "../../../core/model/scene_asset";
import { themeStore } from "../../..";
import { RunTimeActions } from "./ui/actions/runtime_actions";
import { ITopicModel } from "../../topics/topic_view_model";
interface I2DArea {
x: number;
@ -37,8 +39,31 @@ interface I2DArea {
export enum DrawerState {
editThreadBehaviorTree = "Редактирование",
}
interface IActionPanel {
name: string;
selectIsClosePopover?: boolean;
action: (index: number) => void;
}
export class PanelBody {
body?: React.ReactNode;
size: number = 0;
name: string = "выберите тип панели";
constructor(body: React.ReactNode | undefined, name: string | undefined, size: number | undefined) {
makeAutoObservable(this);
if (name) this.name = name;
if (body) this.body = body;
if (size) this.size = size;
}
}
export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
panelActions: IActionPanel[] = [
{ name: "Добавить панель", action: () => this.addNewPanel() },
{ name: "Убрать панель", action: (index) => this.removePanel(index) },
{ name: "Runtime", action: (index) => this.changePanel(index, "Runtime", <RunTimeActions />) },
];
sceneAsset?: SceneAsset;
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
behaviorTreeModel: BehaviorTreeModel = BehaviorTreeModel.empty();
@ -51,15 +76,15 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
activeProject: string = "";
behaviorTreeBuilderHttpRepository = new BehaviorTreeBuilderHttpRepository();
canRun = true;
isNeedSaveBtn = false;
isNeedSaveBtn = true;
selected: string = "";
selectedSid: string = "";
nodeBehaviorTree: NodeBehaviorTree[] = [];
navigate?: NavigateFunction;
editor?: NodeEditor<Schemes>;
areaPlugin?: AreaPlugin<Schemes, AreaExtra>;
nodeUpdateObserver?: NodeRerenderObserver;
primitiveViewModel: PrimitiveViewModel;
topics: ITopicModel[];
skillTree: ISkillView = {
name: "",
children: [
@ -70,7 +95,12 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
},
],
};
panels: PanelBody[] = [new PanelBody(undefined, undefined, undefined)];
addNewPanel = () => this.panels.push(new PanelBody(undefined, undefined, undefined));
removePanel = (index: number) =>
this.panels.length !== 1
? (this.panels = this.panels.filter((_, i) => i !== index))
: message.error("должна быть хоть одна панель");
constructor() {
super(DrawerState);
makeAutoObservable(this);
@ -86,7 +116,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
this.editor = editor;
this.areaPlugin = area;
};
getAllTopics = () => this.filledOutTemplates.topicsStack;
getAllTopics = () => this.filledOutTemplates.topicsStack.plus(this.topics);
errorHandingStrategy = (_: CoreError) => {};
dragEnd = (e: EventTarget) => {
@ -102,6 +132,13 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
};
drawSkillCheck = (x: number, y: number, name: string) => {
const drawPoint = { x: x, y: y, w: 1, h: 1 };
console.log(
drawPoint.x < this.area!.x + this.area!.w &&
drawPoint.x + drawPoint.w > this.area!.x &&
drawPoint.y < this.area!.y + this.area!.h &&
drawPoint.y + drawPoint.h > this.area!.y
);
if (
drawPoint.x < this.area!.x + this.area!.w &&
drawPoint.x + drawPoint.w > this.area!.x &&
@ -115,7 +152,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
name: name,
id: sid,
});
this.isNeedSaveBtn = true;
if (!name.isEqualMany(Object.keys(SystemPrimitive))) {
this.skillTemplates?.getSkill(name).fold(
(skill) => {
@ -179,7 +216,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
await this.mapOk("sceneAsset", this.behaviorTreeBuilderHttpRepository.getSceneAsset(model.sceneId));
this.isLoading = false;
await this.mapOk("topics", this.behaviorTreeBuilderHttpRepository.getAllTopics());
this.reteForceUpdateObserver?.emit("");
},
async () => {
@ -207,7 +244,6 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
)
).fold(
(xml) => {
console.log(xml);
this.behaviorTreeModel.skills = this.filledOutTemplates;
this.behaviorTreeModel.scene = NodeBehaviorTree.fromReteScene(
this.editor as NodeEditor<Schemes>,
@ -345,4 +381,11 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState<BehaviorTreeView
);
}
changeSceneViewModel = (text: string) => {};
changePanel = (index: number, name: string, body?: React.ReactNode) => {
this.panels = this.panels.replacePropIndex({ name: name, body: body }, index);
};
panelResize = (size: number, index: number) => {
this.panels.replacePropIndex({ size: size }, index);
};
}

View file

@ -0,0 +1,94 @@
import { NavigateFunction, useParams } from "react-router-dom";
import { CoreHttpRepository, HttpMethod } from "../../../../../core/repository/core_http_repository";
import { CoreError, UiErrorState } from "../../../../../core/store/base_store";
import { ProcessStatus } from "../../../../dataset/dataset_model";
import makeAutoObservable from "mobx-store-inheritance";
import { useStore } from "../../../../../core/helper/use_store";
import { Loader } from "../../../../../core/ui/loader/loader";
import { useEffect } from "react";
import {
RunTimeSocketRepository,
runTimeSocketRepository,
} from "../../../../../core/repository/core_socket_repository";
import { observer } from "mobx-react-lite";
interface IPid {
pid: string;
}
export interface RuntimeModel {
[name: string]: { pid: number; status: String };
}
export class RunTimeHttpRepository extends CoreHttpRepository {
feature = "/run_time";
stop = (model: IPid) => this._jsonRequest(HttpMethod.POST, this.feature + "/kill", model);
getStatuses = () => this._jsonRequest<RuntimeModel>(HttpMethod.GET, this.feature + "/status");
execBt = (id: string) => this._jsonRequest(HttpMethod.POST, this.feature + `/exec/bt?id=${id}`);
}
// export class RunTimeSocketRepository extends SockeCore {}
export class RunTimeStore extends UiErrorState<CoreError> {
runTimeHttpRepository: RunTimeHttpRepository = new RunTimeHttpRepository();
runTimeSocketRepository: RunTimeSocketRepository = runTimeSocketRepository;
pageId: string;
runTime?: RuntimeModel = {};
constructor() {
super();
makeAutoObservable(this);
runTimeSocketRepository.on((event) => (console.log(event), (this.runTime = event)));
}
async init(navigate?: NavigateFunction | undefined): Promise<any> {
this.mapOk("runTime", this.runTimeHttpRepository.getStatuses());
this.navigate = navigate;
}
executeBt = () => this.runTimeHttpRepository.execBt(this.pageId);
initParam = (id: string) => (this.pageId = id);
isExecuteBt = (): boolean => {
if (this.runTime === undefined) {
return false;
}
if (
this.runTime["EXEC_BT"] !== undefined &&
this.runTime["EXEC_BT"]["status"] !== undefined &&
this.runTime["EXEC_BT"]["status"] === "endOk"
) {
return false;
}
return true;
};
}
export const RunTimeActions = observer(() => {
const store = useStore(RunTimeStore);
const params = useParams();
useEffect(() => {
store.initParam(params.id as string);
}, []);
return (
<div style={{ width: "100%" }}>
{store.isLoading ? (
<Loader />
) : (
<>
{store.isExecuteBt() ? (
<>
<div
style={{ justifyContent: "center", color: "white", justifySelf: "center" }}
onClick={() => store.executeBt()}
>
Дерево запущено
</div>
</>
) : (
<>
<div
style={{ justifyContent: "center", color: "white", justifySelf: "center" }}
onClick={() => store.executeBt()}
>
Запустить дерево
</div>
</>
)}
</>
)}
</div>
);
});

View file

@ -19,7 +19,7 @@ export const FormBuilderForm = observer((props: IPropsForm<Partial<FormBuilderVa
}, []);
return (
<div style={{ overflowX: "scroll", height: "100%" }}>
<div style={{ overflowX: "scroll", height: "100%", flex: 1 }}>
<div>FormBuilder</div>
{store.isBtScreen ? (
<div>
@ -27,7 +27,6 @@ export const FormBuilderForm = observer((props: IPropsForm<Partial<FormBuilderVa
formBuilder={store.viewModel}
onChange={(form) => {
store.viewModel = form;
console.log(form);
}}
/>
<div style={{ height: 100 }} />
@ -81,7 +80,7 @@ export const FormBuilderForm = observer((props: IPropsForm<Partial<FormBuilderVa
store.isModalOpen = false;
}}
>
<FormBuilder formBuilder={store.viewModel} onChange={() => {}} />
<FormBuilder formBuilder={store.viewModel} onChange={(form) => (console.log(form), props.onChange(form))} />
</Modal>
</>
)}

View file

@ -4,7 +4,7 @@ import { BehaviorTreeViewModel } from "../behavior_tree_builder/model/behavior_t
export class BehaviorTreeManagerHttpRepository extends CoreHttpRepository {
featureApi = `/behavior/trees`;
deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
deleteBt = (id: string) => this._jsonRequest(HttpMethod.POST, `${this.featureApi}/delete/bt?id=${id}`);
saveNewBt = async (model: BehaviorTreeViewModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
getAllBtInstances = async () => this._jsonRequest<BehaviorTreeModel[]>(HttpMethod.GET, this.featureApi);
}

View file

@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { BehaviorTreeManagerStore } from "./behavior_tree_manager_store";
import React from "react";
import React, { useEffect } from "react";
import { useStore } from "../../core/helper/use_store";
import { ButtonV2, ButtonV2Type } from "../../core/ui/button/button_v2";
import { CoreCard } from "../../core/ui/card/card";
@ -16,13 +16,13 @@ export const BehaviorTreeManagerScreenPath = "/behavior/tree/manager";
export const BehaviorTreeManagerScreen = observer(() => {
const store = useStore(BehaviorTreeManagerStore);
useEffect(() => {}, []);
return (
<>
<MainPageV2
children={
<>
<div style={{ height: "100%", overflowY: "auto", overflowX: "hidden" }}>
<div style={{ height: "100%", }}>
<div style={{ height: 20 }} />
<ButtonV2
icon={<Icon type={"Plus"} style={{ alignSelf: "center", marginLeft: 10, marginRight: 10 }} />}
@ -65,9 +65,13 @@ export const BehaviorTreeManagerScreen = observer(() => {
}}
/>
<div style={{ height: 20 }} />
<InputV2 trim={true} label={"Название"} onChange={(text) => store.updateForm({ name: text })} />
<InputV2 trim={true} label={"Название"} onChange={(text) => store.updateForm({ name: text })} />
<div style={{ height: 20 }} />
<InputV2 trim={true} label={"Описание"} onChange={(text) => store.updateForm({ description: text })} />
<InputV2
trim={true}
label={"Описание"}
onChange={(text) => store.updateForm({ description: text })}
/>
<div style={{ height: 20 }} />
<SelectV2
items={store.scenes?.map((el) => ({ name: el.name, value: el._id })) ?? []}

View file

@ -12,7 +12,6 @@ export enum DrawerState {
}
export class BehaviorTreeManagerStore extends UiDrawerFormState<BehaviorTreeViewModel, CoreError> {
viewModel: BehaviorTreeViewModel = BehaviorTreeViewModel.empty();
navigate?: NavigateFunction;
btTreeModels: BehaviorTreeModel[] = [];
scenes?: SceneModel[];
behaviorTreeManagerHttpRepository = new BehaviorTreeManagerHttpRepository();

View file

@ -14,7 +14,13 @@ export interface ISkils {
}
export class CalculationHttpRepository extends CoreHttpRepository {
async getLogs(id: string) {
await this._request(HttpMethod.GET, `/logs?id=${id}`)
window.location.href = 'http://localhost:4001/log.txt';
}
featureApi = `/calculations/instances`;
subFeatureApi = `/calculations/template`;

View file

@ -4,7 +4,9 @@ import { FormBuilderValidationModel } from "../../../core/model/form_builder_val
export enum ModelMachineLearningTypes {
OBJECT_DETECTION = "OBJECT_DETECTION",
POSE_ESTIMATE = "POSE_ESTIMATE",
POSE_ESTIMATION = "POSE_ESTIMATION",
BOP_DATASET = "BOP_DATASET",
WEIGHTS = "WEIGHTS",
}
export class CalculationModel extends ValidationModel {

View file

@ -28,7 +28,6 @@ export const CalculationInstanceScreenPath = "/calculation";
export const CalculationInstanceScreen = observer(() => {
const store = useStore(CalculationInstanceStore);
return (
<>
<MainPage
@ -60,7 +59,9 @@ export const CalculationInstanceScreen = observer(() => {
() => store.makeEditProcess(el),
() => store.deleteInstance(el._id ?? ""),
() => store.execSkill(el._id ?? ""),
() => store.execSkill(el._id ?? "")
() => store.execSkill(el._id ?? ""),
() => store.changeProcessStatus(el._id ?? ""),
() => store.getTxtLog(el._id ?? "")
)}
</span>
);
@ -94,7 +95,10 @@ export const CalculationInstanceScreen = observer(() => {
<div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}>
<FormBuilder
formBuilder={store.editProcess?.formBuilder ?? FormBuilderValidationModel.empty()}
onChange={(formBuilder) => store.updateForm({ formBuilder: formBuilder })}
onChange={(formBuilder) => {
console.log(formBuilder);
store.updateForm({ formBuilder: formBuilder });
}}
/>
<div style={{ display: "flex" }}>
@ -134,7 +138,7 @@ export const CalculationInstanceScreen = observer(() => {
<CoreSelect
items={Object.keys(ModelMachineLearningTypes)}
value={store.viewModel.type}
label={"Тип навыка"}
label={"Тип вычисления"}
onChange={(text: string) => store.updateForm({ type: text })}
/>
<CoreSelect
@ -143,8 +147,16 @@ export const CalculationInstanceScreen = observer(() => {
label={"Тип карточки"}
onChange={(text: string) => store.updateForm({ card: text })}
/>
<CoreInput label="Имя" onChange={(text) => store.updateForm({ name: text })} />
<CoreInput label="Команда для запуска" onChange={(text) => store.updateForm({ script: text })} />
<CoreInput
trim={true}
label="Имя"
onChange={(text) => store.updateForm({ name: text.replaceAll("\n", "") })}
/>
<CoreInput
trim={true}
label="Команда для запуска"
onChange={(text) => store.updateForm({ script: text.replaceAll("\n", "") })}
/>
<InputV2
label="FormBuilder Result"
onChange={(text) => (store.viewModel.formBuilder.result = text)}
@ -206,7 +218,7 @@ export const CalculationInstanceScreen = observer(() => {
store.isModalOpen = false;
}}
>
<FormBuilder formBuilder={store.viewModel.formBuilder} onChange={() => {}} />
<FormBuilder formBuilder={store.viewModel.formBuilder} onChange={(e) => {}} />
</Modal>
</>
);

View file

@ -5,7 +5,7 @@ import { Drawer, UiDrawerFormState } from "../../../core/store/base_store";
import { CalculationHttpRepository } from "../data/calculation_http_repository";
import { message } from "antd";
import { UUID } from "../../all_projects/data/project_http_repository";
import { CalculationModel } from "../model/calculation_model";
import { CalculationModel as calculationModel } from "../model/calculation_model";
import { ProcessUpdate, CalculationSocketRepository } from "../data/calculation_socket_repository";
import { match } from "ts-pattern";
import { plainToInstance } from "class-transformer";
@ -20,16 +20,17 @@ export enum StoreTypes {
empty = "empty",
}
export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel, HttpError> {
export class CalculationInstanceStore extends UiDrawerFormState<calculationModel, HttpError> {
getTxtLog = (id: string) => this.calculationHttpRepository.getLogs(id);
calculationHttpRepository: CalculationHttpRepository = new CalculationHttpRepository();
calculationSocketRepository: CalculationSocketRepository = new CalculationSocketRepository();
activeProjectId?: UUID;
storeType: StoreTypes = StoreTypes.empty;
viewModel: CalculationModel = CalculationModel.empty();
modelTemplate?: CalculationModel[];
editProcess?: CalculationModel;
calculationInstances?: CalculationModel[];
selectTemplate?: CalculationModel;
viewModel: calculationModel = calculationModel.empty();
modelTemplate?: calculationModel[];
editProcess?: calculationModel;
calculationInstances?: calculationModel[];
selectTemplate?: calculationModel;
titleDrawer: string = DrawersSkill.NEW_SKILL;
drawers: Drawer[];
isModalOpen: boolean = false;
@ -41,7 +42,7 @@ export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel
status: false,
};
});
this.viewModel = CalculationModel.empty();
this.viewModel = calculationModel.empty();
this.calculationSocketRepository.on(this.socketUpdate);
makeAutoObservable(this);
}
@ -67,9 +68,9 @@ export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel
successMessage: "Процесс запущен",
});
setSelectTemplate = (el: CalculationModel) => {
setSelectTemplate = (el: calculationModel) => {
this.selectTemplate = el;
const instance = plainToInstance(CalculationModel, el);
const instance = plainToInstance(calculationModel, el);
instance.instanceName = this?.viewModel.instanceName;
this.viewModel = instance;
};
@ -84,7 +85,7 @@ export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel
match(this.storeType)
.with(StoreTypes.empty, () => {})
.with(StoreTypes.newType, async () =>
(await this.viewModel.valid<CalculationModel>()).fold(
(await this.viewModel.valid<calculationModel>()).fold(
async (model) => {
model.project = this.activeProjectId?.id;
@ -99,7 +100,7 @@ export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel
)
)
.with(StoreTypes.newModel, async () => {
(await this.viewModel.valid<CalculationModel>()).fold(
(await this.viewModel.valid<calculationModel>()).fold(
async (model) => {
delete model._id;
model.project = this.activeProjectId?.id;
@ -128,20 +129,30 @@ export class CalculationInstanceStore extends UiDrawerFormState<CalculationModel
saveEdiSkill = async () => {
this.editDrawer(DrawersSkill.EDIT_SKILL, false);
(await this.viewModel.valid<CalculationModel>()).fold(
async (model) => await this.calculationHttpRepository.editCalculation(model),
(await this.viewModel.valid<calculationModel>()).fold(
async (model) => (await this.calculationHttpRepository.editCalculation(model), await this.init()),
async (err) => message.error(err)
);
};
makeEditProcess = (el: CalculationModel) => {
makeEditProcess = (el: calculationModel) => {
this.editProcess = el;
this.loadClassInstance(CalculationModel, el);
this.loadClassInstance(calculationModel, el);
this.editDrawer(DrawersSkill.EDIT_SKILL, true);
};
deleteTemplate = async (el: CalculationModel) => {
deleteTemplate = async (el: calculationModel) => {
await this.messageHttp(this.calculationHttpRepository.deleteTemplate(el._id ?? ""), {
successMessage: "Удален",
});
await this.mapOk("modelTemplate", this.calculationHttpRepository.getAllTemplates());
};
changeProcessStatus = async (id: string) =>
this!
.calculationInstances!.whereOne((el) => el._id === id)
.map(
async (calculationModel) => (
(calculationModel.isEnd = !calculationModel.isEnd),
await this.calculationHttpRepository.editCalculation(calculationModel),
await this.init(undefined)
)
);
}

View file

@ -1,6 +1,6 @@
import { match } from "ts-pattern";
import { PoseEstimateCard } from "./pose_estimate_card/model_card";
import { Dropdown, MenuProps, message } from "antd";
import { Dropdown, MenuProps, } from "antd";
import { CoreText, CoreTextType } from "../../../../../core/ui/text/text";
import { IMenuItem } from "../../../../dataset/card_dataset";
import { Icon } from "../../../../../core/ui/icons/icons";
@ -15,7 +15,9 @@ export const getModelCard = (
onEdit: Function,
onDelete: Function,
onPlay: Function,
onPause: Function
onPause: Function,
onChangeProcessIsEnd: Function,
onLog: Function
) => {
const menu: IMenuItem[] = [
{
@ -26,6 +28,10 @@ export const getModelCard = (
onClick: () => onDelete(),
name: "Удалить",
},
{
onClick: () => onChangeProcessIsEnd(),
name: calculationModel.isEnd ? "Вернуть на доработку" : "Завершить",
},
];
const items: MenuProps["items"] = menu.map((el, index) => {
@ -63,7 +69,8 @@ export const getModelCard = (
<Icon
type="Log"
onClick={async () =>
window.prompt("Copy to clipboard: Ctrl+C, Enter", calculationModel.lastProcessLogs ?? "Not found logs")
// window.prompt("Copy to clipboard: Ctrl+C, Enter", calculationModel.lastProcessLogs ?? "Not found logs")
onLog()
}
/>
<Icon

View file

@ -26,7 +26,6 @@ export class PoseEstimateRepository extends HttpRepository {
execAnalyze = (id: string) => this._jsonRequest(HttpMethod.POST, `/run_time/exec/analyze?id=${id}`);
}
export class PoseEstimateStore extends UiErrorState<any> {
navigate?: NavigateFunction;
poseEstimateRepository = new PoseEstimateRepository();
constructor() {
super();

View file

@ -12,7 +12,6 @@ import { useStore } from "../../core/helper/use_store";
import { FormBuilder } from "../../core/ui/form_builder/form_builder";
import { ButtonV2 } from "../../core/ui/button/button_v2";
import { Result } from "../../core/helper/result";
import { plainToInstance } from "class-transformer";
export const isValidJson = <T,>(json: any): Result<string, T> => {
try {
@ -288,3 +287,4 @@ export const SkillsScreen = observer(() => {
</>
);
});

View file

@ -21,6 +21,7 @@ export class TopicViewModel extends ValidationModel {
export interface ITopicModel {
digitalTwinId?: string;
sid?: string;
_id?: string;
name: string;
type: string;
}

View file

@ -1,6 +1,9 @@
import { HttpMethod, HttpRepository } from "../../core/repository/core_http_repository";
import { CoreHttpRepository, HttpMethod, HttpRepository } from "../../core/repository/core_http_repository";
import { TopicViewModel } from "./topic_view_model";
export class TopicsHttpRepository extends HttpRepository {
export class TopicsHttpRepository extends CoreHttpRepository {
deleteTopic = (id: any) => this._jsonRequest(HttpMethod.DELETE, `${this.featureApi}?id=${id}`);
featureApi = "/topics";
getAllTopics = () => this._jsonRequest(HttpMethod.GET, this.featureApi);
createTopics = (model: TopicViewModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model);
}

View file

@ -2,6 +2,11 @@ import React from "react";
import { observer } from "mobx-react-lite";
import { TopicsStore } from "./topics_store";
import { useStore } from "../../core/helper/use_store";
import { CoreButton } from "../../core/ui/button/button";
import { CoreModal } from "../../core/ui/modal/modal";
import { InputV2 } from "../../core/ui/input/input_v2";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
import { CoreInput } from "../../core/ui/input/input";
export const TopicsScreenPath = "/topics";
@ -10,12 +15,43 @@ export const TopicsScreen = observer(() => {
return (
<>
<div style={{ display: "flex" }}>
<CoreButton text="добавить новый топик" style={{ width: 200 }} onClick={() => store.modalShow()} />
<CoreButton text="импорт топика" style={{ width: 200 }} onClick={() => store.openTopicModal()} />
</div>
{store.topics?.map((el) => (
<div style={{ margin: 10, border: "1px solid red" }}>
<CoreText text="Delete" type={CoreTextType.header} onClick={() => store.deleteTopic(el._id ?? "")} />
<CoreText
text="Copy"
type={CoreTextType.header}
onClick={() => window.prompt("Copy to clipboard: Ctrl+C, Enter", JSON.stringify(el))}
/>
<div>{el.name}</div>
<div>{el.type}</div>
</div>
))}
<CoreModal
isOpen={store.isModalOpen}
onClose={() => store.modalCancel()}
children={
<div>
<InputV2 label={"type"} onChange={(text) => store.updateForm({ type: text })} />
<InputV2 label={"name"} onChange={(text) => store.updateForm({ name: text })} />
<CoreButton text="new" onClick={() => store.createTopic()} />
</div>
}
/>
<CoreModal
isOpen={store.importTopicModal}
onClose={() => store.cancelImportTopicModal()}
children={
<div>
<InputV2 onChange={(text) => store.importTopic(text)} label="import" />
</div>
}
/>
</>
);
});

View file

@ -6,14 +6,35 @@ import { NavigateFunction } from "react-router-dom";
import { TopicsHttpRepository } from "./topics_repository";
export class TopicsStore extends FormState<TopicModel, HttpError> {
importTopic(text: string): void {
this.loadClassInstance(TopicModel, JSON.parse(text))
this.createTopic()
}
viewModel: TopicModel = TopicModel.empty();
topics?: ITopicModel[];
topicsHttpRepository: TopicsHttpRepository = new TopicsHttpRepository();
importTopicModal = false;
constructor() {
super();
makeAutoObservable(this);
}
init = async (navigate?: NavigateFunction | undefined) => {
this.modalCancel();
this.importTopicModal = false;
await this.mapOk("topics", this.topicsHttpRepository.getAllTopics());
};
cancelImportTopicModal = () => {
this.importTopicModal = false;
}
openTopicModal = () => {
this.importTopicModal = true;
}
deleteTopic = async (id: string) => (await (this.topicsHttpRepository.deleteTopic(id))).map(() => this.init());
createTopic = async () =>
(await this.viewModel.validMessage<TopicModel>()).map(async (model) =>
(await this.topicsHttpRepository.createTopics(model)).map(() => this.init())
);
}

View file

@ -7,6 +7,7 @@ import { RouterProvider } from "react-router-dom";
import { router } from "./core/routers/routers";
import { configure } from "mobx";
import { ThemeStore } from "./core/store/theme_store";
import { FormBuildTest } from "./core/ui/form_builder/test";
configure({
enforceActions: "never",
@ -22,5 +23,6 @@ root.render(
<SocketListener>
<RouterProvider router={router} />
</SocketListener>
{/* <FormBuildTest /> */}
</>
);

View file

@ -0,0 +1,6 @@
{
"_id": "67b43b59ba78351d1a11d74a",
"name": "/rgbd_camera/camera_info",
"type": "sensor_msgs/msg/CameraInfo",
"__v": 0
}

6
web_p/image_topic.json Normal file
View file

@ -0,0 +1,6 @@
{
"_id": "67b43b20ba78351d1a11d747",
"name": "/rgbd_camera/image",
"type": "sensor_msgs/msg/Image",
"__v": 0
}

196
web_p/models_dope.py Executable file
View file

@ -0,0 +1,196 @@
"""
NVIDIA from jtremblay@gmail.com
"""
# Networks
import torch
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
import torchvision.models as models
class DopeNetwork(nn.Module):
def __init__(
self,
pretrained=False,
numBeliefMap=9,
numAffinity=16,
stop_at_stage=6, # number of stages to process (if less than total number of stages)
):
super(DopeNetwork, self).__init__()
self.stop_at_stage = stop_at_stage
vgg_full = models.vgg19(pretrained=False).features
self.vgg = nn.Sequential()
for i_layer in range(24):
self.vgg.add_module(str(i_layer), vgg_full[i_layer])
# Add some layers
i_layer = 23
self.vgg.add_module(
str(i_layer), nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1)
)
self.vgg.add_module(str(i_layer + 1), nn.ReLU(inplace=True))
self.vgg.add_module(
str(i_layer + 2), nn.Conv2d(256, 128, kernel_size=3, stride=1, padding=1)
)
self.vgg.add_module(str(i_layer + 3), nn.ReLU(inplace=True))
# print('---Belief------------------------------------------------')
# _2 are the belief map stages
self.m1_2 = DopeNetwork.create_stage(128, numBeliefMap, True)
self.m2_2 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numBeliefMap, False
)
self.m3_2 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numBeliefMap, False
)
self.m4_2 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numBeliefMap, False
)
self.m5_2 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numBeliefMap, False
)
self.m6_2 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numBeliefMap, False
)
# print('---Affinity----------------------------------------------')
# _1 are the affinity map stages
self.m1_1 = DopeNetwork.create_stage(128, numAffinity, True)
self.m2_1 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numAffinity, False
)
self.m3_1 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numAffinity, False
)
self.m4_1 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numAffinity, False
)
self.m5_1 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numAffinity, False
)
self.m6_1 = DopeNetwork.create_stage(
128 + numBeliefMap + numAffinity, numAffinity, False
)
def forward(self, x):
"""Runs inference on the neural network"""
out1 = self.vgg(x)
out1_2 = self.m1_2(out1)
out1_1 = self.m1_1(out1)
if self.stop_at_stage == 1:
return [out1_2], [out1_1]
out2 = torch.cat([out1_2, out1_1, out1], 1)
out2_2 = self.m2_2(out2)
out2_1 = self.m2_1(out2)
if self.stop_at_stage == 2:
return [out1_2, out2_2], [out1_1, out2_1]
out3 = torch.cat([out2_2, out2_1, out1], 1)
out3_2 = self.m3_2(out3)
out3_1 = self.m3_1(out3)
if self.stop_at_stage == 3:
return [out1_2, out2_2, out3_2], [out1_1, out2_1, out3_1]
out4 = torch.cat([out3_2, out3_1, out1], 1)
out4_2 = self.m4_2(out4)
out4_1 = self.m4_1(out4)
if self.stop_at_stage == 4:
return [out1_2, out2_2, out3_2, out4_2], [out1_1, out2_1, out3_1, out4_1]
out5 = torch.cat([out4_2, out4_1, out1], 1)
out5_2 = self.m5_2(out5)
out5_1 = self.m5_1(out5)
if self.stop_at_stage == 5:
return [out1_2, out2_2, out3_2, out4_2, out5_2], [
out1_1,
out2_1,
out3_1,
out4_1,
out5_1,
]
out6 = torch.cat([out5_2, out5_1, out1], 1)
out6_2 = self.m6_2(out6)
out6_1 = self.m6_1(out6)
return [out1_2, out2_2, out3_2, out4_2, out5_2, out6_2], [
out1_1,
out2_1,
out3_1,
out4_1,
out5_1,
out6_1,
]
@staticmethod
def create_stage(in_channels, out_channels, first=False):
"""Create the neural network layers for a single stage."""
model = nn.Sequential()
mid_channels = 128
if first:
padding = 1
kernel = 3
count = 6
final_channels = 512
else:
padding = 3
kernel = 7
count = 10
final_channels = mid_channels
# First convolution
model.add_module(
"0",
nn.Conv2d(
in_channels, mid_channels, kernel_size=kernel, stride=1, padding=padding
),
)
# Middle convolutions
i = 1
while i < count - 1:
model.add_module(str(i), nn.ReLU(inplace=True))
i += 1
model.add_module(
str(i),
nn.Conv2d(
mid_channels,
mid_channels,
kernel_size=kernel,
stride=1,
padding=padding,
),
)
i += 1
# Penultimate convolution
model.add_module(str(i), nn.ReLU(inplace=True))
i += 1
model.add_module(
str(i), nn.Conv2d(mid_channels, final_channels, kernel_size=1, stride=1)
)
i += 1
# Last convolution
model.add_module(str(i), nn.ReLU(inplace=True))
i += 1
model.add_module(
str(i), nn.Conv2d(final_channels, out_channels, kernel_size=1, stride=1)
)
i += 1
return model

87
web_p/od_skill.json Normal file
View file

@ -0,0 +1,87 @@
{
"_id": "67b33b15f3412f3530fdb337",
"bgColor": "rgba(5, 26, 39, 1)",
"borderColor": "rgba(25, 130, 196, 1)",
"SkillPackage": { "name": "Robossembler", "version": "1", "format": "1.0" },
"Module": { "node_name": "lc_yolo", "name": "ObjectDetection", "description": "Object detection skill with YOLOv8" },
"BTAction": [
{
"name": "odConfigure",
"type": "run",
"param": [
{
"type": "topic",
"dependency": {
"type": "topic",
"topicType": "sensor_msgs/msg/CameraInfo"
},
"isFilled": false
},
{
"type": "topic",
"dependency": {
"type": "topic",
"topicType": "sensor_msgs/msg/Image"
},
"isFilled": false
},
{
"type": "formBuilder",
"dependency": {
"result": "{\"process\":\\${<SelectProcess/>:OBJECT:{\"type\":\"WEIGHTS\"},\"object_name\":\\${object_name:string:}}",
"context": "",
"form": [],
"output": "",
"type": "formBuilder"
},
"isFilled": false
}
],
"typeAction": "ACTION"
},
{
"name": "odStop",
"type": "stop",
"param": [],
"typeAction": "ACTION"
},
{
"name": "isDetectionRun",
"type": "if",
"param": [],
"typeAction": "CONDITION"
},
{
"name": "isDetection",
"type": "if",
"param": [],
"typeAction": "CONDITION"
}
],
"topicsOut": [
{
"name": "lc_yolo/object_detection",
"type": "rbs_skill_interfaces/msg/BoundBox"
},
{
"name": "lc_yolo/detect_image",
"type": "sensor_msgs/msg/Image"
}
],
"Launch": {
"executable": "od_yolo_lc.py",
"package": "rbss_objectdetection"
},
"Settings": {
"result": "{\"params\": \\${ITEM:Array<ITEM>:[]}}",
"context": "type ITEM = {\"name\": \\${NAME:string:default},\"value\": \\${VALUE:string:default}};",
"form": [
"{\"name\":\"ITEM\",\"type\":\"Array\",\"defaultValue\":\"[]\",\"values\":[{\"name\":\"NAME\",\"type\":\"string\",\"defaultValue\":\"default\",\"isOpen\":false,\"id\":\"fa8b442a-101b-448b-b5fd-55ad64f8e578\"},{\"name\":\"VALUE\",\"type\":\"string\",\"defaultValue\":\"default\",\"isOpen\":false,\"id\":\"c59b86d8-a54b-42da-b2bf-5fdfeff7f711\"}],\"totalValue\":[],\"isOpen\":true,\"subType\":\"ITEM\",\"id\":\"8be7af67-5860-4a3f-bdab-09c75a53bff7\"}"
],
"output": {
"params": []
},
"type": "formBuilder"
},
"__v": 0
}

64
web_p/rbs_train2.py Normal file
View file

@ -0,0 +1,64 @@
"""
rbs_train2
Общая задача: web-service pipeline
Реализуемая функция: обучение нейросетевой модели по заданному BOP-датасету
python3 $PYTHON_EDUCATION --path /home/user/webservice/server/build/public/process/proc/inst_proc \
--form /home/user/webservice/server/build/public/process/proc/inst_proc/form.json
28.01.2025 @shalenikol release 0.1
17.02.2025 @shalenikol release 0.2 addon_dir
"""
import argparse
import os
import json
from train_Yolo import train_YoloV8
from train_Dope import train_Dope_i
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True, help="Output path for weights")
parser.add_argument("--form", required=True, help="Json-file with training parameters")
args = parser.parse_args()
if not os.path.isdir(args.path):
print(f"Invalid output path '{args.path}'")
exit(-1)
wname = os.path.basename(args.path)
outpath = os.path.dirname(args.path)
if not os.path.isfile(args.form):
print(f"Error: no such file '{args.form}'")
exit(-2)
with open(args.form, "r") as f:
j_data = f.read()
try:
cfg = json.loads(j_data)
except json.JSONDecodeError as e:
print(f"JSon error: {e}")
exit(-3)
cfg = cfg["output"] # edited params
dataset_params = cfg["process"]["selectProcess"]["value"]
dataset_type = dataset_params["type"]
if dataset_type != "BOP_DATASET":
print(f"Error: Invalid dataset type '{dataset_type}'")
exit(-4)
dataset_name = dataset_params["instanceName"]
dataset_path = dataset_params["path"]
dataset_path = dataset_path.replace("//", "/") # !!! TODO !!! Nikita
epoch = cfg["n_epoch"]
pretrain = (cfg["pretrain"] == "True") #False
ttype = cfg["typeWeight"] #"ObjectDetection"
addon_dir = ""
if "addon" in cfg:
addon = cfg["addon"].strip()
if addon and os.path.isdir(addon):
addon_dir = addon
if ttype == "ObjectDetection":
train_YoloV8(dataset_path, wname, dataset_name, outpath, epoch, pretrain, addon_dir)
else:
train_Dope_i(dataset_path, wname, dataset_name, outpath, epoch, pretrain)

View file

@ -8,6 +8,7 @@ import blenderproc as bproc
02.05.2024 @shalenikol release 0.1
02.07.2024 @shalenikol release 0.2
28.10.2024 @shalenikol release 0.3
28.02.2025 @shalenikol release 0.4 blenderproc 2.8.0 + blender 4.2.1 LTS
"""
import numpy as np
import argparse
@ -16,17 +17,32 @@ import os
import shutil
import json
from pathlib import Path
import time
###########################
# !!! чтобы избежать ошибки в версии 2.8.0
# free(): invalid pointer
# при вызове bproc.writer.write_bop
import pyrender
from pyrender.platforms import egl
###########################
start_time = time.time() # Запоминаем время начала
import bpy
VHACD_PATH = "blenderproc_resources/vhacd"
DIR_MODELS = "models"
DIR_MESH = "assets/libs/objects/"
# DIR_MESH = "assets/libs/objects/"
FILE_LOG_SCENE = "res.txt"
FILE_RBS_INFO = "rbs_info.json"
FILE_GT_COCO = "scene_gt_coco.json"
EXT_MODELS = ".fbx"
FILE_PARAMS = "form.json"
PROCEDURAL_TEXTURE = "texture_path" # key in randomization params: for texture types (Noise Textures), (Procedural Patterns) or (Tileable Textures)
EXT_MODELS = ".fbx" # for scene objects (floor ...)
DETAIL_KEY = "daeUrl" # "fbx" # key in dict 'Detail' for mesh path of model
TEXTURE_TMPL = "*.jpg"
TEXTURE_IMAGE_TYPES = ["Base Color", "Metallic", "Normal", "Roughness", "Specular IOR Level"]
Not_Categories_Name = True # наименование категории в COCO-аннотации отсутствует
@ -57,19 +73,50 @@ def convert2relative(height, width, bbox):
y += h/2
return x/width, y/height, w/width, h/height
def convert_seconds(total_seconds):
hours = int(total_seconds // 3600)
minutes = int((total_seconds % 3600) // 60)
seconds = int(total_seconds % 60)
return f"{hours:02}:{minutes:02}:{seconds:02}"
def render() -> int:
res_dir = rnd_par.output_dir
log_dir = os.path.dirname(res_dir)
# copy file with randomization params
file_params = os.path.join(res_dir, FILE_PARAMS)
if os.path.isfile(file_params):
shutil.copy2(file_params, log_dir)
if os.path.isdir(res_dir):
shutil.rmtree(res_dir)
i = 0
for obj in all_meshs:
# Make the object actively participate in the physics simulation
obj.enable_rigidbody(active=True, collision_shape="COMPOUND")
# Also use convex decomposition as collision shapes
obj.build_convex_decomposition_collision_shape(VHACD_PATH)
# # это для procedural texture, но пока не правильно
# fn = (os.path.splitext(rnd_par.models.filenames[i]))[0] + ".jpg" # файл с текстурой
# if os.path.isfile(fn):
# material = bproc.material.create_material_from_texture(fn, material_name="texture_model"+str(i))
# # Применяем текстуру к материалу
# obj.replace_materials(material)
tex = rnd_par.models.textures[i] # описание текстур
if tex["is"]:
mat = bproc.material.create("m"+str(i))
for x in tex["t_images"]:
key = list(x.keys())[0]
mat.set_principled_shader_value(key, bpy.data.images.load(filepath=x[key]))
obj.replace_materials(mat)
i += 1
# print(f"{i} : {obj.get_name()}")
objs = all_meshs + rnd_par.scene.objs
log_txt = os.path.join(os.path.dirname(rnd_par.output_dir), FILE_LOG_SCENE)
log_txt = os.path.join(log_dir, FILE_LOG_SCENE)
with open(log_txt, "w") as fh:
for i,o in enumerate(objs):
loc = o.get_location()
@ -91,23 +138,21 @@ def render() -> int:
rnd_par.image_size_wh[1],
lens_unit="FOV")
# Enable transparency so the background becomes transparent
bproc.renderer.set_output_format(enable_transparency=True) # ???
# add segmentation masks (per class and per instance)
bproc.renderer.enable_segmentation_output(map_by=["category_id", "instance", "name"])
# activate depth rendering
bproc.renderer.enable_depth_output(activate_antialiasing=False)
# res_dir = os.path.join(rnd_par.output_dir, rnd_par.ds_name)
res_dir = rnd_par.output_dir
if os.path.isdir(res_dir):
shutil.rmtree(res_dir)
# Цикл рендеринга
# Do multiple times: Position the shapenet objects using the physics simulator and render X images with random camera poses
for r in range(rnd_par.n_series):
print(f"********** Series : {r+1}")
is_texture = True if "texture_path" in rnd_par.models_randomization else False
is_texture = True if PROCEDURAL_TEXTURE in rnd_par.models_randomization else False
if is_texture:
val = rnd_par.models_randomization["texture_path"]
val = rnd_par.models_randomization[PROCEDURAL_TEXTURE]
l_texture = _get_list_texture(val)
image = bpy.data.images.load(filepath=str(l_texture[r % len(l_texture)]))
# один случайный объект в кадре / все заданные объекты
@ -125,16 +170,32 @@ def render() -> int:
for i,o in enumerate(rnd_par.scene.objs): # объекты сцены
rnd_mat = rnd_par.scene.obj_data[i]["material_randomization"]
# if PROCEDURAL_TEXTURE in rnd_mat: # путь к текстурам (*.jpg)
# mat = bproc.material.create("m"+str(i))
# # for x in tex["t_images"]:
# # key = list(x.keys())[0]
# val = rnd_mat[PROCEDURAL_TEXTURE]
# val = _get_list_texture(val)
# image = bpy.data.images.load(filepath=str(random.choice(val)))
# mat.set_principled_shader_value("Base Color", image)
# o.replace_materials(mat)
mats = o.get_materials() #[0]
for mat in mats:
# with open(log_txt, "a") as fh:
# fh.write("************* mat\n")
# fh.write(f"{mat}\n")
val = rnd_mat["specular"]
mat.set_principled_shader_value("Specular", random.uniform(val[0], val[1]))
mat.set_principled_shader_value("Specular IOR Level", random.uniform(val[0], val[1])) # для Blender < 4.2 было "Specular"
val = rnd_mat["roughness"]
mat.set_principled_shader_value("Roughness", random.uniform(val[0], val[1]))
val = rnd_mat["metallic"]
mat.set_principled_shader_value("Metallic", random.uniform(val[0], val[1]))
if "texture_path" in rnd_mat: # путь к текстурам (*.jpg)
val = rnd_mat["texture_path"]
if PROCEDURAL_TEXTURE in rnd_mat: # путь к текстурам (*.jpg)
val = rnd_mat[PROCEDURAL_TEXTURE]
val = _get_list_texture(val)
image = bpy.data.images.load(filepath=str(random.choice(val)))
mat.set_principled_shader_value("Base Color", image)
@ -156,7 +217,7 @@ def render() -> int:
# Define a function that samples 6-DoF poses
def sample_pose(obj: bproc.types.MeshObject):
obj.set_location(np.random.uniform(rnd_par.loc_range_low, rnd_par.loc_range_high)) #[-1, -1, 0], [1, 1, 2]))
obj.set_rotation_euler(bproc.sampler.uniformSO3())
obj.set_rotation_euler(bproc.sampler.uniformSO3(around_x=rnd_par.around_x, around_y=rnd_par.around_y, around_z=rnd_par.around_z))
# Sample the poses of all shapenet objects above the ground without any collisions in-between
bproc.object.sample_poses(meshs,
@ -232,7 +293,12 @@ def render() -> int:
rec["name"] = objn
rec["model"] = os.path.join(DIR_MODELS, os.path.split(rnd_par.models.filenames[i])[1]) # путь относительный
t = [obj.get_bound_box(local_coords=True).tolist() for obj in all_meshs if obj.get_name() == objn]
rec["cuboid"] = t[0]
if len(t) > 0:
rec["cuboid"] = t[0]
else: # object name does not match file name
rec["Error"] = "!!! object name does not match file name: cuboid is zero"
rec["cuboid"] = np.zeros((8, 3)).tolist()
data.append(rec)
shutil.copy2(rnd_par.models.filenames[i], models_dir)
f = (os.path.splitext(rnd_par.models.filenames[i]))[0] + ".mtl" # файл материала
@ -283,9 +349,37 @@ def render() -> int:
if Not_Categories_Name:
explore(res_dir)
end_time = time.time() # время окончания
execution_time = end_time - start_time # время выполнения
with open(log_txt, "a") as fh:
fh.write("*****************\n")
fh.write(f"Время выполнения: {convert_seconds(execution_time)}\n")
return 0 # success
def _get_models(par, data) -> int:
def set_texture_model(name: str, textures: list, model_d) -> None:
"""
textures заполняется массивом текстур вида:
[{"is": True, "t_images": [{"Base Color":"/path/to/shkaf_d.png"}, {"Normal":"/path/to/shkaf_n.png"}] }, ... ]
"""
d = {"is": False}
if "models" in model_d:
for model in model_d["models"]:
if model["name"] == name:
path = model["texture_dir"].strip()
if path:
t_images = []
for x in TEXTURE_IMAGE_TYPES:
if x in model:
rel_path = model[x].strip()
if rel_path:
t_images.append({x: os.path.join(path, rel_path)})
if len(t_images):
d["is"] = True
d["t_images"] = t_images
textures.append(d)
def _get_models(par, data, models_data) -> int:
global all_meshs
par.models = lambda: None
@ -294,13 +388,14 @@ def _get_models(par, data) -> int:
return 0 # no models
# загрузим объекты
par.models.names = [] # obj_names
par.models.filenames = [] # obj_filenames
par.models.names = []
par.models.filenames = []
par.models.textures = []
i = 1
for f in data:
nam = f["name"]
par.models.names.append(nam)
ff = f["fbx"] # _get_path_model(nam)
ff = f[DETAIL_KEY] # _get_path_model(nam)
par.models.filenames.append(ff)
if not os.path.isfile(ff):
print(f"Error: no such file '{ff}'")
@ -311,6 +406,7 @@ def _get_models(par, data) -> int:
obj = bproc.loader.load_obj(ff)
all_meshs += obj
obj[0].set_cp("category_id", i) # начиная с 1
set_texture_model(nam, par.models.textures, models_data)
i += 1
return par.models.n_item
@ -370,8 +466,6 @@ if __name__ == "__main__":
print(f"JSon error: {e}")
exit(-2)
# output_dir = args.path
ds_cfg = cfg["output"] # dataset config
generation = ds_cfg["generation"]
cam_pos = ds_cfg["camera_position"]
@ -399,11 +493,14 @@ if __name__ == "__main__":
rnd_par.models_randomization = models_randomization
rnd_par.loc_range_low = models_randomization["loc_range_low"]
rnd_par.loc_range_high = models_randomization["loc_range_high"]
rnd_par.around_x = (models_randomization["around_x"] == "True")
rnd_par.around_y = (models_randomization["around_y"] == "True")
rnd_par.around_z = (models_randomization["around_z"] == "True")
bproc.init()
all_meshs = []
if _get_models(rnd_par, rnd_par.dataset_objs) <= 0:
if _get_models(rnd_par, rnd_par.dataset_objs, models_randomization) <= 0:
print("Error: no models in config")
exit(-4)
if _get_scene(rnd_par, ds_cfg["scene"]) <= 0:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View file

@ -1,105 +0,0 @@
task: detect
mode: train
model: /home/shalenikol/fork_work/webservice/server/build/public/4c4f3909-74b0-4206-aec1-fc4acd3a1081/weights/od_w01/yolov8n.pt
data: /home/shalenikol/fork_work/webservice/server/build/public/4c4f3909-74b0-4206-aec1-fc4acd3a1081/weights/od_w01/rbs_train.yaml
epochs: 33
time: null
patience: 50
batch: 16
imgsz: 640
save: true
save_period: -1
cache: false
device: null
workers: 8
project: /home/shalenikol/fork_work/webservice/server/build/public/4c4f3909-74b0-4206-aec1-fc4acd3a1081/weights/od_w01
name: train
exist_ok: false
pretrained: true
optimizer: auto
verbose: true
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: false
close_mosaic: 10
resume: false
amp: true
fraction: 1.0
profile: false
freeze: null
multi_scale: false
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
save_hybrid: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: false
opset: null
workspace: 4
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
label_smoothing: 0.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
mosaic: 1.0
mixup: 0.0
copy_paste: 0.0
auto_augment: randaugment
erasing: 0.4
crop_fraction: 1.0
cfg: null
tracker: botsort.yaml
save_dir: /home/shalenikol/fork_work/webservice/server/build/public/4c4f3909-74b0-4206-aec1-fc4acd3a1081/weights/od_w01/train

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

View file

@ -1,34 +0,0 @@
epoch, train/box_loss, train/cls_loss, train/dfl_loss, metrics/precision(B), metrics/recall(B), metrics/mAP50(B), metrics/mAP50-95(B), val/box_loss, val/cls_loss, val/dfl_loss, lr/pg0, lr/pg1, lr/pg2
1, 0.62674, 1.281, 0.92555, 0.99239, 0.99448, 0.99323, 0.90966, 0.40212, 0.8264, 0.80447, 0.00066247, 0.00066247, 0.00066247
2, 0.60996, 0.71899, 0.93387, 0.9945, 0.99945, 0.99484, 0.91551, 0.43253, 0.60301, 0.8228, 0.0012893, 0.0012893, 0.0012893
3, 0.58648, 0.54879, 0.92909, 1, 0.98871, 0.99494, 0.9213, 0.40211, 0.39327, 0.81593, 0.0018761, 0.0018761, 0.0018761
4, 0.58195, 0.48301, 0.92375, 0.99087, 0.9337, 0.97172, 0.89393, 0.41614, 0.46785, 0.82069, 0.00182, 0.00182, 0.00182
5, 0.56201, 0.44926, 0.92381, 0.99447, 0.99385, 0.99494, 0.94951, 0.34807, 0.32406, 0.8013, 0.00182, 0.00182, 0.00182
6, 0.52696, 0.40581, 0.9068, 0.95813, 0.98343, 0.99281, 0.94494, 0.33023, 0.48053, 0.79401, 0.00176, 0.00176, 0.00176
7, 0.51017, 0.3952, 0.90752, 0.99889, 1, 0.995, 0.95388, 0.3192, 0.33973, 0.7992, 0.0017, 0.0017, 0.0017
8, 0.50772, 0.37889, 0.90238, 0.98351, 0.98842, 0.98581, 0.94918, 0.30154, 0.28504, 0.79667, 0.00164, 0.00164, 0.00164
9, 0.47737, 0.3576, 0.89251, 0.99946, 0.99448, 0.995, 0.97205, 0.28135, 0.23642, 0.79101, 0.00158, 0.00158, 0.00158
10, 0.46587, 0.34547, 0.89324, 0.99948, 1, 0.995, 0.96897, 0.28021, 0.28522, 0.78694, 0.00152, 0.00152, 0.00152
11, 0.45881, 0.33452, 0.89055, 0.99954, 1, 0.995, 0.97012, 0.26364, 0.21443, 0.7813, 0.00146, 0.00146, 0.00146
12, 0.44939, 0.32887, 0.89206, 0.9996, 1, 0.995, 0.98382, 0.24486, 0.20614, 0.78109, 0.0014, 0.0014, 0.0014
13, 0.44388, 0.32289, 0.88796, 0.99932, 1, 0.995, 0.97195, 0.27681, 0.21443, 0.77933, 0.00134, 0.00134, 0.00134
14, 0.43847, 0.31282, 0.88496, 0.99965, 1, 0.995, 0.98019, 0.25014, 0.20255, 0.7775, 0.00128, 0.00128, 0.00128
15, 0.41585, 0.30067, 0.8774, 0.99943, 1, 0.995, 0.97609, 0.25842, 0.21239, 0.78006, 0.00122, 0.00122, 0.00122
16, 0.41436, 0.29784, 0.87488, 0.99964, 1, 0.995, 0.97823, 0.25499, 0.19837, 0.78004, 0.00116, 0.00116, 0.00116
17, 0.414, 0.29771, 0.87575, 0.99943, 1, 0.995, 0.98746, 0.2251, 0.203, 0.77468, 0.0011, 0.0011, 0.0011
18, 0.39273, 0.29075, 0.86927, 0.99445, 1, 0.995, 0.98597, 0.22693, 0.19648, 0.77208, 0.00104, 0.00104, 0.00104
19, 0.40052, 0.28802, 0.87804, 0.99958, 1, 0.995, 0.98541, 0.22268, 0.18749, 0.77233, 0.00098, 0.00098, 0.00098
20, 0.38066, 0.27951, 0.86666, 0.99969, 1, 0.995, 0.98901, 0.20959, 0.1775, 0.7697, 0.00092, 0.00092, 0.00092
21, 0.38115, 0.27813, 0.8658, 0.99964, 1, 0.995, 0.98895, 0.20699, 0.1779, 0.77073, 0.00086, 0.00086, 0.00086
22, 0.37441, 0.27094, 0.87121, 0.99965, 1, 0.995, 0.98975, 0.20138, 0.17235, 0.76785, 0.0008, 0.0008, 0.0008
23, 0.36808, 0.26148, 0.86426, 0.99965, 1, 0.995, 0.98829, 0.19861, 0.1628, 0.76706, 0.00074, 0.00074, 0.00074
24, 0.25547, 0.199, 0.77555, 0.99955, 1, 0.995, 0.98791, 0.21853, 0.18063, 0.76972, 0.00068, 0.00068, 0.00068
25, 0.24799, 0.1969, 0.78404, 0.99958, 1, 0.995, 0.98812, 0.23069, 0.18178, 0.76985, 0.00062, 0.00062, 0.00062
26, 0.24232, 0.1915, 0.78022, 0.99968, 1, 0.995, 0.99024, 0.20883, 0.16788, 0.76752, 0.00056, 0.00056, 0.00056
27, 0.23288, 0.1839, 0.77463, 0.99968, 1, 0.995, 0.99151, 0.2026, 0.16501, 0.76809, 0.0005, 0.0005, 0.0005
28, 0.23066, 0.18012, 0.77547, 0.99961, 1, 0.995, 0.98912, 0.19388, 0.1534, 0.76246, 0.00044, 0.00044, 0.00044
29, 0.22286, 0.17062, 0.77932, 0.9997, 1, 0.995, 0.99039, 0.20566, 0.14978, 0.76601, 0.00038, 0.00038, 0.00038
30, 0.21427, 0.16357, 0.77529, 0.9997, 1, 0.995, 0.99215, 0.18345, 0.14148, 0.76206, 0.00032, 0.00032, 0.00032
31, 0.20895, 0.16067, 0.77189, 0.9997, 1, 0.995, 0.99187, 0.17027, 0.13746, 0.76124, 0.00026, 0.00026, 0.00026
32, 0.20248, 0.15421, 0.77526, 0.9997, 1, 0.995, 0.99246, 0.17229, 0.13828, 0.76056, 0.0002, 0.0002, 0.0002
33, 0.19494, 0.15005, 0.76361, 0.99971, 1, 0.995, 0.99302, 0.16442, 0.12543, 0.76043, 0.00014, 0.00014, 0.00014
1 epoch train/box_loss train/cls_loss train/dfl_loss metrics/precision(B) metrics/recall(B) metrics/mAP50(B) metrics/mAP50-95(B) val/box_loss val/cls_loss val/dfl_loss lr/pg0 lr/pg1 lr/pg2
2 1 0.62674 1.281 0.92555 0.99239 0.99448 0.99323 0.90966 0.40212 0.8264 0.80447 0.00066247 0.00066247 0.00066247
3 2 0.60996 0.71899 0.93387 0.9945 0.99945 0.99484 0.91551 0.43253 0.60301 0.8228 0.0012893 0.0012893 0.0012893
4 3 0.58648 0.54879 0.92909 1 0.98871 0.99494 0.9213 0.40211 0.39327 0.81593 0.0018761 0.0018761 0.0018761
5 4 0.58195 0.48301 0.92375 0.99087 0.9337 0.97172 0.89393 0.41614 0.46785 0.82069 0.00182 0.00182 0.00182
6 5 0.56201 0.44926 0.92381 0.99447 0.99385 0.99494 0.94951 0.34807 0.32406 0.8013 0.00182 0.00182 0.00182
7 6 0.52696 0.40581 0.9068 0.95813 0.98343 0.99281 0.94494 0.33023 0.48053 0.79401 0.00176 0.00176 0.00176
8 7 0.51017 0.3952 0.90752 0.99889 1 0.995 0.95388 0.3192 0.33973 0.7992 0.0017 0.0017 0.0017
9 8 0.50772 0.37889 0.90238 0.98351 0.98842 0.98581 0.94918 0.30154 0.28504 0.79667 0.00164 0.00164 0.00164
10 9 0.47737 0.3576 0.89251 0.99946 0.99448 0.995 0.97205 0.28135 0.23642 0.79101 0.00158 0.00158 0.00158
11 10 0.46587 0.34547 0.89324 0.99948 1 0.995 0.96897 0.28021 0.28522 0.78694 0.00152 0.00152 0.00152
12 11 0.45881 0.33452 0.89055 0.99954 1 0.995 0.97012 0.26364 0.21443 0.7813 0.00146 0.00146 0.00146
13 12 0.44939 0.32887 0.89206 0.9996 1 0.995 0.98382 0.24486 0.20614 0.78109 0.0014 0.0014 0.0014
14 13 0.44388 0.32289 0.88796 0.99932 1 0.995 0.97195 0.27681 0.21443 0.77933 0.00134 0.00134 0.00134
15 14 0.43847 0.31282 0.88496 0.99965 1 0.995 0.98019 0.25014 0.20255 0.7775 0.00128 0.00128 0.00128
16 15 0.41585 0.30067 0.8774 0.99943 1 0.995 0.97609 0.25842 0.21239 0.78006 0.00122 0.00122 0.00122
17 16 0.41436 0.29784 0.87488 0.99964 1 0.995 0.97823 0.25499 0.19837 0.78004 0.00116 0.00116 0.00116
18 17 0.414 0.29771 0.87575 0.99943 1 0.995 0.98746 0.2251 0.203 0.77468 0.0011 0.0011 0.0011
19 18 0.39273 0.29075 0.86927 0.99445 1 0.995 0.98597 0.22693 0.19648 0.77208 0.00104 0.00104 0.00104
20 19 0.40052 0.28802 0.87804 0.99958 1 0.995 0.98541 0.22268 0.18749 0.77233 0.00098 0.00098 0.00098
21 20 0.38066 0.27951 0.86666 0.99969 1 0.995 0.98901 0.20959 0.1775 0.7697 0.00092 0.00092 0.00092
22 21 0.38115 0.27813 0.8658 0.99964 1 0.995 0.98895 0.20699 0.1779 0.77073 0.00086 0.00086 0.00086
23 22 0.37441 0.27094 0.87121 0.99965 1 0.995 0.98975 0.20138 0.17235 0.76785 0.0008 0.0008 0.0008
24 23 0.36808 0.26148 0.86426 0.99965 1 0.995 0.98829 0.19861 0.1628 0.76706 0.00074 0.00074 0.00074
25 24 0.25547 0.199 0.77555 0.99955 1 0.995 0.98791 0.21853 0.18063 0.76972 0.00068 0.00068 0.00068
26 25 0.24799 0.1969 0.78404 0.99958 1 0.995 0.98812 0.23069 0.18178 0.76985 0.00062 0.00062 0.00062
27 26 0.24232 0.1915 0.78022 0.99968 1 0.995 0.99024 0.20883 0.16788 0.76752 0.00056 0.00056 0.00056
28 27 0.23288 0.1839 0.77463 0.99968 1 0.995 0.99151 0.2026 0.16501 0.76809 0.0005 0.0005 0.0005
29 28 0.23066 0.18012 0.77547 0.99961 1 0.995 0.98912 0.19388 0.1534 0.76246 0.00044 0.00044 0.00044
30 29 0.22286 0.17062 0.77932 0.9997 1 0.995 0.99039 0.20566 0.14978 0.76601 0.00038 0.00038 0.00038
31 30 0.21427 0.16357 0.77529 0.9997 1 0.995 0.99215 0.18345 0.14148 0.76206 0.00032 0.00032 0.00032
32 31 0.20895 0.16067 0.77189 0.9997 1 0.995 0.99187 0.17027 0.13746 0.76124 0.00026 0.00026 0.00026
33 32 0.20248 0.15421 0.77526 0.9997 1 0.995 0.99246 0.17229 0.13828 0.76056 0.0002 0.0002 0.0002
34 33 0.19494 0.15005 0.76361 0.99971 1 0.995 0.99302 0.16442 0.12543 0.76043 0.00014 0.00014 0.00014

Some files were not shown because too many files have changed in this diff Show more