added scene manager

This commit is contained in:
IDONTSUDO 2024-01-23 17:26:59 +03:00
parent 2adb939f37
commit 9028186a74
34 changed files with 962 additions and 281 deletions

View file

@ -6,7 +6,7 @@ export const validationModelMiddleware = (
type: any,
value = "body",
skipMissingProperties = false,
whitelist = true,
whitelist = false,
forbidNonWhitelisted = true
): RequestHandler => {
return (req, res, next) => {

View file

@ -3,7 +3,7 @@ import { ReadFileAndParseJsonUseCase } from "../usecases/read_file_and_parse_jso
import { Result } from "../helpers/result";
import { validate, ValidationError } from "class-validator";
const skipMissingProperties = false,
whitelist = true,
whitelist = false,
forbidNonWhitelisted = true;
export class ReadingJsonFileAndConvertingToInstanceClassScenario<T> {

View file

@ -100,12 +100,14 @@ export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
loadInstances(robossemblerAssets: RobossemblerAssets) {
robossemblerAssets.instances.forEach(async (el) => {
console.log(el);
if (el instanceof InstanceRgbCamera) {
const cameraModel = CameraViewModel.fromInstanceRgbCamera(el);
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
this.emit(cameraModel);
}
if (el instanceof SceneSimpleObject) {
console.log(el);
const asset = robossemblerAssets.getAssetAtInstance(el.instanceAt as string);
this.loader([asset.meshPath], () => {}, asset.name);
}

View file

@ -1,5 +1,6 @@
// TODO(IDONTSUDO): нужно переписать все запросы под BaseStore
import { NavigateFunction } from "react-router-dom";
import { Result } from "../helper/result";
import { UiBaseError } from "../model/ui_base_error";
import { HttpError } from "../repository/http_repository";
@ -8,7 +9,7 @@ export type CoreError = HttpError | Error;
export abstract class UiLoader {
isLoading = false;
async loadingHelper<T>(callBack: Promise<Result<any, T>>) {
async httpHelper<T>(callBack: Promise<Result<any, T>>) {
this.isLoading = true;
const result = await callBack;
@ -25,7 +26,7 @@ export abstract class UiLoader {
mapOk = async <T>(property: string, callBack: Promise<Result<CoreError, T>>) => {
return (
(await this.loadingHelper(callBack))
(await this.httpHelper(callBack))
// eslint-disable-next-line array-callback-return
.map((el) => {
// @ts-ignore
@ -43,7 +44,7 @@ export class SimpleErrorState extends UiLoader {
export abstract class UiErrorState<T> extends UiLoader {
abstract errorHandingStrategy: (error: T) => void;
abstract init(): Promise<any>;
abstract init(navigate?: NavigateFunction): Promise<any>;
dispose() {}
errors: UiBaseError[] = [];
}

View file

@ -4,13 +4,13 @@ import { IProjectModel } from "../model/project_model";
export class ProjectRepository extends HttpRepository {
async getAllProject() {
return this.jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project_instance");
return this._jsonRequest<IProjectModel[]>(HttpMethod.GET, "/project_instance");
}
async getActivePipeline() {
return this.jsonRequest<ActivePipeline>(HttpMethod.GET, "/realtime");
return this._jsonRequest<ActivePipeline>(HttpMethod.GET, "/realtime");
}
async setActivePipeline(id: string) {
return this.jsonRequest(HttpMethod.POST, `/project_instance/set/active/project?id=${id}`);
return this._jsonRequest(HttpMethod.POST, `/project_instance/set/active/project?id=${id}`);
}
}

View file

@ -32,12 +32,12 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => {
<h5 style={{ backgroundColor: "ButtonShadow" }}>
<Button
onClick={() => {
navigate(PipelineInstanceScreenPath + allProjectStore.activePipeline?.projectUUID ?? "");
navigate(PipelineInstanceScreenPath + allProjectStore.activePipeline?.projectId ?? "");
}}
>
Project main panel
</Button>
{allProjectStore.activePipeline?.projectUUID ?? "loading"}
{allProjectStore.activePipeline?.projectId ?? "loading"}
</h5>
{allProjectStore.projectsModels?.map((el) => {
return (

View file

@ -35,28 +35,15 @@ export class AllProjectStore extends SimpleErrorState {
async getActiveProject(): Promise<void> {
await this.mapOk<ActivePipeline>("activePipeline", this.repository.getActivePipeline());
}
async foo(): Promise<void> {
this.isLoading = true;
const result = await this.repository.getActivePipeline();
result.fold(
(success) => {
this.activePipeline = success;
this.isLoading = false;
},
(_error) => {
this.isError = true;
}
);
}
async init() {
await Promise.all([this.getProjects(), this.getActiveProject()]);
await this.projectViewGenerate();
}
projectViewGenerate() {
this.projectsModels = this.projectsModels?.filter((el) => el._id === this.activePipeline?.projectUUID);
this.projectsModels = this.projectsModels?.filter((el) => el._id !== this.activePipeline?.projectId);
}
async setPipelineActive(id: string) {
await this.loadingHelper(this.repository.setActivePipeline(id));
await this.httpHelper(this.repository.setActivePipeline(id));
}
}

View file

@ -1,26 +1,18 @@
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { ITriggerModel } from "../../../core/model/trigger_model";
import { Result } from "../../../core/helper/result";
import { IProcess } from "../../create_process/model/process_model";
import { PipelineModelDataBase } from "../model/pipeline_model";
export class CreatePipelineRepository extends HttpRepository {
async savePipeline(
model: PipelineModelDataBase
): Promise<Result<Error, any>> {
return await this.jsonRequest(HttpMethod.POST, `/pipeline`, model);
async savePipeline(model: PipelineModelDataBase): Promise<Result<Error, any>> {
return await this._jsonRequest(HttpMethod.POST, `/pipeline`, model);
}
async getTriggers(page = 1): Promise<Result<Error, ITriggerModel[]>> {
return await this.jsonRequest(HttpMethod.GET, `/trigger?${page}`);
return await this._jsonRequest(HttpMethod.GET, `/trigger?${page}`);
}
async getProcessed(page = 1): Promise<Result<Error, IProcess[]>> {
return await this.jsonRequest<IProcess[]>(
HttpMethod.GET,
`/process?${page}`
);
return await this._jsonRequest<IProcess[]>(HttpMethod.GET, `/process?${page}`);
}
}

View file

@ -1,15 +1,20 @@
import * as React from "react";
import { Row, Button } from "antd";
import { LoadPage } from "../../../core/ui/pages/load_page";
import { createPipelineStore } from "./create_pipeline_store";
import { observer } from "mobx-react-lite";
import { Icon, List } from "../../../core/ui/list/list";
import { CreateTriggerScreenPath } from "../../create_trigger/presentation/create_trigger_screen";
import { CreateProcessScreenPath } from "../../create_process/presentation/create_process_screen";
import { CreatePipelineStore } from "./create_pipeline_store";
import { CreatePipelineRepository } from "../data/create_pipeline_repository";
export const CreatePipelineScreenPath = "/create_pipeline";
export const CreatePipelineScreen: React.FunctionComponent = observer(() => {
const [createPipelineStore] = React.useState(() => new CreatePipelineStore(new CreatePipelineRepository()));
React.useEffect(() => {}, [createPipelineStore]);
return (
<>
<LoadPage

View file

@ -97,4 +97,3 @@ export class CreatePipelineStore extends SimpleErrorState {
this.mapOk("triggersModels", this.repository.getTriggers());
}
}
export const createPipelineStore = new CreatePipelineStore(new CreatePipelineRepository());

View file

@ -1,11 +1,8 @@
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { IProcess } from "../model/process_model";
export class ProcessRepository extends HttpRepository {
async save(model: IProcess): Promise<void> {
await this.jsonRequest(HttpMethod.POST, "/process", model);
await this._jsonRequest(HttpMethod.POST, "/process", model);
}
}

View file

@ -1,18 +1,11 @@
import * as React from "react";
import { processStore } from "./logic/process_store";
import { observer } from "mobx-react-lite";
import {
SubmitButton,
Input,
ResetButton,
Form,
Radio,
Switch,
} from "formik-antd";
import { SubmitButton, Input, ResetButton, Form, Radio, Switch } from "formik-antd";
import { Formik } from "formik";
import { Row, Col } from "antd";
import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model";
export const CreateProcessScreenPath = '/create/process'
export const CreateProcessScreenPath = "/create/process";
export const CreateProcessScreen = observer(() => {
return (
<div>
@ -49,22 +42,11 @@ export const CreateProcessScreen = observer(() => {
<Radio.Group name="type" options={Object.values(EXEC_TYPE)} />
<Radio.Group
name="issueType"
options={Object.values(IssueType)}
/>
<Radio.Group name="issueType" options={Object.values(IssueType)} />
<Col style={{ marginTop: 20, justifyContent: "center" }}>
<Switch
name="isGenerating"
checkedChildren="is generating"
unCheckedChildren="is generating"
/>
<Switch
name="isLocalCode"
checkedChildren="is local code"
unCheckedChildren="is local code"
/>
<Switch name="isGenerating" checkedChildren="is generating" unCheckedChildren="is generating" />
<Switch name="isLocalCode" checkedChildren="is local code" unCheckedChildren="is local code" />
</Col>
<Row style={{ marginTop: 20, justifyContent: "center" }}>

View file

@ -1,10 +1,7 @@
import { Result } from "../../core/helper/result";
import { DatabaseModel } from "../../core/model/database_model";
import { ITriggerModel } from "../../core/model/trigger_model";
import {
HttpMethod,
HttpRepository,
} from "../../core/repository/http_repository";
import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
import { IProcess } from "../create_process/model/process_model";
import { ICreateProjectViewModel } from "./project_model";
@ -15,11 +12,9 @@ export interface PipelineModel extends DatabaseModel {
export class CreateProjectRepository extends HttpRepository {
async getAllPipelines(page = 1): Promise<Result<Error, PipelineModel[]>> {
return await this.jsonRequest<PipelineModel[]>(HttpMethod.GET, "/pipeline");
return await this._jsonRequest<PipelineModel[]>(HttpMethod.GET, "/pipeline");
}
async saveProject(
model: ICreateProjectViewModel
): Promise<Result<Error, void>> {
return await this.jsonRequest<void>(HttpMethod.POST, "/project", model);
async saveProject(model: ICreateProjectViewModel): Promise<Result<Error, void>> {
return await this._jsonRequest<void>(HttpMethod.POST, "/project", model);
}
}

View file

@ -3,7 +3,8 @@ import { CreateProjectInstanceStore } from "./create_project_instance_store";
import { CreateProjectInstanceRepository } from "./create_project_instance_repository";
import { observer } from "mobx-react-lite";
import { Upload, Button } from "antd";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import { Input } from "antd";
export const CreateProjectInstancePath = "/create/project/instance/";
@ -12,9 +13,17 @@ export const CreateProjectInstanceScreen = observer(() => {
() => new CreateProjectInstanceStore(new CreateProjectInstanceRepository())
);
const id = useParams().id;
React.useEffect(() => {}, [id, createProjectInstanceStore]);
const navigate = useNavigate();
React.useEffect(() => {
createProjectInstanceStore.init(navigate, id as string);
}, [id, createProjectInstanceStore, navigate]);
return (
<>
<h1>project description</h1>
<Input onChange={(e) => createProjectInstanceStore.setProjectDescription(e.target.value)}></Input>
<h1>root entity</h1>
<Upload
onChange={(e) => {
createProjectInstanceStore.file = e.file.originFileObj;

View file

@ -1,13 +1,16 @@
import { HttpMethod, HttpRepository } from "../../core/repository/http_repository";
import { NewProjectModel } from "./new_project_model";
// this.subRoutes.push({
// method: "POST",
// subUrl: "upload
export class CreateProjectInstanceRepository extends HttpRepository {
async setProjectRootFile() {
return await this.formDataRequest(HttpMethod.POST, "/project_instance/upload/");
async setProjectRootFile(file: File) {
return await this._formDataRequest(HttpMethod.POST, "/project_instance/upload", file);
}
async createNewProject(project: NewProjectModel) {
return await this._jsonRequest(HttpMethod.POST, "/project_instance", project);
}
async setActiveProject(id: string) {
return await this._jsonRequest(HttpMethod.POST, `/project_instance/set/active/project?id=${id}`);
}
// async getProjectInstance(id: string) {
// return await this.jsonRequest(HttpMethod.GET, "");
// }
}

View file

@ -3,21 +3,37 @@ import { SimpleErrorState } from "../../core/store/base_store";
import { CreateProjectInstanceRepository } from "./create_project_instance_repository";
import { message } from "antd";
import { HttpMethod } from "../../core/repository/http_repository";
import { NavigateFunction } from "react-router-dom";
import { NewProjectModel } from "./new_project_model";
export class CreateProjectInstanceStore extends SimpleErrorState {
newProjectModel: NewProjectModel;
repository: CreateProjectInstanceRepository;
file?: File;
saveInstance(): void {
if (this.file === undefined) {
message.error("Need upload file");
} else {
// this.repository.formDataRequest(HttpMethod.POST, "", this.file);
}
}
navigate?: NavigateFunction;
constructor(repository: CreateProjectInstanceRepository) {
super();
this.repository = repository;
makeAutoObservable(this);
this.newProjectModel = NewProjectModel.empty();
}
setProjectDescription(value: string): void {
this.newProjectModel.description = value;
}
init(navigate: NavigateFunction, projectId: string) {
this.navigate = navigate;
this.newProjectModel.project = projectId;
}
async saveInstance(): Promise<void> {
if (this.file === undefined) {
message.error("Need upload file");
} else {
if (this.newProjectModel.isValid()) {
await this.repository.createNewProject(this.newProjectModel);
await this.repository.setProjectRootFile(this.file);
if (this.navigate !== undefined) this.navigate("/");
}
}
}
repository: CreateProjectInstanceRepository;
}

View file

@ -0,0 +1,17 @@
export class NewProjectModel {
project: string;
description: string;
constructor(project: string, description: string) {
this.project = project;
this.description = description;
}
static empty() {
return new NewProjectModel("", "");
}
isValid(): boolean {
return this.project.isNotEmpty() && this.description.isNotEmpty();
}
messages() {
return "";
}
}

View file

@ -1,11 +1,8 @@
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { ITriggerModel } from "../../../core/model/trigger_model";
export class TriggerRepository extends HttpRepository {
public async save(model: ITriggerModel) {
return await this.jsonRequest(HttpMethod.POST, "/trigger", model);
return await this._jsonRequest(HttpMethod.POST, "/trigger", model);
}
}

View file

@ -70,7 +70,7 @@ export class TriggerStore extends SimpleErrorState {
}
}
async saveResult(): Promise<void> {
await this.loadingHelper(
await this.httpHelper(
this.repository.save({
type: this.getTriggerDescription(),
description: this.triggerDescription,

View file

@ -1,14 +0,0 @@
import * as React from "react";
import { StaticAssetItemModel } from "../scene_manager_store";
export interface IStaticAssetModelViewProps {
model: StaticAssetItemModel;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
return (
<div style={{ width: "100px", textAlignLast: "center", backgroundColor: "aqua", margin: "10px" }}>
{props.model.name}
</div>
);
}

View file

@ -0,0 +1,19 @@
import { Result } from "../../../core/helper/result";
import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository";
import { CoreError } from "../../../core/store/base_store";
import { RobossemblerAssets } from "../model/robossembler_assets";
export class SceneHttpRepository extends HttpRepository {
async getRobossemblerAssets() {
return this._jsonToClassInstanceRequest<RobossemblerAssets>(
HttpMethod.GET,
"/robossembler_assets",
RobossemblerAssets
) as unknown as Promise<Result<CoreError, RobossemblerAssets>>;
}
async saveScene(robossemblerAssets: RobossemblerAssets) {
return this._jsonRequest(HttpMethod.POST, "/robossembler_assets", robossemblerAssets) as unknown as Promise<
Result<CoreError, void>
>;
}
}

View file

@ -0,0 +1,160 @@
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class Gravity {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export class Pose {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
@IsNumber()
roll: number;
@IsNumber()
pitch: number;
@IsNumber()
yaw: number;
}
export class Position {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export enum InstanceType {
RGB_CAMERA = "rgb_camera",
SCENE_SIMPLE_OBJECT = "scene_simple_object",
}
abstract class CoreInstances {}
export class Instance extends CoreInstances {
@IsEnum(InstanceType)
instanceType: InstanceType;
@Type(() => Position)
position: Position;
@IsArray()
quaternion: number[];
@IsOptional()
@IsString()
instanceAt: null | string = null;
}
export class SceneSimpleObject extends Instance {}
export class InstanceRgbCamera extends Instance {
@IsString()
cameraLink: string;
@IsString()
topicCameraInfo: string;
@IsOptional()
@IsString()
topicDepth: string | null;
@IsString()
topicImage: string;
}
export class Asset {
@IsString()
name: string;
@IsString()
ixx: string;
@IsString()
ixy: string;
@IsString()
ixz: string;
@IsString()
iyy: string;
@IsString()
izz: string;
@IsString()
mass: string;
@IsString()
posX: string;
@IsString()
posY: string;
@IsString()
posZ: string;
@IsString()
eulerX: string;
@IsString()
eulerY: string;
@IsString()
eulerZ: string;
@IsString()
iyz: string;
@IsString()
meshPath: string;
@IsString()
friction: string;
@IsString()
centerMassX: string;
@IsString()
centerMassY: string;
@IsString()
centerMassZ: string;
}
export class Physics {
@IsString()
engine_name: string;
@Type(() => Gravity)
gravity: Gravity;
}
export class RobossemblerAssets {
@ValidateNested()
@Type(() => Asset)
assets: Asset[];
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "instanceType",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
instances: Instance[];
@IsOptional()
@ValidateNested()
@Type(() => Physics)
physics: Physics;
convertLocalPathsToServerPaths(server_address: string): RobossemblerAssets {
this.assets = this.assets.map((el) => {
el.meshPath = server_address + el.meshPath;
return el;
});
return this;
}
getAssetPath(assetName: string): string {
const findElement = this.assets.find((el) => el.name === assetName);
if (findElement === undefined) {
throw new Error("RobossemblerAssets.getAssetPath not found asset by name:" + assetName);
}
return findElement.meshPath;
}
getAssetAtInstance(instanceAt: string): Asset {
return this.assets.filter((el) => el.name === instanceAt)[0];
}
}

View file

@ -0,0 +1,164 @@
import { CameraHelper, Object3D, PerspectiveCamera, Quaternion, Scene, Vector3 } from "three";
import { v4 as uuidv4 } from "uuid";
import { UserData } from "../../../core/repository/core_there_repository";
import { Asset, Instance, InstanceRgbCamera, InstanceType, SceneSimpleObject } from "./robossembler_assets";
export enum RobossemblerFiles {
robossemblerAssets = "robossembler_assets.json",
}
export enum SceneModelsType {
ASSET,
}
export abstract class BaseSceneItemModel {
id: string;
name: string;
position: Vector3;
quaternion: Quaternion;
constructor(name: string) {
this.id = uuidv4();
this.name = name;
}
abstract toInstance(): Instance;
abstract deleteToScene(scene: Scene): Object3D[];
toQuaternionArray() {
return [this.quaternion.x, this.quaternion.y, this.quaternion.z, this.quaternion.w];
}
}
export class StaticAssetItemModel extends BaseSceneItemModel {
name: string;
type = SceneModelsType.ASSET;
constructor(name: string, position: Vector3, quaternion: Quaternion) {
super(name);
this.name = name;
this.position = position;
this.quaternion = quaternion;
}
static fromSceneSimpleObjectAndAsset(sceneSimpleObject: SceneSimpleObject, asset: Asset) {
const { x, y, z } = sceneSimpleObject.position;
const quaternion = sceneSimpleObject.quaternion;
return new StaticAssetItemModel(
asset.name,
new Vector3(x, y, z),
new Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3])
);
}
toInstance(): Instance {
const instance = new Instance();
instance.instanceType = InstanceType.SCENE_SIMPLE_OBJECT;
instance.position = this.position;
instance.instanceAt = this.name;
instance.quaternion = this.toQuaternionArray();
return instance;
}
deleteToScene(scene: Scene): Object3D[] {
return scene.children.filter((el) => el.name !== this.name);
}
}
export interface ICoreInstance {
instanceAt: null | string;
type: string;
position: Vector3;
quaternion: Quaternion;
}
export interface ICameraInstance extends ICoreInstance {
cameraLink: string;
topicImage: string;
topicCameraInfo: string;
topicDepth: string | null;
}
export class CameraViewModel extends BaseSceneItemModel {
cameraLink: string;
topicImage: string;
topicCameraInfo: string;
topicDepth: null | string;
constructor(cameraLink: string, topicImage: string, topicCameraInfo: string, topicDepth: string | null) {
super(cameraLink);
this.cameraLink = cameraLink;
this.topicImage = topicImage;
this.topicCameraInfo = topicCameraInfo;
this.topicDepth = topicDepth;
}
static fromInstanceRgbCamera(instanceRgbCamera: InstanceRgbCamera) {
const { cameraLink, topicImage, topicCameraInfo, topicDepth, position, quaternion } = instanceRgbCamera;
const instance = new CameraViewModel(cameraLink, topicImage, topicCameraInfo, topicDepth);
const { x, y, z } = position;
instance.position = new Vector3(x, y, z);
instance.quaternion = new Quaternion(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
return instance;
}
deleteToScene(scene: Scene): Object3D[] {
return scene.children.filter((el) => {
if (el.name === this.name) {
return false;
}
if (el.name === this.cameraLink + "camera_helper") {
return false;
}
return true;
});
}
toInstance(): InstanceRgbCamera {
return {
instanceType: InstanceType.RGB_CAMERA,
position: this.position,
quaternion: this.toQuaternionArray(),
instanceAt: null,
cameraLink: this.cameraLink,
topicCameraInfo: this.topicCameraInfo,
topicDepth: this.topicDepth,
topicImage: this.topicImage,
};
}
validate(cameraLinksNames: string[]) {
if (cameraLinksNames.filter((el) => this.cameraLink === el).isNotEmpty()) {
return { cameraLink: "the name for the camera is not unique" };
}
if (this.cameraLink.isEmpty()) {
return { cameraLink: "is empty" };
}
if (this.topicImage.isEmpty()) {
return { topicImage: "is empty" };
}
if (this.topicCameraInfo.isEmpty()) {
return { topicCameraInfo: "is empty" };
}
}
mapPerspectiveCamera(htmlSceneWidth: number, htmlSceneHeight: number) {
const perspectiveCamera = new PerspectiveCamera(48, htmlSceneWidth / htmlSceneHeight, 7.1, 28.5);
perspectiveCamera.position.copy(this.position);
perspectiveCamera.quaternion.copy(this.quaternion);
perspectiveCamera.name = this.cameraLink;
const cameraHelper = new CameraHelper(perspectiveCamera);
perspectiveCamera.userData[UserData.selectedObject] = true;
cameraHelper.userData[UserData.selectedObject] = true;
cameraHelper.name = this.cameraLink + "camera_helper";
return [cameraHelper, perspectiveCamera];
}
static empty() {
return new CameraViewModel("", "", "", "");
}
}

View file

@ -0,0 +1,27 @@
export class SceneMenu {
x?: number;
y?: number;
isShow?: boolean;
constructor(x: number | undefined, y: number | undefined, isShow: boolean | undefined) {
this.x = x;
this.y = y;
this.isShow = isShow;
}
static empty() {
return new SceneMenu(undefined, undefined, false);
}
}
export interface SceneManagerView {
name: string;
clickHandel: Function;
}
export enum SceneMode {
ROTATE = "Rotate",
MOVING = "Moving",
EMPTY = "Empty",
ADD_CAMERA = "Add camera",
}

View file

@ -0,0 +1,27 @@
interface SceneMenuProps {
x?: number;
y?: number;
}
export const SceneMenu = (props: SceneMenuProps) => {
const sceneMenuStyle = {
transform: "rotate(0deg)",
background: "rgb(73 73 73)",
color: "#FFFFFF",
fontSize: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderColor: "aqua",
border: "solid",
borderRadius: "30px",
width: "150px",
height: "300px",
};
return (
<div style={{ position: "absolute", top: "0px", left: "0px" }}>
<div style={{ ...sceneMenuStyle, marginLeft: `${props.x}px`, marginTop: `${props.y}px` }}></div>
</div>
);
};

View file

@ -0,0 +1,68 @@
import React from "react";
export const SceneWidget = () => {
const [pressed, setPressed] = React.useState(false);
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
if (pressed) {
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", togglePressed);
}
return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", togglePressed);
};
}, [position, pressed]);
const onMouseMove = (event: any) => {
const x = position.x + event.movementX;
const y = position.y + event.movementY;
setPosition({ x, y });
};
const togglePressed = () => {
setPressed((prev) => !prev);
};
const quickAndDirtyStyle = {
transform: "rotate(0deg)",
background: "rgb(73 73 73)",
color: "#FFFFFF",
fontSize: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderColor: "aqua",
border: "solid",
borderRadius: "30px",
};
return (
<div style={{ position: "absolute", top: "0px", left: "0px" }}>
<div
className={pressed ? "box_0-active" : "box-0"}
style={{
...quickAndDirtyStyle,
marginLeft: `${position.x}px`,
marginTop: `${position.y}px`,
}}
onClickCapture={(event) => {
event.stopPropagation();
setPressed(false);
}}
onMouseDown={togglePressed}
>
<p>{pressed ? "Dragging..." : "Press to drag"}</p>
<h1
onClick={(event) => {
event.stopPropagation();
console.log(201);
}}
>
HYO
</h1>
</div>
</div>
);
};

View file

@ -0,0 +1,45 @@
import * as React from "react";
import { BaseSceneItemModel, CameraViewModel, StaticAssetItemModel } from "../../model/scene_assets";
import { Button } from "antd";
export interface IStaticAssetModelViewProps {
model: BaseSceneItemModel;
onTap: Function;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
if (props.model instanceof CameraViewModel) {
return (
<div
style={{
backgroundColor: "ActiveBorder",
padding: "10px",
color: "white",
width: "100px",
textAlignLast: "center",
}}
>
{props.model.cameraLink}
<Button onClick={() => props.onTap()}>delete</Button>
</div>
);
}
if (props.model instanceof StaticAssetItemModel) {
return (
<div
style={{
backgroundColor: "brown",
padding: "10px",
color: "white",
width: "100px",
textAlignLast: "center",
}}
>
{props.model.name}
<Button onClick={() => props.onTap()}>delete</Button>
</div>
);
}
return <></>;
}

View file

@ -0,0 +1,162 @@
import * as React from "react";
import { SceneMangerStore } from "./scene_manager_store";
import { observer } from "mobx-react-lite";
import { StaticAssetModelView } from "./components/static_asset_item_view";
import { useParams } from "react-router-dom";
import { SceneManagerView, SceneMode } from "../model/scene_view";
import { Button } from "antd";
import { Form, Input, ResetButton, SubmitButton } from "formik-antd";
import { Formik } from "formik";
import { CameraViewModel } from "../model/scene_assets";
export const SceneManagerPath = "/scene/manager/";
export const SceneManger = observer(() => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
const id = useParams().id as string;
React.useEffect(() => {
sceneMangerStore.init();
sceneMangerStore.loadScene(id, canvasRef.current!);
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "scroll";
sceneMangerStore.dispose();
};
}, [id, sceneMangerStore]);
const sceneIcons: SceneManagerView[] = [SceneMode.ROTATE, SceneMode.MOVING, SceneMode.ADD_CAMERA].map((el) => {
return { name: el, clickHandel: () => sceneMangerStore.setSceneMode(el) };
});
return (
<div>
<canvas ref={canvasRef} style={{ position: "absolute", overflow: "hidden" }} />
<div
style={{
display: "flex",
flexDirection: "row",
alignContent: "center",
justifyContent: "space-between",
position: "absolute",
width: "100vw",
}}
>
<div>
{sceneIcons.map((el) => {
return (
<div
style={{
backgroundColor: sceneMangerStore.sceneMode === el.name ? "aqua" : "ActiveBorder",
width: "100px",
}}
onClick={() => {
el.clickHandel();
}}
>
{el.name}
</div>
);
})}
<div
style={{
marginTop: "10px",
backgroundColor: "GrayText",
border: "solid",
borderRadius: "10px",
padding: "8px",
borderColor: "white",
}}
>
<div style={{ color: "white" }}>Scene manager</div>
{sceneMangerStore.isVisibleSaveButton ? (
<>
<Button onClick={() => sceneMangerStore.onTapSave()}>Save</Button>
</>
) : (
<></>
)}
{sceneMangerStore.isLoading ? <>Loading...</> : <></>}
{sceneMangerStore.sceneMode === SceneMode.ADD_CAMERA ? (
<div>
<Formik
initialValues={CameraViewModel.empty()}
onSubmit={async (model, actions) => {
sceneMangerStore.addNewCamera(model);
actions.setSubmitting(false);
actions.resetForm();
}}
validate={(model) => {
return model.validate(sceneMangerStore.getCameraLinkNames());
}}
render={() => (
<Form>
<div
style={{
background: "white",
flex: 1,
padding: 40,
width: "400px",
}}
>
<Input name="cameraLink" placeholder="Camera link" />
<Input name="topicImage" placeholder="Topic Image" />
<Input name="topicCameraInfo" placeholder="Topic Camera Info" />
<Input name="topicDepth" placeholder="Topic Depth" />
<ResetButton>Reset</ResetButton>
<SubmitButton>Submit</SubmitButton>
</div>
</Form>
)}
/>
</div>
) : (
<></>
)}
{sceneMangerStore.sceneMode === SceneMode.MOVING || SceneMode.ROTATE ? (
<>
{sceneMangerStore.robossemblerAssets?.assets.map((el) => {
return (
<div>
<div style={{ color: "white", marginLeft: "10px", marginRight: "10px", display: "contents" }}>
{el.name}
{sceneMangerStore.isRenderedAsset(el.name) ? (
<></>
) : (
<Button
onClick={() => {
sceneMangerStore.loadSceneRobossemblerAsset(el.name);
}}
>
add scene
</Button>
)}
</div>
</div>
);
})}
</>
) : (
<></>
)}
</div>
</div>
<div>
{sceneMangerStore.sceneModels.map((el) => {
return <StaticAssetModelView onTap={() => sceneMangerStore.deleteSceneItem(el)} model={el} />;
})}
</div>
{/* {sceneMangerStore.sceneMenuIsShow ? (
<>
<SceneMenu x={sceneMangerStore.sceneMenu.x} y={sceneMangerStore.sceneMenu.y} />
</>
) : (
<></>
)} */}
</div>
</div>
);
});

View file

@ -0,0 +1,179 @@
import makeAutoObservable from "mobx-store-inheritance";
import { CoreThereRepository } from "../../../core/repository/core_there_repository";
import { Object3D, Vector2 } from "three";
import { HttpError } from "../../../core/repository/http_repository";
import { UiErrorState } from "../../../core/store/base_store";
import { UiBaseError } from "../../../core/model/ui_base_error";
import { SceneMenu, SceneMode } from "../model/scene_view";
import { BaseSceneItemModel, CameraViewModel, RobossemblerFiles, StaticAssetItemModel } from "../model/scene_assets";
import { SceneHttpRepository } from "../data/scene_repository";
import { message } from "antd";
import { RobossemblerAssets } from "../model/robossembler_assets";
export class SceneMangerStore extends UiErrorState<HttpError> {
sceneMode: SceneMode;
sceneMenu: SceneMenu;
isVisibleSaveButton: boolean = false;
coreThereRepository: null | CoreThereRepository = null;
sceneHttpRepository: SceneHttpRepository;
sceneModels: BaseSceneItemModel[] = [];
isSceneMenuShow = false;
robossemblerAssets?: RobossemblerAssets;
constructor() {
super();
makeAutoObservable(this);
this.sceneHttpRepository = new SceneHttpRepository();
this.sceneMode = SceneMode.EMPTY;
this.sceneMenu = SceneMenu.empty();
}
onTapSave(): void {
this.robossemblerAssets!.instances = [];
this.sceneModels.forEach((el) => this.robossemblerAssets?.instances.push(el.toInstance()));
this.httpHelper(this.sceneHttpRepository.saveScene(this.robossemblerAssets as RobossemblerAssets));
this.isVisibleSaveButton = false;
}
deleteSceneItem(item: BaseSceneItemModel) {
const itm = this.sceneModels.filter((el) => el.id === item.id);
this.coreThereRepository!.deleteSceneItem(itm[0]);
this.sceneModels = this.sceneModels.filter((el) => el.name !== item.name);
this.visibleSaveButton();
}
visibleSaveButton() {
this.isVisibleSaveButton = true;
}
addNewCamera(model: CameraViewModel) {
model.position = this.coreThereRepository!.camera.position;
model.quaternion = this.coreThereRepository!.camera.quaternion;
this.sceneModels.push(model);
this.coreThereRepository?.addSceneCamera(model);
this.visibleSaveButton();
}
getCameraLinkNames(): string[] {
return this.sceneModels.map((el) => {
if (el instanceof CameraViewModel) {
return el.cameraLink;
}
return "";
});
}
loaderWatcher() {}
loadSceneRobossemblerAsset(name: string) {
try {
const assetPath = this.robossemblerAssets?.getAssetPath(name) as string;
this.coreThereRepository?.loader([assetPath], this.loaderWatcher, name);
this.visibleSaveButton();
} catch (error) {
message.error(String(error));
}
}
isRenderedAsset(name: string): boolean {
return this.sceneModels
.filter((el) => {
if (el instanceof StaticAssetItemModel) {
return el.name === name;
}
return false;
})
.isNotEmpty();
}
hiddenMenu() {
this.isSceneMenuShow = false;
}
setSceneMode = (mode: SceneMode) => {
if (this.sceneMode === undefined || this.sceneMode !== mode) {
this.sceneMode = mode;
} else if (this.sceneMode === mode) {
this.sceneMode = SceneMode.EMPTY;
}
this.coreThereRepository?.setTransformMode(this.sceneMode);
this.sceneModeWatcher();
};
sceneModeWatcher() {}
async init(): Promise<any> {}
errorHandingStrategy = (error: HttpError) => {
if (error.status === 404) {
this.errors.push(new UiBaseError(`${RobossemblerFiles.robossemblerAssets} not found to project`));
}
};
async loadScene(sceneId: string, canvasRef: HTMLCanvasElement) {
this.loadWebGl(canvasRef);
await this.mapOk<RobossemblerAssets>("robossemblerAssets", this.sceneHttpRepository.getRobossemblerAssets());
if (this.robossemblerAssets) {
this.coreThereRepository?.loadInstances(this.robossemblerAssets);
}
}
loadWebGl(canvasRef: HTMLCanvasElement): void {
this.coreThereRepository = new CoreThereRepository(canvasRef as HTMLCanvasElement, this.watcherSceneEditorObject);
this.coreThereRepository.on(this.watcherThereObjects);
this.coreThereRepository.render();
this.sceneModels = this.coreThereRepository.getAllSceneModels();
window.addEventListener("click", (event) => this.clickLister(event));
window.addEventListener("mousedown", (e) => this.sceneContextMenu(e));
}
clickLister(event: MouseEvent) {
if (this.sceneMode === SceneMode.EMPTY) {
return;
}
if (this.sceneMode === SceneMode.MOVING || this.sceneMode === SceneMode.ROTATE) {
const vector = new Vector2();
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.transformContollsCall(vector);
}
}
sceneContextMenu(e: MouseEvent): void {
if (e.button === 2) {
this.isSceneMenuShow = true;
this.sceneMenu.x = e.clientX;
this.sceneMenu.y = e.clientY;
}
}
watcherThereObjects = (sceneItemModel: BaseSceneItemModel): void => {
this.sceneModels.push(sceneItemModel);
};
watcherSceneEditorObject = (mesh: Object3D) => {
this.sceneModels = this.sceneModels.map((el) => {
if (el instanceof CameraViewModel || (el instanceof StaticAssetItemModel && el.name === mesh.name)) {
el.position = mesh.position;
el.quaternion = mesh.quaternion;
return el;
}
return el;
});
this.visibleSaveButton();
};
transformContollsCall = (vector: Vector2) => {
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).fold(
(success) => this.coreThereRepository?.setTransformControlsAttach(success),
(_error) => this.coreThereRepository?.disposeTransformControlsMode()
);
};
dispose() {
window.removeEventListener("click", this.clickLister);
window.removeEventListener("mousedown", (e) => this.sceneContextMenu(e));
}
}

View file

@ -1,60 +0,0 @@
import * as React from "react";
import { SceneMangerStore, StaticAssetItemModel } from "./scene_manager_store";
import { observer } from "mobx-react-lite";
import { StaticAssetModelView } from "./components/static_asset_item_view";
import { useParams } from "react-router-dom";
// const useKeyLister = (fn: Function) => {
// const pressed = new Map();
// const registerKeyPress = React.useCallback(
// (event: KeyboardEvent, codes: string[], callBack: Function) => {
// if (codes.hasIncludeElement(event.code)) {
// pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
// if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
// callBack();
// }
// });
// }
// },
// [pressed]
// );
// React.useEffect(() => {
// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
// }, [fn, registerKeyPress]);
// return [];
// };
export const SceneManagerPath = "/scene/manager/";
export const SceneManger = observer(() => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
const id = useParams().id as string;
React.useEffect(() => {
if (id) {
sceneMangerStore.loadScene(id, canvasRef.current!);
}
return () => {
sceneMangerStore.dispose();
};
}, [id, sceneMangerStore]);
return (
<div>
<div>{sceneMangerStore.errors.isNotEmpty() ? <>{sceneMangerStore.errors[0].text}</> : <></>}</div>
<div style={{ position: "absolute" }}>
{sceneMangerStore.sceneItems.map((el) => {
if (el instanceof StaticAssetItemModel) {
return StaticAssetModelView({ model: el });
}
return <></>;
})}
</div>
<canvas ref={canvasRef} />
</div>
);
});

View file

@ -1,96 +0,0 @@
/* eslint-disable array-callback-return */
import makeAutoObservable from "mobx-store-inheritance";
import { CoreThereRepository } from "../../core/repository/core_there_repository";
import { v4 as uuidv4 } from "uuid";
import { Vector2 } from "three";
import { HttpError, HttpMethod, HttpRepository } from "../../core/repository/http_repository";
import { UiErrorState } from "../../core/store/base_store";
import { UiBaseError } from "../../core/model/ui_base_error";
export class BaseSceneItemModel {
id: string;
constructor() {
this.id = uuidv4();
}
}
enum SceneModelsType {
ASSET,
}
export class StaticAssetItemModel extends BaseSceneItemModel {
name: string;
type = SceneModelsType.ASSET;
constructor(name: string) {
super();
this.name = name;
}
}
export enum RobossemblerFiles {
robossemblerAssets = "robossembler_assets.json",
}
export class SceneMangerStore extends UiErrorState<HttpError> {
async init(): Promise<any> {}
errorHandingStrategy = (error: HttpError) => {
if (error.status === 404) {
this.errors.push(new UiBaseError(`${RobossemblerFiles.robossemblerAssets} not found to project`));
}
};
async loadScene(sceneId: string, canvasRef: HTMLCanvasElement) {
(
await this.loadingHelper(
this.httpRepository.jsonRequest(HttpMethod.GET, "/" + sceneId + "/" + RobossemblerFiles.robossemblerAssets)
)
).map((el) => {
this.loadGl(canvasRef);
});
}
coreThereRepository: null | CoreThereRepository = null;
httpRepository: HttpRepository;
sceneItems: BaseSceneItemModel[] = [];
constructor() {
super();
makeAutoObservable(this);
this.httpRepository = new HttpRepository();
}
loadGl(canvasRef: HTMLCanvasElement): void {
this.coreThereRepository = new CoreThereRepository(canvasRef as HTMLCanvasElement);
this.coreThereRepository.on(this.watcherThereObjects);
this.coreThereRepository.render();
this.coreThereRepository.fitCameraToCenteredObject(this.coreThereRepository.getAllSceneNameModels());
this.sceneItems = this.coreThereRepository.getAllSceneModels();
window.addEventListener("click", this.handleMouseClick);
}
watcherThereObjects(sceneItemModel: BaseSceneItemModel): void {
this.sceneItems.push(sceneItemModel);
}
handleMouseClick = (event: MouseEvent) => {
const vector = new Vector2();
console.log("====");
console.log(event.pageX);
console.log(event.clientX);
console.log(event.x);
console.log(event.movementX);
console.log(event.screenX);
console.log("====");
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).map((el) => {
this.coreThereRepository?.switchObjectEmissive(el);
});
};
dispose() {
window.removeEventListener("click", this.handleMouseClick);
}
}

View file

@ -4,9 +4,9 @@ import { IProjectModel } from "../model/project_model";
export class SelectProjectRepository extends HttpRepository {
async setActiveProject(id: string) {
return await this.jsonRequest(HttpMethod.POST, `/project?${id}`);
return await this._jsonRequest(HttpMethod.POST, `/project?${id}`);
}
async getAllProjects(page = 1): Promise<Result<Error, IProjectModel[]>> {
return await this.jsonRequest(HttpMethod.GET, `/project?${page}`);
return await this._jsonRequest(HttpMethod.GET, `/project?${page}`);
}
}

View file

@ -10,10 +10,11 @@ export const SelectProjectScreenPath = "/select_project";
export const SelectProjectScreen: React.FunctionComponent = observer(() => {
const [selectProjectStore] = React.useState(() => new SelectProjectStore());
const navigate = useNavigate();
React.useEffect(() => {
selectProjectStore.init();
}, [selectProjectStore]);
const navigate = useNavigate();
}, [selectProjectStore, navigate]);
return (
<>

View file

@ -4,10 +4,7 @@ import { IProjectModel } from "../model/project_model";
import { CoreError, UiErrorState } from "../../../core/store/base_store";
export class SelectProjectStore extends UiErrorState<CoreError> {
errorHandingStrategy = (error: CoreError) => {
console.log(error);
};
errorHandingStrategy = (error: CoreError) => {};
repository: SelectProjectRepository;
errors = [];
page = 1;
@ -20,7 +17,7 @@ export class SelectProjectStore extends UiErrorState<CoreError> {
makeAutoObservable(this);
}
async setActiveProject(id: string): Promise<void> {
this.loadingHelper(this.repository.setActiveProject(id));
this.httpHelper(this.repository.setActiveProject(id));
}
async getPipelines(): Promise<void> {
await this.mapOk("projects", this.repository.getAllProjects(this.page));