alexander bt fixed

This commit is contained in:
IDONTSUDO 2024-06-10 15:16:30 +03:00
parent 61118a7423
commit 8f8fc3107d
46 changed files with 1109 additions and 304 deletions

View file

@ -5,6 +5,9 @@ import { BehaviorTreeDBModel } from "./models/behavior_tree_database_model";
import { ReadByIdDataBaseModelScenario } from "../../core/scenarios/read_by_id_database_model_scenario";
import { GetBehaviorTreeActiveProjectScenario } from "./domain/get_behavior_tree_active_project_scenario";
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";
export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValidationModel, typeof BehaviorTreeDBModel> {
constructor() {
@ -25,5 +28,21 @@ export class BehaviorTreesPresentation extends CrudController<BehaviorTreeValida
subUrl: "by_id",
fn: new ReadByIdDataBaseModelScenario<BehaviorTreeValidationModel>(BehaviorTreeDBModel),
});
this.subRoutes.push({
method: "GET",
subUrl: "cameras",
fn: new GetCameraUseCase()
})
this.subRoutes.push({
method: "GET",
subUrl: "robots",
fn: new GetRobotsUseCase()
})
this.subRoutes.push({
method: "GET",
subUrl: "topics",
fn: new GetTopicsUseCase()
})
}
}

View file

@ -6,7 +6,7 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE
return Result.ok({
skills: [
{
SkillPackage: { name: "Robossembler", version: "1.0", format: "1" },
SkillPackage: { name: "Robossembler", version: "1.0", format: "1", type: "Action" },
Module: { name: "PoseEstimation", description: "Pose Estimation skill with DOPE" },
Launch: { package: "rbs_perception", executable: "pe_dope_lc.py", name: "lc_dope" },
BTAction: [
@ -18,6 +18,14 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE
type: "weights",
dependency: {},
},
{
type: "camera",
dependency: {}
},
{
type: "topic",
dependency: {}
}
],
result: ["POSE"],
},
@ -38,6 +46,28 @@ export class GetBehaviorTreeSkillsTemplatesUseCase extends CallbackStrategyWithE
{ name: "mesh_scale", value: 0.001 },
],
},
{
SkillPackage: { name: "Robossembler", version: "1.0", format: "1" },
Module: { name: "MoveToPose", description: "Move to Pose skill with ? controllers" },
Launch: { package: "rbss_movetopose", executable: "movetopose.py", name: "skill_move" },
BTAction: [
{
name: "move",
type: "action",
param: [{ type: "topic", dependency: { } }],
result: []
}
],
Interface: {
Input: [
{ name: "robotName", type: "ROBOT" },
{ name: "pose", type: "POSE" }
],
Output: []
},
Settings: []
}
],
});
};

View file

@ -0,0 +1,120 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller"
import { Result } from "../../../core/helpers/result"
export class GetCameraUseCase extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => {
return Result.ok({
"camera": [
{
"sid": "1",
"DevicePackage": {
"name": "Robossembler",
"version": "1.0",
"format": "1"
},
"Module": {
"name": "RealSense Dxx",
"description": "ROS Wrapper for Intel(R) RealSense(TM) Cameras"
},
"Launch": {
"package": "realsense2_camera",
"executable": "rs_launch.py"
},
"DTwin": [
{
"interface": {
"input": [
{
"camera_namespace": "robot1"
},
{
"camera_name": "455_1"
},
{
"serial_port": "USB_1"
},
{
"pose": "[0.0,0.0,0.0,0.0,0.0,0.0]"
}
]
}
}
],
"Interface": {
"param": [
{
"type": "camera",
"dependency": {}
}
]
},
"Settings": [
{
"name": "camera_config",
"description": "Camera Config",
"type": "file",
"defaultValue": "{ rgb_camera.profile: 1280x720x15 }"
}
]
},
{
"sid": "2",
"DevicePackage": {
"name": "Robossembler",
"version": "1.0",
"format": "1"
},
"Module": {
"name": "RealSense Dxx",
"description": "ROS Wrapper for Intel(R) RealSense(TM) Cameras"
},
"Launch": {
"package": "realsense2_camera",
"executable": "rs_launch.py"
},
"DTwin": [
{
"interface": {
"input": [
{
"camera_namespace": "robot1"
},
{
"camera_name": "455_1"
},
{
"serial_port": "USB_1"
},
{
"pose": "[0.0,0.0,0.0,0.0,0.0,0.0]"
}
]
}
}
],
"Interface": {
"param": [
{
"type": "camera",
"dependency": {}
}
]
},
"Settings": [
{
"name": "camera_config",
"description": "Camera Config",
"type": "file",
"defaultValue": "{ rgb_camera.profile: 1280x720x15 }"
}
]
}
]
})
}
}

View file

@ -0,0 +1,50 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
export class GetRobotsUseCase extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => {
return Result.ok({
"robots": [
{
"sid": "1",
"DevicePackage": {
"name": "Robossembler",
"version": "1.0",
"format": "1"
},
"Module": {
"name": "RBS",
"description": "Main Robot"
},
"Launch": {
"package": "rbs_bringup",
"executable": "single_robot.launch.py"
},
"DTwin": [
{
"interface": {
"input": [
{
"robot_namespace": "robot1"
},
{
"dof": 6
}
]
}
}
],
"Settings": [
{
"name": "robot_type",
"description": "Type of robot by name",
"defaultValue": "rbs_arm"
}
]
}
]
})
}
}

View file

@ -0,0 +1,23 @@
import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
export class GetTopicsUseCase extends CallbackStrategyWithEmpty {
call = async (): ResponseBase => {
return Result.ok({
"topics": [
{
"sid": "1",
"name": "topic camera 1",
"type": "sensor_msgs/Image"
},
{
"sid": "2",
"name": "topic camera 2",
"type": "sensor_msgs/Image"
}
]
}
)
}
}

1
ui/.mason/bricks.json Normal file
View file

@ -0,0 +1 @@
{"base_feature":"/Users/idontsudo/ozon/ui/bricks/base_feature","form":"/Users/idontsudo/webservice/ui/bricks/form"}

View file

@ -0,0 +1,11 @@
import { Result } from "../../core/helper/result";
export class NumberTriviaModel {
constructor() {}
isValid(): Result<string, void> {
return Result.ok();
}
static empty() {
return new NumberTriviaModel();
}
}

View file

@ -0,0 +1,3 @@
export class {{name.pascalCase()}}Repository {
}

View file

@ -0,0 +1,10 @@
import { observer } from "mobx-react-lite";
import { {{name.pascalCase()}}Store } from "./{{name.snakeCase()}}_store";
import React from "react";
export const {{name.pascalCase()}}ScreenPath = "/auth";
export const {{name.pascalCase()}}Screen = observer(() => {
const [store] = React.useState(() => new {{name.pascalCase()}}Store());
return <></>;
});

View file

@ -0,0 +1,7 @@
import makeAutoObservable from "mobx-store-inheritance";
export class {{name.pascalCase()}}Store {
constructor() {
makeAutoObservable(this);
}
}

View file

@ -0,0 +1,16 @@
name: base_feature
description: A brick to create Clean Architecture Feature
version: 0.0.1
# The following defines the environment for the current brick.
# It includes the version of mason that the brick requires.
environment:
mason: ">=0.1.0-dev.26 <0.1.0"
vars:
name:
type: string
description: The feature name
default: my feature
prompt: What's your feature name (e.g. dance school)

View file

@ -0,0 +1,11 @@
import { Result } from "../../../../../../core/helper/result";
export class NumberTriviaModel {
constructor() {}
isValid(): Result<string, void> {
return Result.ok();
}
static empty() {
return new NumberTriviaModel();
}
}

View file

@ -0,0 +1,14 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { {{ name.pascalCase() }}Store } from "./{{name.snakeCase()}}_store";
import { IPropsForm } from "../forms";
import { NumberTriviaModel } from "./number_trivia";
export const {{ name.pascalCase()}} = observer((props: IPropsForm<Partial<NumberTriviaModel>>) => {
const [store] = React.useState(() => new {{ name.pascalCase() }}Store());
React.useEffect(() => {
store.init();
}, [store]);
return <></>;
});

View file

@ -0,0 +1,5 @@
import { HttpRepository } from "../../../../../../core/repository/http_repository";
export class {{name.pascalCase()}}HttpRepository extends HttpRepository {
}

View file

@ -0,0 +1,19 @@
import makeAutoObservable from "mobx-store-inheritance";
import { NavigateFunction } from "react-router-dom";
import { NumberTriviaModel } from "./number_trivia";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { {{name.pascalCase()}}HttpRepository } from "./{{name.snakeCase()}}_http_repository";
export class {{name.pascalCase()}}Store extends FormState<NumberTriviaModel, CoreError> {
constructor() {
super();
makeAutoObservable(this);
}
viewModel: NumberTriviaModel = NumberTriviaModel.empty();
cameraDeviceHttpRepository: {{name.pascalCase()}}HttpRepository = new {{name.pascalCase()}}HttpRepository();
errorHandingStrategy = (error: CoreError) => { }
init = async (navigate?: NavigateFunction | undefined) => {
}
}

16
ui/bricks/form/brick.yaml Normal file
View file

@ -0,0 +1,16 @@
name: form
description: A brick to create form at behavior_tree_builder
version: 0.0.1
# The following defines the environment for the current brick.
# It includes the version of mason that the brick requires.
environment:
mason: ">=0.1.0-dev.26 <0.1.0"
vars:
name:
type: string
description: The feature name
default: my feature
prompt: What's your feature name (e.g. dance school)

1
ui/mason-lock.json Normal file
View file

@ -0,0 +1 @@
{"bricks":{"base_feature":{"path":"bricks/base_feature"},"form":{"path":"bricks/form"}}}

5
ui/mason.yaml Normal file
View file

@ -0,0 +1,5 @@
bricks:
base_feature:
path: base_feature
form:
path: form

46
ui/readme.md Normal file
View file

@ -0,0 +1,46 @@
# Зависимости
brew install mason
### Инициализация
В корне проекта вызовите команду `init`, которая создаст папку `.mason/`
```
mason init
```
### Использование 👷
Все готовые `brick'и` хранятся в папке `/bricks`
Для примера попробуем использовать `brick` под названием `base_feature`
```
# Добавляем `brick` себе в mason (посмотреть уже добавленные можно через `mason ls/list`, а удалить через `mason remove`)
mason add base_feature --path bricks/base_feature
# Используем `brick` для в интересующей нас папке (поскольку это шаблон фичи, выбрана папка src/features/)
mason make base_feature -o lib/features
```
Далее необходимо ответить на вопросы задаваемые в CLI и на основе ответов `brick` сгенериует фичу
### Разработка
В папке `bricks/` вы можете создать свой `brick`
```
# создать hello brick
mason new my_brick_name
```
Далее всю структуру папок и файлов необходимо описать в папке `__brick__`
Для большей информации [читайте и смотрите примеры](https://github.com/felangel/mason/blob/master/packages/mason_cli/README.md)
# Добавить новую форму
mason make form -o ./src/features/behavior_tree_builder/presentation/ui/forms
# Добавить новый экран

View file

@ -0,0 +1,63 @@
export interface Cameras {
camera: Camera[];
}
export interface Camera {
sid: string;
DevicePackage: DevicePackage;
Module: Module;
Launch: Launch;
DTwin: DTwin[];
Interface: InterfaceClass;
Settings: Setting[];
}
export interface DTwin {
interface: Interface;
}
export interface Interface {
input: Input[];
}
export interface Input {
camera_namespace?: string;
camera_name?: string;
serial_port?: string;
pose?: string;
}
export interface DevicePackage {
name: string;
version: string;
format: string;
}
export interface InterfaceClass {
param: Param[];
}
export interface Param {
type: string;
dependency: Dependency;
}
export interface Dependency {
}
export interface Launch {
package: string;
executable: string;
}
export interface Module {
name: string;
description: string;
}
export interface Setting {
name: string;
description: string;
type: string;
defaultValue: string;
}

View file

@ -0,0 +1,18 @@
import { Result } from "../helper/result";
import { IDeviceDependency } from "./skill_model";
export class SidViewModel implements IDeviceDependency {
sid: string;
constructor(sid: string) {
this.sid = sid;
}
valid = (): Result<string, SidViewModel> => {
if (this.sid.isEmpty()) {
return Result.error('sid is empty')
}
return Result.ok(this)
}
static empty() {
return new SidViewModel('')
}
}

View file

@ -0,0 +1,47 @@
export interface Robots {
robots: Robot[];
}
export interface Robot {
sid: string;
DevicePackage: DevicePackage;
Module: Module;
Launch: Launch;
DTwin: DTwin[];
Settings: Setting[];
}
export interface DTwin {
interface: Interface;
}
export interface Interface {
input: Input[];
}
export interface Input {
robot_namespace?: string;
dof?: number;
}
export interface DevicePackage {
name: string;
version: string;
format: string;
}
export interface Launch {
package: string;
executable: string;
}
export interface Module {
name: string;
description: string;
}
export interface Setting {
name: string;
description: string;
defaultValue: string;
}

View file

@ -26,11 +26,14 @@ export interface ISkill {
xxx: IXxx;
}
export interface IWeightsDependency {
weights_name:string;
weights_name: string;
object_name: string;
weights_file: string;
dimensions: number[];
}
export interface IDeviceDependency {
sid: string;
}
export interface IParam {
type: string;
@ -195,7 +198,7 @@ export class SkillModel implements ISkill {
}
export class SkillDependency implements IDependency {
constructor(public skills: ISkillDependency[]) {}
constructor(public skills: ISkillDependency[]) { }
static empty() {
return new SkillDependency([]);
}

View file

@ -0,0 +1,9 @@
export interface Topics {
topics: Topic[];
}
export interface Topic {
sid: string;
name: string;
type: string;
}

View file

@ -44,10 +44,10 @@ export abstract class UiLoader {
};
messageHttp = async <T>(callBack: Promise<Result<CoreError, T>>, report?: IMessage) => {
return (await this.httpHelper(callBack)).fold(
(s) => {
(_s) => {
if (report && report.successMessage) message.success(report.successMessage);
},
(e) => {
(_e) => {
if (report && report.errorMessage) message.error(report.errorMessage);
}
);

View file

@ -1,81 +1,75 @@
import * as React from "react";
import { IStyle } from "../../model/style";
export enum CoreTextType {
header,
medium,
large,
small,
header = 'header',
medium = 'medium',
large = 'large',
small = 'small',
}
export interface ITextProps {
export interface ITextProps extends IStyle {
text: string;
type: CoreTextType;
color?: string;
}
export function CoreText(props: ITextProps) {
if (props.type === CoreTextType.small) {
return (
<div
style={{
color: props.color ?? "rgba(73, 69, 79, 1)",
fontSize: 12,
fontFamily: "Roboto",
fontWeight: 400,
fontSizeAdjust: 14,
textOverflow: "ellipsis",
}}
>
{props.text}
</div>
);
const getStyle = (type: CoreTextType, color: string | undefined) => {
if (type.isEqual(CoreTextType.small)) return {
color: color ?? "rgba(73, 69, 79, 1)",
fontSize: 12,
fontFamily: "Roboto",
fontWeight: 400,
fontSizeAdjust: 14,
textOverflow: "ellipsis",
}
if (props.type === CoreTextType.large) {
return (
<div
style={{
color: props.color ?? "#1D1B20",
fontSize: 16,
fontFamily: "Roboto",
fontWeight: 400,
fontSizeAdjust: 14,
textOverflow: "ellipsis",
}}
>
{props.text}
</div>
);
}
if (props.type === CoreTextType.medium)
return (
<div
style={{
color: props.color ?? "#1D1B20",
fontSize: 14,
fontFamily: "Roboto",
fontWeight: 400,
textOverflow: "ellipsis",
fontSizeAdjust: 14,
}}
>
{props.text}
</div>
);
if (props.type === CoreTextType.header)
return (
<div
style={{
color: props.color ?? "#1D1B20",
fontSize: 20,
fontFamily: "Roboto",
fontWeight: 500,
textOverflow: "ellipsis",
fontSizeAdjust: 16,
}}
>
{props.text}
</div>
);
return <div>{props.text}</div>;
if (type.isEqual(CoreTextType.large)) return {
color: color ?? "#1D1B20",
fontSize: 16,
fontFamily: "Roboto",
fontWeight: 400,
fontSizeAdjust: 14,
textOverflow: "ellipsis",
}
if (type.isEqual(CoreTextType.medium)) return {
color: color ?? "#1D1B20",
fontSize: 14,
fontFamily: "Roboto",
fontWeight: 400,
textOverflow: "ellipsis",
fontSizeAdjust: 14,
}
if (type.isEqual(CoreTextType.header)) return {
color: color ?? "#1D1B20",
fontSize: 20,
fontFamily: "Roboto",
fontWeight: 500,
textOverflow: "ellipsis",
fontSizeAdjust: 16,
}
return {
color: color ?? "rgba(73, 69, 79, 1)",
fontSize: 12,
fontFamily: "Roboto",
fontWeight: 400,
fontSizeAdjust: 14,
textOverflow: "ellipsis",
}
}
const appendStyle = (type: CoreTextType, color: string | undefined, style: React.CSSProperties | undefined) => {
return Object.assign(getStyle(type, color), style)
}
export function CoreText(props: ITextProps) {
return (
<div
style={appendStyle(props.type, props.color, props.style)}
>
{props.text}
</div>
);
}

View file

@ -0,0 +1,80 @@
import { extensions } from "../../../core/extensions/extensions";
import { Form } from "../presentation/ui/forms/forms";
import { ISkillView } from "../presentation/ui/skill_tree/skill_tree";
extensions();
export enum SystemPrimitive {
Sequence = 'Sequence',
Fallback = "Fallback",
ReactiveSequence = "ReactiveSequence",
SequenceWithMemory = 'SequenceWithMemory',
ReactiveFallback = "ReactiveFallback",
Decorator = "Decorator",
Inverter = "Inverter",
ForceSuccess = "ForceSuccess",
ForceFailure = "ForceFailure",
Repeat = "Repeat",
RetryUntilSuccessful = "RetryUntilSuccessful",
KeepRunningUntilFailure = "KeepRunningUntilFailure",
Delay = "Delay",
RunOnce = "RunOnce",
}
interface ISystemPrimitive {
label: string;
group: string;
css?: React.CSSProperties;
input: boolean;
output: boolean;
}
export class PrimitiveViewModel {
primitives: ISystemPrimitive[] = [
{
label: SystemPrimitive.Inverter,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid #003E61",
borderRadius: 33,
background: "#BDE8AE",
},
},
{
label: SystemPrimitive.ForceSuccess,
group: SystemPrimitive.Decorator,
input: true,
output: true,
css: {
border: "1px solid #003E61",
borderRadius: 33,
background: "#BDE8AE",
},
},
{ label: SystemPrimitive.ForceFailure, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.Repeat, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.RetryUntilSuccessful, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.KeepRunningUntilFailure, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.Delay, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.RunOnce, group: SystemPrimitive.Decorator, input: true, output: true },
{ label: SystemPrimitive.ReactiveSequence, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.Sequence, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.SequenceWithMemory, group: SystemPrimitive.Sequence, input: true, output: true },
{ label: SystemPrimitive.ReactiveFallback, group: SystemPrimitive.Fallback, input: true, output: true },
{ label: SystemPrimitive.Fallback, group: SystemPrimitive.Fallback, input: true, output: true },
];
getPrimitiveAtLabel = (name: string) => {
return this.primitives.find((el) => el.label.isEqual(name));
}
toSkillView = () => this.primitives.reduce<ISkillView[]>((acc, primitive) => {
acc.rFind<ISkillView>((el) => el.name.isEqual(primitive.group)).fold(() => {
acc.at(acc.findIndex((eeee) => eeee.name === primitive.group))?.children?.push({ name: primitive.label })
}, () => { acc.push({ name: primitive.group, children: [{ name: primitive.label }] }) })
return acc;
}, [])
}

View file

@ -169,3 +169,4 @@ export const BehaviorTreeBuilderScreen = observer(() => {
/>
);
});

View file

@ -23,6 +23,8 @@ import { v4 } from "uuid";
import { behaviorTreeBuilderStore } from "./behavior_tree_builder_screen";
import clone from "just-clone";
import { BehaviorTreeModel } from "../model/behavior_tree_model";
import { PrimitiveViewModel, SystemPrimitive } from "../model/primitive_view_model";
interface I2DArea {
x: number;
@ -41,10 +43,6 @@ export enum StoreUIType {
ViewBehaviorTree,
}
export enum SystemPrimitive {
Sequence = "Sequence",
Fallback = "Fallback",
}
export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel, CoreError> {
type: StoreUIType = StoreUIType.ViewBehaviorTree;
@ -67,6 +65,8 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
navigate?: NavigateFunction;
editor?: NodeEditor<Schemes>;
areaPlugin?: AreaPlugin<Schemes, AreaExtra>;
nodeUpdateObserver?: NodeRerenderObserver;
primitiveViewModel: PrimitiveViewModel;
skillTree: ISkillView = {
name: "",
children: [
@ -74,20 +74,17 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
name: "Действия",
children: [],
},
{
name: "Примитивы BT",
children: [
{ name: "Fallback", interface: "Vector3", out: "vo" },
{ name: "Sequence", interface: "Vector3", out: "vo" },
],
},
],
};
nodeUpdateObserver?: NodeRerenderObserver;
constructor() {
super(DrawerState);
makeAutoObservable(this);
this.type = StoreUIType.SelectBehaviorTree;
this.primitiveViewModel = new PrimitiveViewModel()
this.skillTree.children?.push({ name: "Примитивы BT", children: this.primitiveViewModel.toSkillView() })
}
syncScene = async (editor: NodeEditor<Schemes>, area: AreaPlugin<Schemes, AreaExtra>) => {
@ -95,7 +92,7 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
this.areaPlugin = area;
};
errorHandingStrategy = (_: CoreError) => {};
errorHandingStrategy = (_: CoreError) => { };
dragEnd = (e: EventTarget) => {
if (this.canRun) {
@ -205,8 +202,8 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
};
}
dispose(): void {
document.removeEventListener("keyup", () => {});
document.removeEventListener("keydown", () => {});
document.removeEventListener("keyup", () => { });
document.removeEventListener("keydown", () => { });
}
onClickSaveBehaviorTree = async (): Promise<void> => {
this.filledOutTemplates.validation().fold(
@ -236,7 +233,7 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
async () => message.error(`Дерево поведения не заполнено`)
);
};
validateBt() {}
validateBt() { }
createNewBehaviorTree = async () => {
this.viewModel.project = this.activeProject;
this.viewModel.valid().fold(
@ -267,7 +264,6 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
}
};
formUpdateDependency = (dependency: Object, formType: string) => {
this.edtDrawer(DrawerState.editThreadBehaviorTree, false);
this.filledOutTemplates?.skillBySid(this.selectedSid ?? "").fold(
(m) => {
const model = clone(m);
@ -286,6 +282,7 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
});
this.filledOutTemplates.updateSkill(model);
console.log(this.filledOutTemplates)
},
() => console.log("UNKNOWN SID: " + this.selectedSid)
);
@ -293,7 +290,8 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
this.nodeUpdateObserver?.emit({ id: this.selectedSid, type: UpdateEvent.UPDATE });
};
getBodyNode(label: string, sid: string): React.ReactNode | null {
if (Object.keys(SystemPrimitive).includes(label)) {
const primitive = this.primitiveViewModel.getPrimitiveAtLabel(label);
if (primitive) {
return null;
}
return (
@ -323,18 +321,24 @@ export class BehaviorTreeBuilderStore extends UiFormState<BehaviorTreeViewModel,
}
isFilledInput = (type: string, sid: string): boolean => this.filledOutTemplates?.dependencyIsFilled(type, sid);
getInputs(name: string) {
if (Object.keys(SystemPrimitive).includes(name)) {
const result = this.primitiveViewModel.getPrimitiveAtLabel(name)
if (result) {
return {
input: "a",
output: "a",
input: result.input ? "a" : null,
output: result.output ? "a" : null,
};
}
return {
input: "a",
output: null,
};
input: 'a',
output: null
}
}
getStylesByLabelNode(label: string): React.CSSProperties | undefined {
const result = this.primitiveViewModel.getPrimitiveAtLabel(label)
if (result) {
return clone(result.css as Object);
}
if (label.isEqual(SystemPrimitive.Fallback)) {
return {
border: "1px solid #003E61",

View file

@ -0,0 +1,38 @@
import React from "react"
import { observer } from "mobx-react-lite"
import { message } from "antd";
import { CameraDeviceStore } from "./camera_device_store"
import { IPropsForm } from "../forms";
import { IDeviceDependency } from "../../../../../../core/model/skill_model";
import { CoreButton } from "../../../../../../core/ui/button/button";
import { CoreSelect } from "../../../../../../core/ui/select/select";
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
export const CameraDeviceForm = observer((props: IPropsForm<Partial<IDeviceDependency>>) => {
const [store] = React.useState(() => new CameraDeviceStore());
React.useEffect(() => {
store.init();
}, [store]);
return (
<div style={{ border: '1px solid', margin: 10, padding: 10 }}>
<CoreText text={"Cameras"} type={CoreTextType.header} style={{ padding: 10 }} />
<CoreSelect
items={store.cameras?.camera.map((el) => el.sid) ?? []}
value={props.dependency?.sid ?? ""}
label={'Выберите камеру'}
onChange={(value: string) =>
store.updateForm({ sid: value })
} />
<CoreButton style={{ margin: 10, width: 100 }} text="OK" onClick={() => {
store.viewModel.valid().fold((s) => {
props.onChange(s);
}, (e) => message.error(e))
}} />
</div>
)
})

View file

@ -0,0 +1,6 @@
import { Cameras } from "../../../../../../core/model/cameras";
import { HttpMethod, HttpRepository } from "../../../../../../core/repository/http_repository";
export class CameraDeviceHttpRepository extends HttpRepository {
getAllCameras = () => this._jsonRequest<Cameras>(HttpMethod.GET, '/behavior/trees/cameras');
}

View file

@ -0,0 +1,21 @@
import makeAutoObservable from "mobx-store-inheritance";
import { NavigateFunction } from "react-router-dom";
import { Cameras } from "../../../../../../core/model/cameras";
import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { CameraDeviceHttpRepository } from "./camera_device_form_http_repository";
export class CameraDeviceStore extends FormState<SidViewModel, CoreError> {
constructor() {
super();
makeAutoObservable(this);
}
viewModel: SidViewModel = SidViewModel.empty();
cameras?: Cameras;
cameraDeviceHttpRepository: CameraDeviceHttpRepository = new CameraDeviceHttpRepository();
errorHandingStrategy = (error: CoreError) => { }
init = async (navigate?: NavigateFunction | undefined) => {
this.mapOk('cameras', this.cameraDeviceHttpRepository.getAllCameras())
}
}

View file

@ -1,6 +1,8 @@
import { SimpleForm } from "./simple_form";
import { TestForm } from "./test";
import WeightsForm from "./weights_form";
import { CameraDeviceForm } from "./camera_device_form/camera_device_form_form";
import { RobotDeviceForm } from "./robot_device_form/robot_device_form_form";
import { TopicsForm } from "./topics_form/topics_form";
import { WeightsForm } from "./weights_form/weights_form";
export interface IPropsForm<T> {
dependency: T;
@ -11,9 +13,15 @@ export interface IForms {
name: string;
component: JSX.Element;
}
export enum Form {
weights = 'weights',
robotName = 'robot_name',
cameraDeviceForm = 'camera',
topic = 'topic'
}
export const forms = (props: any, onChange: (dependency: Object) => void): IForms[] => [
{ name: "weights", component: <WeightsForm dependency={props} onChange={onChange} /> },
{ name: "simple", component: <SimpleForm dependency={props} onChange={onChange} /> },
{ name: "test", component: <TestForm dependency={props} onChange={onChange} /> },
{ name: Form.weights, component: <WeightsForm dependency={props} onChange={onChange} /> },
{ name: Form.robotName, component: <RobotDeviceForm dependency={props} onChange={onChange} /> },
{ name: Form.cameraDeviceForm, component: <CameraDeviceForm dependency={props} onChange={onChange} /> },
{ name: Form.topic, component: <TopicsForm dependency={props} onChange={onChange} /> }
];

View file

@ -0,0 +1,14 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { RobotDeviceFormStore } from "./robot_device_form_store";
import { IPropsForm } from "../forms";
import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model";
export const RobotDeviceForm = observer((props: IPropsForm<Partial<SidViewModel>>) => {
const [store] = React.useState(() => new RobotDeviceFormStore());
React.useEffect(() => {
store.init();
}, [store]);
return <div></div>;
});

View file

@ -0,0 +1,5 @@
import { HttpRepository } from "../../../../../../core/repository/http_repository";
export class RobotDeviceFormHttpRepository extends HttpRepository {
}

View file

@ -0,0 +1,19 @@
import { makeAutoObservable } from "mobx";
import { NavigateFunction } from "react-router-dom";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { RobotDeviceFormHttpRepository } from "./robot_device_form_http_repository";
import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model";
export class RobotDeviceFormStore extends FormState<SidViewModel, CoreError> {
constructor() {
super();
makeAutoObservable(this);
}
viewModel: SidViewModel = SidViewModel.empty();
cameraDeviceHttpRepository: RobotDeviceFormHttpRepository = new RobotDeviceFormHttpRepository();
errorHandingStrategy = (error: CoreError) => { }
init = async (navigate?: NavigateFunction | undefined) => {
}
}

View file

@ -1,14 +0,0 @@
import { CoreButton } from "../../../../../core/ui/button/button";
import { IPropsForm } from "./forms";
export interface ISimpleFormDependency {
simple?: string;
}
export const SimpleForm = (props: IPropsForm<ISimpleFormDependency>) => {
return (
<div>
<CoreButton onClick={() => props.onChange({ simple: "132" })} text="OK" />
</div>
);
};

View file

@ -1,13 +0,0 @@
import { CoreButton } from "../../../../../core/ui/button/button";
import { CoreText, CoreTextType } from "../../../../../core/ui/text/text";
import { IPropsForm } from "./forms";
import { ISimpleFormDependency } from "./simple_form";
export const TestForm = (props: IPropsForm<ISimpleFormDependency>) => {
return (
<div>
<CoreText text="Test Form" type={CoreTextType.header} />
<CoreButton onClick={() => props.onChange({ test: "132" })} text="OK" />
</div>
);
};

View file

@ -0,0 +1,18 @@
import makeAutoObservable from "mobx-store-inheritance";
import { Result } from "../../../../../../core/helper/result";
import { SidViewModel } from "../../../../../../core/model/device_dependency_view_model";
export class TopicDependencyViewModel extends SidViewModel {
axis: boolean;
constructor(sid: string, axis: boolean) {
super(sid);
this.axis = axis;
makeAutoObservable(this)
}
isValid = (): Result<string, void> => {
return Result.ok();
}
static empty() {
return new TopicDependencyViewModel('', false);
}
}

View file

@ -0,0 +1,40 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { TopicsFormStore } from "./topics_form_store";
import { IPropsForm } from "../forms";
import { TopicDependencyViewModel } from "./topic_dependency_view_model";
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
import { CoreSwitch } from "../../../../../../core/ui/switch/switch";
import { CoreSelect } from "../../../../../../core/ui/select/select";
import { CoreButton } from "../../../../../../core/ui/button/button";
import { message } from "antd";
export const TopicsForm = observer((props: IPropsForm<Partial<TopicDependencyViewModel>>) => {
const [store] = React.useState(() => new TopicsFormStore());
React.useEffect(() => {
store.init();
}, [store, props]);
return <div style={{ border: '1px solid', margin: 10, padding: 10 }}>
<CoreText text={"Topics"} type={CoreTextType.header} style={{ padding: 10 }} />
<CoreSelect
items={store.topics?.topics?.map((el) => el.name) ?? []}
value={props.dependency?.sid ?? ""}
label={'Выберите топик'}
onChange={(value: string) =>
store.updateForm({ sid: value })
} />
<div>is input: <CoreSwitch isSelected={store.viewModel.axis} id={""} onChange={(status: boolean, id: string) => {
console.log(200)
store.updateForm({ axis: !status })
}} /></div>
<CoreButton style={{ margin: 10, width: 100 }} text="OK" onClick={() => {
store.viewModel.valid().fold((s) => {
props.onChange(s);
}, (e) => message.error(e))
}} />
</div>;
});

View file

@ -0,0 +1,6 @@
import { Topics } from "../../../../../../core/model/topics";
import { HttpMethod, HttpRepository } from "../../../../../../core/repository/http_repository";
export class TopicsFormHttpRepository extends HttpRepository {
getAllTopics = () => this._jsonRequest<Topics>(HttpMethod.GET, '/behavior/trees/topics');
}

View file

@ -0,0 +1,21 @@
import makeAutoObservable from "mobx-store-inheritance";
import { NavigateFunction } from "react-router-dom";
import { TopicDependencyViewModel } from "./topic_dependency_view_model";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { TopicsFormHttpRepository } from "./topics_form_http_repository";
import { Topics } from "../../../../../../core/model/topics";
export class TopicsFormStore extends FormState<TopicDependencyViewModel, CoreError> {
constructor() {
super();
makeAutoObservable(this);
}
topics?: Topics;
viewModel: TopicDependencyViewModel = TopicDependencyViewModel.empty();
cameraDeviceHttpRepository: TopicsFormHttpRepository = new TopicsFormHttpRepository();
errorHandingStrategy = (error: CoreError) => { }
init = async (navigate?: NavigateFunction | undefined) => {
await this.mapOk('topics', this.cameraDeviceHttpRepository.getAllTopics())
}
}

View file

@ -1,173 +0,0 @@
import React from "react";
import makeAutoObservable from "mobx-store-inheritance";
import { IWeightsDependency } from "../../../../../core/model/skill_model";
import { CoreError, FormState } from "../../../../../core/store/base_store";
import { ISkils, SkillsHttpRepository } from "../../../../skils/skills_http_repository";
import { observer } from "mobx-react-lite";
import { CoreButton } from "../../../../../core/ui/button/button";
import { Result } from "../../../../../core/helper/result";
import { Select, message } from "antd";
import { CoreInput } from "../../../../../core/ui/input/input";
import { DataSetHttpRepository } from "../../../../dataset/dataset_http_repository";
import { Assets } from "../../../../dataset/dataset_model";
export class WeightsViewModel implements IWeightsDependency {
constructor(
public object_name: string = "",
public weights_file: string = "",
public dimensions: number[] = [],
public weights_name = ""
) {}
static empty() {
return new WeightsViewModel();
}
isEmpty = (): Result<string, undefined> => {
if (this.weights_name.isEmpty()) {
return Result.error("weights_name is empty");
}
if (this.object_name.isEmpty()) {
return Result.error("object name is empty");
}
if (this.weights_file.isEmpty()) {
return Result.error("weights_file is empty");
}
if (this.dimensions.isEmpty()) {
return Result.error("dimensions is empty");
}
return Result.ok(undefined);
};
}
export class WeightsFormStore extends FormState<WeightsViewModel, CoreError> {
weights?: ISkils[];
assets?: Assets;
suitableWeights: string[] = [];
viewModel: WeightsViewModel = WeightsViewModel.empty();
skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository();
datasetHttpRepository: DataSetHttpRepository = new DataSetHttpRepository();
constructor() {
super();
makeAutoObservable(this);
}
init = async () => {
await this.mapOk("weights", this.skillsHttpRepository.getAllSkills());
await this.mapOk("assets", this.datasetHttpRepository.getAssetsActiveProject());
};
changeDimensions = (index: number, value: number) => {
this.viewModel.dimensions[index] = value;
};
selectAsset = (): void => {
this.suitableWeights =
this.weights
?.filter((el) =>
el.datasetId.dataSetObjects.filter((datasetObject: string) =>
this.viewModel.object_name.isEqual(datasetObject)
)
)
.map((el) => el.name) ?? [];
};
updateWeights = (text: string) => {
const model = this.weights
?.filter((el) =>
el.datasetId.dataSetObjects.filter((datasetObject: string) => this.viewModel.object_name.isEqual(datasetObject))
)
.at(0);
this.updateForm({ weights_file: `${model?.datasetId.local_path}/weights/${model?.name}/${model?.name}.pt` });
};
errorHandingStrategy = (_: CoreError) => {};
}
interface IWeightsFormProps {
dependency?: IWeightsDependency;
onChange: (dependency: IWeightsDependency) => void;
}
const WeightsForm = observer((props: IWeightsFormProps) => {
const [store] = React.useState(() => new WeightsFormStore());
React.useEffect(() => {
store.init();
}, [store]);
return (
<div>
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.object_name}
onChange={(e) => {
store.updateForm({ object_name: e });
store.selectAsset();
}}
filterOption={(input: string, option?: { label: string; value: string }) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
options={
store.assets?.assets.map((el) => {
return { label: el.name, value: el.name };
}) ?? []
}
/>
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.weights_name}
onChange={(e) => {
store.updateForm({ weights_name: e });
store.updateWeights(e);
}}
filterOption={(input: string, option?: { label: string; value: string }) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
options={
store.suitableWeights?.map((el) => {
return { label: el, value: el };
}) ?? []
}
/>
<div style={{ height: 5 }} />
<CoreInput
label={"dimensions 1"}
onChange={(text) => store.changeDimensions(0, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(0)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 2"}
onChange={(text) => store.changeDimensions(1, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(1)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 3"}
onChange={(text) => store.changeDimensions(2, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(2)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreButton
onClick={() => {
store.viewModel.isEmpty().fold(
() => {
props.onChange(store.viewModel);
},
(e) => {
message.error(e);
}
);
}}
text="OK"
style={{ width: 100 }}
/>
</div>
);
});
export default WeightsForm;

View file

@ -0,0 +1,105 @@
import { observer } from "mobx-react-lite";
import React from "react";
import { Select, message } from "antd";
import { WeightsFormStore } from "./weights_store";
import { IWeightsDependency } from "../../../../../../core/model/skill_model";
import { CoreButton } from "../../../../../../core/ui/button/button";
import { CoreInput } from "../../../../../../core/ui/input/input";
import { CoreText, CoreTextType } from "../../../../../../core/ui/text/text";
interface IWeightsFormProps {
dependency?: IWeightsDependency;
onChange: (dependency: IWeightsDependency) => void;
}
export const WeightsForm = observer((props: IWeightsFormProps) => {
const [store] = React.useState(() => new WeightsFormStore());
React.useEffect(() => {
store.init();
}, [store]);
return (
<div style={{ border: '1px solid', padding: 10, margin: 10 }}>
<CoreText text="Weights" type={CoreTextType.header} style={{ padding: 10 }} />
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.object_name}
onChange={(e) => {
store.updateForm({ object_name: e });
store.selectAsset();
}}
filterOption={(input: string, option?: { label: string; value: string }) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
options={
store.assets?.assets.map((el) => {
return { label: el.name, value: el.name };
}) ?? []
}
/>
<Select
showSearch
placeholder="Выберите деталь"
optionFilterProp="children"
defaultValue={props.dependency?.weights_name}
onChange={(e) => {
store.updateForm({ weights_name: e });
store.updateWeights(e);
}}
filterOption={(input: string, option?: { label: string; value: string }) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
options={
store.suitableWeights?.map((el) => {
return { label: el, value: el };
}) ?? []
}
/>
<div style={{ height: 5 }} />
<CoreInput
label={"dimensions 1"}
onChange={(text) => store.changeDimensions(0, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(0)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 2"}
onChange={(text) => store.changeDimensions(1, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(1)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreInput
label={"dimensions 3"}
onChange={(text) => store.changeDimensions(2, Number(text))}
validation={(text) => Number().isValid(text)}
value={props.dependency?.dimensions?.at(2)?.toString()}
/>
<div style={{ height: 15 }} />
<CoreButton
onClick={() => {
store.viewModel.isEmpty().fold(
() => {
props.onChange(store.viewModel);
},
(e) => {
message.error(e);
}
);
}}
text="OK"
style={{ width: 100 }}
/>
</div>
);
});

View file

@ -0,0 +1,48 @@
import makeAutoObservable from "mobx-store-inheritance";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { DataSetHttpRepository } from "../../../../../dataset/dataset_http_repository";
import { Assets } from "../../../../../dataset/dataset_model";
import { ISkils, SkillsHttpRepository } from "../../../../../skils/skills_http_repository";
import { WeightsViewModel } from "./weights_view_model";
export class WeightsFormStore extends FormState<WeightsViewModel, CoreError> {
weights?: ISkils[];
assets?: Assets;
suitableWeights: string[] = [];
viewModel: WeightsViewModel = WeightsViewModel.empty();
skillsHttpRepository: SkillsHttpRepository = new SkillsHttpRepository();
datasetHttpRepository: DataSetHttpRepository = new DataSetHttpRepository();
constructor() {
super();
makeAutoObservable(this);
}
init = async () => {
await this.mapOk("weights", this.skillsHttpRepository.getAllSkills());
await this.mapOk("assets", this.datasetHttpRepository.getAssetsActiveProject());
};
changeDimensions = (index: number, value: number) => {
this.viewModel.dimensions[index] = value;
};
selectAsset = (): void => {
this.suitableWeights =
this.weights
?.filter((el) =>
el.datasetId.dataSetObjects.filter((datasetObject: string) =>
this.viewModel.object_name.isEqual(datasetObject)
)
)
.map((el) => el.name) ?? [];
};
updateWeights = (text: string) => {
const model = this.weights
?.filter((el) =>
el.datasetId.dataSetObjects.filter((datasetObject: string) => this.viewModel.object_name.isEqual(datasetObject))
)
.at(0);
this.updateForm({ weights_file: `${model?.datasetId.local_path}/weights/${model?.name}/${model?.name}.pt` });
};
errorHandingStrategy = (_: CoreError) => { };
}

View file

@ -0,0 +1,30 @@
import { Result } from "../../../../../../core/helper/result";
import { IWeightsDependency } from "../../../../../../core/model/skill_model";
export class WeightsViewModel implements IWeightsDependency {
constructor(
public object_name: string = "",
public weights_file: string = "",
public dimensions: number[] = [],
public weights_name = ""
) { }
static empty() {
return new WeightsViewModel();
}
isEmpty = (): Result<string, undefined> => {
if (this.weights_name.isEmpty()) {
return Result.error("weights_name is empty");
}
if (this.object_name.isEmpty()) {
return Result.error("object name is empty");
}
if (this.weights_file.isEmpty()) {
return Result.error("weights_file is empty");
}
if (this.dimensions.isEmpty()) {
return Result.error("dimensions is empty");
}
return Result.ok(undefined);
};
}