added scene manager
This commit is contained in:
parent
ae9842d5e1
commit
2adb939f37
36 changed files with 703 additions and 196 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -15,6 +15,6 @@
|
|||
"*ui": false,
|
||||
"*ui.*": false
|
||||
},
|
||||
"cSpell.words": ["antd", "fileupload", "metadatas", "undici", "uuidv"],
|
||||
"cSpell.words": ["antd", "Collada", "Contolls", "fileupload", "metadatas", "undici", "uuidv"],
|
||||
"editor.rulers": [100]
|
||||
}
|
||||
|
|
1
server/.gitignore
vendored
1
server/.gitignore
vendored
|
@ -9,3 +9,4 @@ package-lock.json
|
|||
build/
|
||||
model_create.ts
|
||||
public
|
||||
p.ts
|
|
@ -12,7 +12,7 @@ export abstract class CallbackStrategyWithEmpty {
|
|||
}
|
||||
export abstract class CallbackStrategyWithValidationModel<V> {
|
||||
abstract validationModel: V;
|
||||
abstract call(a: V): ResponseBase;
|
||||
abstract call(model: V): ResponseBase;
|
||||
}
|
||||
export abstract class CallbackStrategyWithIdQuery {
|
||||
abstract idValidationExpression: CoreValidation;
|
||||
|
@ -25,7 +25,8 @@ export abstract class CallBackStrategyWithQueryPage {
|
|||
|
||||
export abstract class CallbackStrategyWithFileUpload {
|
||||
abstract checkingFileExpression: RegExp;
|
||||
abstract call(file: File): ResponseBase;
|
||||
abstract idValidationExpression: CoreValidation;
|
||||
abstract call(file: File, id: string, description: string): ResponseBase;
|
||||
}
|
||||
|
||||
interface ISubSetFeatureRouter<T> {
|
||||
|
@ -116,17 +117,32 @@ export class CoreHttpController<V> implements ICoreHttpController {
|
|||
res.status(400).json("need files to form-data request");
|
||||
return;
|
||||
}
|
||||
|
||||
if (req["files"]["file"] === undefined) {
|
||||
res.status(400).json("need file to form data request");
|
||||
return;
|
||||
}
|
||||
if (req.query.description === undefined) {
|
||||
res
|
||||
.status(400)
|
||||
.json("request query description is null, need query description &description={description:String}");
|
||||
return;
|
||||
}
|
||||
if (req.query.id === undefined) {
|
||||
res.status(400).json("request query id is null, need query id ?id={id:String}");
|
||||
return;
|
||||
}
|
||||
if (!el.fn.idValidationExpression.regExp.test(req.query.id)) {
|
||||
res.status(400).json(el.fn.idValidationExpression.message);
|
||||
return;
|
||||
}
|
||||
if (el.fn instanceof CallbackStrategyWithFileUpload) {
|
||||
if (!el.fn.checkingFileExpression.test(req["files"]["file"]["name"])) {
|
||||
res.status(400).json("a file with this extension is expected: " + String(el.fn.checkingFileExpression));
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.responseHelper(res, el.fn.call(req["files"]["file"]));
|
||||
await this.responseHelper(res, el.fn.call(req["files"]["file"], req.query.id, req.query.description));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { NixStoreManagerPresentation } from "../../features/nix_store_manager/nix_store_manager";
|
||||
import { PipelinePresentation } from "../../features/pipelines/pipeline_presentation";
|
||||
import { ProcessPresentation } from "../../features/process/process_presentation";
|
||||
import { ProjectInstancePresentation } from "../../features/project_instance/project_instance_presentation";
|
||||
import {
|
||||
ProjectInstancePresentation,
|
||||
RobossemblerAssetsPresentation,
|
||||
} from "../../features/project_instance/project_instance_presentation";
|
||||
import { ProjectsPresentation } from "../../features/projects/projects_presentation";
|
||||
import { RealTimePresentation } from "../../features/realtime/realtime_presentation";
|
||||
import { TriggerPresentation } from "../../features/triggers/triggers_presentation";
|
||||
|
@ -21,6 +24,7 @@ export const httpRoutes: Routes[] = [
|
|||
new RealTimePresentation(),
|
||||
new ProjectInstancePresentation(),
|
||||
new NixStoreManagerPresentation(),
|
||||
new RobossemblerAssetsPresentation(),
|
||||
]
|
||||
.concat(routersImplementPureCrud)
|
||||
.map((el) => el.call());
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
export class ActivePipeline {
|
||||
pipelineIsRunning: boolean;
|
||||
projectUUID?: string | null;
|
||||
projectId?: string | null;
|
||||
lastProcessCompleteCount: number | null;
|
||||
error: any;
|
||||
rootDir: string;
|
||||
path: string;
|
||||
constructor(
|
||||
pipelineIsRunning: boolean,
|
||||
lastProcessCompleteCount: number | null,
|
||||
error: any,
|
||||
path: string | null,
|
||||
projectUUID?: string | null
|
||||
projectId: string | null,
|
||||
rootDir: string | null
|
||||
) {
|
||||
this.pipelineIsRunning = pipelineIsRunning;
|
||||
this.projectUUID = projectUUID;
|
||||
this.projectId = projectId;
|
||||
this.lastProcessCompleteCount = lastProcessCompleteCount;
|
||||
this.error = error;
|
||||
this.path = path;
|
||||
this.rootDir = rootDir;
|
||||
}
|
||||
|
||||
static empty() {
|
||||
return new ActivePipeline(false, null, null, null, null);
|
||||
return new ActivePipeline(false, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import { IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
|
||||
export enum AssetsType {
|
||||
Asset = "asset",
|
||||
Light = "light",
|
||||
}
|
||||
|
||||
export class Gravity {
|
||||
@IsNumber()
|
||||
x: number;
|
||||
|
@ -14,6 +9,7 @@ export class Gravity {
|
|||
@IsNumber()
|
||||
z: number;
|
||||
}
|
||||
|
||||
export class Pose {
|
||||
@IsNumber()
|
||||
x: number;
|
||||
|
@ -29,37 +25,47 @@ export class Pose {
|
|||
yaw: number;
|
||||
}
|
||||
|
||||
export class Instance {
|
||||
@IsString()
|
||||
model_name: string;
|
||||
@IsString()
|
||||
model_id: string;
|
||||
@IsString()
|
||||
id: string;
|
||||
@ValidateNested()
|
||||
@Type(() => Pose)
|
||||
pose: Pose;
|
||||
export class Position {
|
||||
@IsNumber()
|
||||
scale: number;
|
||||
@IsEnum(AssetsType)
|
||||
type: AssetsType;
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
parent?: string;
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
light_type?: string;
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
intencity?: number;
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
diffuse?: number[];
|
||||
@IsOptional()
|
||||
x: number;
|
||||
@IsNumber()
|
||||
spot_angle?: number;
|
||||
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;
|
||||
|
@ -113,14 +119,24 @@ export class RobossemblerAssets {
|
|||
@Type(() => Asset)
|
||||
assets: Asset[];
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Instance)
|
||||
@IsArray()
|
||||
@Type(() => Instance, {
|
||||
discriminator: {
|
||||
property: "type",
|
||||
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;
|
||||
|
@ -128,4 +144,17 @@ export class RobossemblerAssets {
|
|||
});
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,24 +6,28 @@ const skipMissingProperties = false,
|
|||
whitelist = true,
|
||||
forbidNonWhitelisted = true;
|
||||
|
||||
export class ReadingJsonFileAndConvertingToInstanceClass<T> {
|
||||
export class ReadingJsonFileAndConvertingToInstanceClassScenario<T> {
|
||||
model: ClassConstructor<T>;
|
||||
constructor(cls: ClassConstructor<T>) {
|
||||
this.model = cls;
|
||||
}
|
||||
call = async (path: string): Promise<Result<string, T>> => {
|
||||
const result = await new ReadFileAndParseJsonUseCase().call(path);
|
||||
if (result.isFailure()) {
|
||||
return result.forward();
|
||||
}
|
||||
const json = result.value;
|
||||
const model = plainToInstance(this.model, json);
|
||||
const errors = await validate(model as object, { skipMissingProperties, whitelist, forbidNonWhitelisted });
|
||||
if (errors.length > 0) {
|
||||
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");
|
||||
return Result.error(message);
|
||||
} else {
|
||||
return Result.ok(model as T);
|
||||
try {
|
||||
const result = await new ReadFileAndParseJsonUseCase().call(path);
|
||||
if (result.isFailure()) {
|
||||
return result.forward();
|
||||
}
|
||||
const json = result.value;
|
||||
const model = plainToInstance(this.model, json);
|
||||
const errors = await validate(model as object, { skipMissingProperties, whitelist, forbidNonWhitelisted });
|
||||
if (errors.length > 0) {
|
||||
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(", ");
|
||||
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario:" + message);
|
||||
} else {
|
||||
return Result.ok(model as T);
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.error("ReadingJsonFileAndConvertingToInstanceClassScenario" + String(error));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,14 +9,24 @@ import { SearchDataBaseModelUseCase } from "../usecases/search_database_model_us
|
|||
|
||||
export class SetLastActivePipelineToRealTimeServiceScenario {
|
||||
call = async (): Promise<void> => {
|
||||
const result = await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({
|
||||
isActive: true,
|
||||
});
|
||||
await result.map(async (el) => {
|
||||
const projectModel = el;
|
||||
const projectPath = App.staticFilesStoreDir() + projectModel.rootDir + "/";
|
||||
await new CreateFolderUseCase().call(projectPath);
|
||||
pipelineRealTimeService.setPipelineDependency(projectModel.project.pipelines, projectPath, projectModel._id);
|
||||
});
|
||||
return (
|
||||
await new SearchDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call({
|
||||
isActive: true,
|
||||
})
|
||||
).fold(
|
||||
async (projectModel) => {
|
||||
const projectPath = App.staticFilesStoreDir() + projectModel.rootDir + "/";
|
||||
await new CreateFolderUseCase().call(projectPath);
|
||||
pipelineRealTimeService.setPipelineDependency(
|
||||
projectModel.project.pipelines,
|
||||
projectPath,
|
||||
projectModel._id,
|
||||
projectModel.rootDir
|
||||
);
|
||||
},
|
||||
async (_e) => {
|
||||
console.log("not found active pipeline");
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,9 +32,8 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
|
|||
this.iterationLogSaver(iteration);
|
||||
}
|
||||
|
||||
iterationLogSaver(iteration: Iteration): void {
|
||||
iterationLogSaver(_iteration: Iteration): void {
|
||||
// throw new Error("Method not implemented.");
|
||||
// citeration.result.data
|
||||
}
|
||||
|
||||
iterationErrorObserver(iteration: Iteration): void {
|
||||
|
@ -59,10 +58,11 @@ export class PipelineRealTimeService extends TypedEvent<ActivePipeline> {
|
|||
this.status.pipelineIsRunning = false;
|
||||
}
|
||||
}
|
||||
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectUUID: string) {
|
||||
setPipelineDependency(pipelineModels: IPipeline[], path: string, projectId: string, rootDir: string) {
|
||||
this.pipelineModels = pipelineModels;
|
||||
this.status["projectUUID"] = projectUUID;
|
||||
this.status["projectId"] = projectId;
|
||||
this.status["path"] = path;
|
||||
this.status["rootDir"] = rootDir;
|
||||
}
|
||||
runPipeline(): void {
|
||||
const stack = new StackService(this.pipelineModels, this.status.path);
|
||||
|
|
|
@ -8,7 +8,8 @@ export class CreateFileUseCase {
|
|||
}
|
||||
async call(path: string, buffer: Buffer): Promise<Result<Error, void>> {
|
||||
try {
|
||||
await this.fileSystemRepository.writeFileAsync(path, buffer);
|
||||
await this.fileSystemRepository.writeFileAsync(path.pathNormalize(), buffer);
|
||||
|
||||
return Result.ok();
|
||||
} catch (err) {
|
||||
return Result.error(err as Error);
|
||||
|
|
|
@ -13,9 +13,13 @@ export class ReadFileAndParseJsonUseCase {
|
|||
return Result.error(`ReadFileAndParseJsonUseCase got the bad way: ${path}`);
|
||||
}
|
||||
const file = await this.fileSystemRepository.readFileAsync(path);
|
||||
return Result.ok(JSON.parse(file.toString()));
|
||||
try {
|
||||
return Result.ok(JSON.parse(file.toString()));
|
||||
} catch {
|
||||
return Result.error(`ReadFileAndParseJsonUseCase is not json type file parse path: ${path}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.error(`ReadFileAndParseJsonUseCase is not json type file parse path:${path}`);
|
||||
return Result.error(`ReadFileAndParseJsonUseCase error:${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
server/src/core/usecases/write_file_system_file_usecase.ts
Normal file
13
server/src/core/usecases/write_file_system_file_usecase.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Result } from "../helpers/result";
|
||||
import { FileSystemRepository } from "../repository/file_system_repository";
|
||||
|
||||
export class WriteFileSystemFileUseCase {
|
||||
call = async (path: string, fileData: string): Promise<Result<string, void>> => {
|
||||
try {
|
||||
await new FileSystemRepository().writeFileAsync(path, fileData);
|
||||
return Result.ok(undefined);
|
||||
} catch (error) {
|
||||
return Result.error(`WriteFileSystemFileUseCase error: ${error}`);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
interface IMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export abstract class CoreValidation {
|
||||
abstract regExp: RegExp;
|
||||
abstract message: string;
|
||||
abstract message: IMessage;
|
||||
}
|
||||
|
|
|
@ -2,5 +2,5 @@ import { CoreValidation } from "./core_validation";
|
|||
|
||||
export class MongoIdValidation extends CoreValidation {
|
||||
regExp = RegExp("^[0-9a-fA-F]{24}$");
|
||||
message = "is do not mongo db object uuid";
|
||||
message = { message: "is do not mongo db object uuid" };
|
||||
}
|
||||
|
|
|
@ -2,28 +2,20 @@ import { App } from "../../../core/controllers/app";
|
|||
import { Result } from "../../../core/helpers/result";
|
||||
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
|
||||
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
|
||||
import { pipelineRealTimeService } from "../../realtime/realtime_presentation";
|
||||
import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
|
||||
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
|
||||
|
||||
export class CreateNewProjectInstanceScenario {
|
||||
call = async (model: ProjectInstanceValidationModel): Promise<Result<Error, any>> => {
|
||||
call = async (): Promise<Result<Error, any>> => {
|
||||
try {
|
||||
const folderName = uuidv4() + "/";
|
||||
const createFolderUseCase = await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName);
|
||||
if (createFolderUseCase.isFailure()) {
|
||||
return createFolderUseCase.forward();
|
||||
}
|
||||
model.rootDir = folderName;
|
||||
|
||||
const createDataBaseModelUseCase = await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model);
|
||||
|
||||
if (createDataBaseModelUseCase.isFailure()) {
|
||||
return createDataBaseModelUseCase.forward();
|
||||
}
|
||||
|
||||
return Result.ok({ status: "ok" });
|
||||
// (await new SetActiveProjectScenario().call(id)).map(() => {
|
||||
// return Result.ok({ status: "ok" });
|
||||
// });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Result.error(error as Error);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { CallbackStrategyWithEmpty, ResponseBase } from "../../../core/controlle
|
|||
import { Result } from "../../../core/helpers/result";
|
||||
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
|
||||
import { StaticFiles } from "../../../core/models/static_files";
|
||||
import { ReadingJsonFileAndConvertingToInstanceClass } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
|
||||
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
|
||||
import { GetServerAddressUseCase } from "../../../core/usecases/get_server_address_usecase";
|
||||
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
|
||||
|
||||
|
@ -13,13 +13,15 @@ export class RobossemblerAssetsNetworkMapperScenario extends CallbackStrategyWit
|
|||
|
||||
return await result.map(async (activeInstanceModel) => {
|
||||
return (
|
||||
await new ReadingJsonFileAndConvertingToInstanceClass(RobossemblerAssets).call(
|
||||
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
|
||||
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`
|
||||
)
|
||||
).map((robossemblerAssets) => {
|
||||
return new GetServerAddressUseCase().call().map((address) => {
|
||||
return Result.ok(
|
||||
robossemblerAssets.convertLocalPathsToServerPaths(`${address}/${activeInstanceModel.projectUUID}`)
|
||||
robossemblerAssets.convertLocalPathsToServerPaths(
|
||||
`${address}/${activeInstanceModel.rootDir.pathNormalize()}`
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { CallbackStrategyWithValidationModel, ResponseBase } from "../../../core/controllers/http_controller";
|
||||
import { Result } from "../../../core/helpers/result";
|
||||
import { RobossemblerAssets } from "../../../core/models/robossembler_assets";
|
||||
import { StaticFiles } from "../../../core/models/static_files";
|
||||
import { ReadingJsonFileAndConvertingToInstanceClassScenario } from "../../../core/scenarios/read_file_and_json_to_plain_instance_class_scenario";
|
||||
import { WriteFileSystemFileUseCase } from "../../../core/usecases/write_file_system_file_usecase";
|
||||
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
|
||||
|
||||
export class SaveActiveSceneScenario extends CallbackStrategyWithValidationModel<RobossemblerAssets> {
|
||||
validationModel: RobossemblerAssets = new RobossemblerAssets();
|
||||
async call(model: RobossemblerAssets): ResponseBase {
|
||||
return (await new PipelineStatusUseCase().call()).map(async (activeInstanceModel) =>
|
||||
(
|
||||
await new ReadingJsonFileAndConvertingToInstanceClassScenario(RobossemblerAssets).call(
|
||||
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize()
|
||||
)
|
||||
).map(async (prevModel) => {
|
||||
model.assets = prevModel.assets;
|
||||
return (
|
||||
await new WriteFileSystemFileUseCase().call(
|
||||
`${activeInstanceModel.path}${StaticFiles.robossembler_assets}`.pathNormalize(),
|
||||
JSON.stringify(model)
|
||||
)
|
||||
).map(() => Result.ok("assets is rewrite"));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { App } from "../../../core/controllers/app";
|
||||
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
|
||||
import { Result } from "../../../core/helpers/result";
|
||||
import { SetLastActivePipelineToRealTimeServiceScenario } from "../../../core/scenarios/set_active_pipeline_to_realtime_service_scenario";
|
||||
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
|
||||
import { FindPredicateModelAndUpdateDatabaseModelUseCase } from "../../../core/usecases/find_and_update_database_model_usecase";
|
||||
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
|
||||
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
|
||||
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
|
||||
|
@ -13,16 +13,21 @@ export class SetActiveProjectScenario extends CallbackStrategyWithIdQuery {
|
|||
|
||||
async call(id: string): ResponseBase {
|
||||
const result = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(ProjectInstanceDbModel).call(id);
|
||||
// id
|
||||
|
||||
if (result.isFailure()) {
|
||||
return result.forward();
|
||||
}
|
||||
|
||||
const model = result.value;
|
||||
return (await new CreateFolderUseCase().call(App.staticFilesStoreDir() + model.rootDir)).map(async () => {
|
||||
|
||||
return await (
|
||||
await new CreateFolderUseCase().call(App.staticFilesStoreDir() + model.rootDir)
|
||||
).map(async () => {
|
||||
model.isActive = true;
|
||||
new FindPredicateModelAndUpdateDatabaseModelUseCase(ProjectInstanceDbModel).call({}, {});
|
||||
return (await new UpdateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(() => {
|
||||
return (await new UpdateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (el) => {
|
||||
// TODO(IDONTSUDO): move it to a separate UseCase
|
||||
await ProjectInstanceDbModel.updateMany({ _id: { $ne: el._id }, isActive: { $eq: true } }, { isActive: false });
|
||||
await new SetLastActivePipelineToRealTimeServiceScenario().call();
|
||||
return Result.ok(`project ${id} is active`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,23 +1,37 @@
|
|||
import { App } from "../../../core/controllers/app";
|
||||
import { CallbackStrategyWithFileUpload, ResponseBase } from "../../../core/controllers/http_controller";
|
||||
import { Result } from "../../../core/helpers/result";
|
||||
import { IFile } from "../../../core/interfaces/file";
|
||||
import { CreateDataBaseModelUseCase } from "../../../core/usecases/create_database_model_usecase";
|
||||
import { CreateFileUseCase } from "../../../core/usecases/create_file_usecase";
|
||||
import { PipelineStatusUseCase } from "../../realtime/domain/pipeline_status_usecase";
|
||||
import { CreateFolderUseCase } from "../../../core/usecases/create_folder_usecase";
|
||||
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
|
||||
import { ProjectInstanceDbModel } from "../models/project_instance_database_model";
|
||||
import { ProjectInstanceValidationModel } from "../models/project_instance_validation_model";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { SetActiveProjectScenario } from "./set_active_project_use_scenario";
|
||||
|
||||
export class UploadCadFileToProjectScenario extends CallbackStrategyWithFileUpload {
|
||||
checkingFileExpression: RegExp = RegExp(".FCStd");
|
||||
idValidationExpression = new MongoIdValidation();
|
||||
|
||||
async call(file: IFile): ResponseBase {
|
||||
const pipelineStatusUseCase = await new PipelineStatusUseCase().call();
|
||||
if (pipelineStatusUseCase.isFailure()) {
|
||||
return pipelineStatusUseCase.forward();
|
||||
}
|
||||
const projectFolder = pipelineStatusUseCase.value.path;
|
||||
const createFileUseCase = await new CreateFileUseCase().call(projectFolder + file.name, file.data);
|
||||
if (createFileUseCase.isFailure()) {
|
||||
return createFileUseCase.forward();
|
||||
}
|
||||
|
||||
return Result.ok("ok");
|
||||
async call(file: IFile, id: string, description: string): ResponseBase {
|
||||
const folderName = uuidv4() + "/";
|
||||
const model = new ProjectInstanceValidationModel();
|
||||
model["project"] = id;
|
||||
model["description"] = description;
|
||||
model["rootDir"] = folderName;
|
||||
model["isActive"] = true;
|
||||
return (await new CreateFolderUseCase().call(App.staticFilesStoreDir() + folderName)).map(async () =>
|
||||
(await new CreateDataBaseModelUseCase(ProjectInstanceDbModel).call(model)).map(async (databaseModel) =>
|
||||
(await new SetActiveProjectScenario().call(databaseModel.id)).map(async () =>
|
||||
(await new CreateFileUseCase().call(App.staticFilesStoreDir() + folderName + file.name, file.data)).map(
|
||||
() => {
|
||||
return Result.ok("ok");
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,7 @@ export class ProjectInstanceValidationModel {
|
|||
|
||||
@IsOptional()
|
||||
public rootDir: string;
|
||||
|
||||
@IsOptional()
|
||||
public isActive: boolean;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { CrudController } from "../../core/controllers/crud_controller";
|
||||
import { CoreHttpController } from "../../core/controllers/http_controller";
|
||||
import { RobossemblerAssets } from "../../core/models/robossembler_assets";
|
||||
|
||||
import { CreateNewProjectInstanceScenario } from "./domain/create_new_project_scenario";
|
||||
import { RobossemblerAssetsNetworkMapperScenario } from "./domain/robossembler_assets_network_mapper_scenario";
|
||||
import { SaveActiveSceneScenario } from "./domain/save_active_scene_scenario";
|
||||
import { SetActiveProjectScenario } from "./domain/set_active_project_use_scenario";
|
||||
import { UploadCadFileToProjectScenario } from "./domain/upload_file_to_to_project_scenario";
|
||||
import { ProjectInstanceDbModel } from "./models/project_instance_database_model";
|
||||
|
@ -31,11 +34,16 @@ export class ProjectInstancePresentation extends CrudController<
|
|||
subUrl: "upload",
|
||||
fn: new UploadCadFileToProjectScenario(),
|
||||
});
|
||||
|
||||
this.subRoutes.push({
|
||||
method: "GET",
|
||||
subUrl: "assets",
|
||||
fn: new RobossemblerAssetsNetworkMapperScenario(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RobossemblerAssetsPresentation extends CoreHttpController<RobossemblerAssets> {
|
||||
constructor() {
|
||||
super({
|
||||
url: "robossembler_assets",
|
||||
validationModel: RobossemblerAssets,
|
||||
});
|
||||
super.get(new RobossemblerAssetsNetworkMapperScenario().call);
|
||||
super.post(new SaveActiveSceneScenario().call);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ export class PipelineStatusUseCase {
|
|||
async call(): Promise<Result<Error, ActivePipeline>> {
|
||||
try {
|
||||
const status = pipelineRealTimeService.status;
|
||||
if (status.projectUUID !== null) {
|
||||
if (status.projectId !== null) {
|
||||
return Result.ok(status);
|
||||
}
|
||||
|
||||
if (status.projectUUID === null) {
|
||||
if (status.projectId === null) {
|
||||
return Result.error(new Error("pipelineRealTimeService does not have an active project instance"));
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { App } from "../../../core/controllers/app";
|
||||
import { CallbackStrategyWithEmpty } from "../../../core/controllers/http_controller";
|
||||
import { Result } from "../../../core/helpers/result";
|
||||
import { IPipeline } from "../../../core/models/process_model";
|
||||
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
|
||||
|
@ -28,30 +29,32 @@ const mongoPipelineModelMapper = (el: PipelineValidationModel): IPipeline => {
|
|||
};
|
||||
return mapObj;
|
||||
};
|
||||
export class RunInstancePipelineUseCase {
|
||||
async call(): Promise<Result<Error, any>> {
|
||||
const r = await new PipelineStatusUseCase().call();
|
||||
if (r.isFailure()) {
|
||||
return r;
|
||||
}
|
||||
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
|
||||
ProjectInstanceDbModel
|
||||
).call(r.value.projectUUID);
|
||||
|
||||
if (readByIdDataBaseModelUseCase.isFailure()) {
|
||||
return readByIdDataBaseModelUseCase.forward();
|
||||
}
|
||||
const projectModel = readByIdDataBaseModelUseCase.value;
|
||||
const resultMapper = projectModel.project.pipelines.map((el) => mongoPipelineModelMapper(el));
|
||||
export class RunInstancePipelineUseCase extends CallbackStrategyWithEmpty {
|
||||
async call(): Promise<Result<any, string>> {
|
||||
return (await new PipelineStatusUseCase().call()).map(async (activePipelineModel) => {
|
||||
if (activePipelineModel.pipelineIsRunning) {
|
||||
return Result.error("pipeline is running");
|
||||
}
|
||||
const readByIdDataBaseModelUseCase = await new ReadByIdDataBaseModelUseCase<IProjectInstanceModel>(
|
||||
ProjectInstanceDbModel
|
||||
).call(activePipelineModel.projectId);
|
||||
|
||||
pipelineRealTimeService.setPipelineDependency(
|
||||
resultMapper,
|
||||
App.staticFilesStoreDir() + projectModel.rootDir + "/",
|
||||
projectModel._id
|
||||
);
|
||||
if (readByIdDataBaseModelUseCase.isFailure()) {
|
||||
return readByIdDataBaseModelUseCase.forward();
|
||||
}
|
||||
const projectModel = readByIdDataBaseModelUseCase.value;
|
||||
const resultMapper = projectModel.project.pipelines.map((el) => mongoPipelineModelMapper(el));
|
||||
|
||||
pipelineRealTimeService.runPipeline();
|
||||
pipelineRealTimeService.setPipelineDependency(
|
||||
resultMapper,
|
||||
App.staticFilesStoreDir() + projectModel.rootDir + "/",
|
||||
projectModel._id,
|
||||
projectModel.rootDir
|
||||
);
|
||||
|
||||
return Result.ok({ status: "ok" });
|
||||
pipelineRealTimeService.runPipeline();
|
||||
return Result.ok("ok");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,11 @@ export class RealTimePresentation extends CoreHttpController<RealTimeValidationM
|
|||
url: "realtime",
|
||||
databaseModel: null,
|
||||
});
|
||||
super.post(new RunInstancePipelineUseCase().call);
|
||||
super.get(new PipelineStatusUseCase().call);
|
||||
this.subRoutes.push({
|
||||
method: "POST",
|
||||
subUrl: "run",
|
||||
fn: new RunInstancePipelineUseCase(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,11 @@ import { SocketSubscriber } from "./core/controllers/socket_controller";
|
|||
import { extensions } from "./core/extensions/extensions";
|
||||
import { httpRoutes } from "./core/controllers/routes";
|
||||
import { pipelineRealTimeService } from "./features/realtime/realtime_presentation";
|
||||
import { main } from "./p";
|
||||
|
||||
extensions();
|
||||
|
||||
const socketSubscribers = [new SocketSubscriber(pipelineRealTimeService, "realtime")];
|
||||
|
||||
new App(httpRoutes, socketSubscribers).listen();
|
||||
main();
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"@types/react-dom": "^18.2.7",
|
||||
"@types/socket.io-client": "^3.0.0",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"formik-antd": "^2.0.4",
|
||||
"i18next": "^23.6.0",
|
||||
"mobx": "^6.10.0",
|
||||
|
@ -24,9 +26,13 @@
|
|||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sass": "^1.66.1",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"three": "^0.159.0",
|
||||
"three-stdlib": "^2.28.9",
|
||||
"three-transform-controls": "^1.0.4",
|
||||
"ts-pattern": "^5.0.6",
|
||||
"typescript": "^4.9.5",
|
||||
"urdf-loader": "^0.12.1",
|
||||
"uuid": "^9.0.1",
|
||||
|
|
|
@ -16,6 +16,7 @@ declare global {
|
|||
}
|
||||
interface String {
|
||||
isEmpty(): boolean;
|
||||
isNotEmpty(): boolean;
|
||||
}
|
||||
interface Map<K, V> {
|
||||
addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
|
||||
|
|
|
@ -5,4 +5,10 @@ export const StringExtensions = () => {
|
|||
return this.length === 0;
|
||||
};
|
||||
}
|
||||
if ("".isNotEmpty === undefined) {
|
||||
// eslint-disable-next-line no-extend-native
|
||||
String.prototype.isNotEmpty = function () {
|
||||
return this.length !== 0;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
82
ui/src/core/helper/debounce.ts
Normal file
82
ui/src/core/helper/debounce.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
export type Options<Result> = {
|
||||
isImmediate?: boolean;
|
||||
maxWait?: number;
|
||||
callback?: (data: Result) => void;
|
||||
};
|
||||
|
||||
export interface DebouncedFunction<Args extends any[], F extends (...args: Args) => any> {
|
||||
(this: ThisParameterType<F>, ...args: Args & Parameters<F>): Promise<ReturnType<F>>;
|
||||
cancel: (reason?: any) => void;
|
||||
}
|
||||
|
||||
interface DebouncedPromise<FunctionReturn> {
|
||||
resolve: (result: FunctionReturn) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}
|
||||
|
||||
export function debounce<Args extends any[], F extends (...args: Args) => any>(
|
||||
func: F,
|
||||
waitMilliseconds = 50,
|
||||
options: Options<ReturnType<F>> = {}
|
||||
): DebouncedFunction<Args, F> {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
const isImmediate = options.isImmediate ?? false;
|
||||
const callback = options.callback ?? false;
|
||||
const maxWait = options.maxWait;
|
||||
let lastInvokeTime = Date.now();
|
||||
|
||||
let promises: DebouncedPromise<ReturnType<F>>[] = [];
|
||||
|
||||
function nextInvokeTimeout() {
|
||||
if (maxWait !== undefined) {
|
||||
const timeSinceLastInvocation = Date.now() - lastInvokeTime;
|
||||
|
||||
if (timeSinceLastInvocation + waitMilliseconds >= maxWait) {
|
||||
return maxWait - timeSinceLastInvocation;
|
||||
}
|
||||
}
|
||||
|
||||
return waitMilliseconds;
|
||||
}
|
||||
|
||||
const debouncedFunction = function (this: ThisParameterType<F>, ...args: Parameters<F>) {
|
||||
const context = this;
|
||||
return new Promise<ReturnType<F>>((resolve, reject) => {
|
||||
const invokeFunction = function () {
|
||||
timeoutId = undefined;
|
||||
lastInvokeTime = Date.now();
|
||||
if (!isImmediate) {
|
||||
const result = func.apply(context, args);
|
||||
callback && callback(result);
|
||||
promises.forEach(({ resolve }) => resolve(result));
|
||||
promises = [];
|
||||
}
|
||||
};
|
||||
|
||||
const shouldCallNow = isImmediate && timeoutId === undefined;
|
||||
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(invokeFunction, nextInvokeTimeout());
|
||||
|
||||
if (shouldCallNow) {
|
||||
const result = func.apply(context, args);
|
||||
callback && callback(result);
|
||||
return resolve(result);
|
||||
}
|
||||
promises.push({ resolve, reject });
|
||||
});
|
||||
};
|
||||
|
||||
debouncedFunction.cancel = function (reason?: any) {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
promises.forEach(({ reject }) => reject(reason));
|
||||
promises = [];
|
||||
};
|
||||
|
||||
return debouncedFunction;
|
||||
}
|
29
ui/src/core/helper/throttle.ts
Normal file
29
ui/src/core/helper/throttle.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
export const throttle = <R, A extends any[]>(
|
||||
fn: (...args: A) => R,
|
||||
delay: number
|
||||
): [(...args: A) => R | undefined, () => void] => {
|
||||
let wait = false;
|
||||
let timeout: undefined | number;
|
||||
let cancelled = false;
|
||||
|
||||
return [
|
||||
(...args: A) => {
|
||||
if (cancelled) return undefined;
|
||||
if (wait) return undefined;
|
||||
|
||||
const val = fn(...args);
|
||||
|
||||
wait = true;
|
||||
|
||||
timeout = window.setTimeout(() => {
|
||||
wait = false;
|
||||
}, delay);
|
||||
|
||||
return val;
|
||||
},
|
||||
() => {
|
||||
cancelled = true;
|
||||
clearTimeout(timeout);
|
||||
},
|
||||
];
|
||||
};
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
export interface ActivePipeline {
|
||||
pipelineIsRunning: boolean;
|
||||
projectUUID?: string | null;
|
||||
projectId?: string | null;
|
||||
lastProcessCompleteCount: number | null;
|
||||
error: any;
|
||||
}
|
||||
|
|
|
@ -6,47 +6,137 @@ import {
|
|||
WebGLRenderer,
|
||||
AmbientLight,
|
||||
Vector3,
|
||||
MeshBasicMaterial,
|
||||
Mesh,
|
||||
BoxGeometry,
|
||||
Object3DEventMap,
|
||||
Box3,
|
||||
Sphere,
|
||||
LineBasicMaterial,
|
||||
EdgesGeometry,
|
||||
Intersection,
|
||||
Raycaster,
|
||||
LineSegments,
|
||||
Vector2,
|
||||
Color,
|
||||
GridHelper,
|
||||
CameraHelper,
|
||||
} from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import { BaseSceneItemModel, StaticAssetItemModel } from "../../features/scene_manager/scene_manager_store";
|
||||
import { TypedEvent } from "../helper/typed_event";
|
||||
import { Result } from "../helper/result";
|
||||
import { GLTFLoader, OrbitControls, TransformControls, OBJLoader, STLLoader, ColladaLoader } from "three-stdlib";
|
||||
import {
|
||||
BaseSceneItemModel,
|
||||
CameraViewModel,
|
||||
StaticAssetItemModel,
|
||||
} from "../../features/scene_manager/model/scene_assets";
|
||||
import { SceneMode } from "../../features/scene_manager/model/scene_view";
|
||||
import { throttle } from "../helper/throttle";
|
||||
import {
|
||||
InstanceRgbCamera,
|
||||
RobossemblerAssets,
|
||||
SceneSimpleObject,
|
||||
} from "../../features/scene_manager/model/robossembler_assets";
|
||||
|
||||
export enum UserData {
|
||||
selectedObject = "selected_object",
|
||||
cameraInitialization = "camera_initialization",
|
||||
}
|
||||
|
||||
interface IEventDraggingChange {
|
||||
target: null;
|
||||
type: string;
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface IEmissiveCache {
|
||||
status: boolean;
|
||||
object3d: Object3D<Object3DEventMap>;
|
||||
}
|
||||
|
||||
export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
|
||||
scene = new Scene();
|
||||
camera: PerspectiveCamera;
|
||||
webGlRender: WebGLRenderer;
|
||||
htmlCanvasRef: HTMLCanvasElement;
|
||||
objectEmissive = new Map<string, IEmissiveCache>();
|
||||
constructor(htmlCanvasRef: HTMLCanvasElement) {
|
||||
transformControls: TransformControls;
|
||||
orbitControls: OrbitControls;
|
||||
htmlSceneWidth: number;
|
||||
htmlSceneHeight: number;
|
||||
objLoader = new OBJLoader();
|
||||
glbLoader = new GLTFLoader();
|
||||
daeLoader = new ColladaLoader();
|
||||
stlLoader = new STLLoader();
|
||||
watcherSceneEditorObject: Function;
|
||||
|
||||
constructor(htmlCanvasRef: HTMLCanvasElement, watcherSceneEditorObject: Function) {
|
||||
super();
|
||||
this.htmlSceneWidth = window.innerWidth;
|
||||
this.htmlSceneHeight = window.innerHeight;
|
||||
this.watcherSceneEditorObject = watcherSceneEditorObject;
|
||||
const renderer = new WebGLRenderer({
|
||||
canvas: htmlCanvasRef as HTMLCanvasElement,
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
});
|
||||
const aspectCamera = window.outerWidth / window.outerHeight;
|
||||
const aspectCamera = this.htmlSceneWidth / this.htmlSceneHeight;
|
||||
this.camera = new PerspectiveCamera(800, aspectCamera, 0.1, 10000);
|
||||
this.camera.position.set(60, 20, 10);
|
||||
|
||||
this.webGlRender = renderer;
|
||||
this.htmlCanvasRef = htmlCanvasRef;
|
||||
|
||||
this.transformControls = new TransformControls(this.camera, htmlCanvasRef);
|
||||
|
||||
this.scene.add(this.transformControls);
|
||||
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
this.scene.background = new Color("black");
|
||||
|
||||
this.init();
|
||||
}
|
||||
setRayCastAndGetFirstObject(vector: Vector2): Result<void, string> {
|
||||
|
||||
deleteSceneItem(item: BaseSceneItemModel) {
|
||||
const updateScene = this.scene;
|
||||
updateScene.children = item.deleteToScene(updateScene);
|
||||
}
|
||||
|
||||
loadInstances(robossemblerAssets: RobossemblerAssets) {
|
||||
robossemblerAssets.instances.forEach(async (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) {
|
||||
const asset = robossemblerAssets.getAssetAtInstance(el.instanceAt as string);
|
||||
this.loader([asset.meshPath], () => {}, asset.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTransformMode(mode?: SceneMode) {
|
||||
switch (mode) {
|
||||
case undefined:
|
||||
this.transformControls.detach();
|
||||
this.transformControls.dispose();
|
||||
break;
|
||||
case SceneMode.MOVING:
|
||||
this.transformControls.setMode("translate");
|
||||
break;
|
||||
case SceneMode.ROTATE:
|
||||
this.transformControls.setMode("rotate");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addSceneCamera(cameraModel: CameraViewModel) {
|
||||
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
|
||||
}
|
||||
|
||||
disposeTransformControlsMode() {
|
||||
this.transformControls.detach();
|
||||
}
|
||||
|
||||
setRayCastAndGetFirstObjectName(vector: Vector2): Result<void, string> {
|
||||
this.scene.add(this.transformControls);
|
||||
const raycaster = new Raycaster();
|
||||
raycaster.setFromCamera(vector, this.camera);
|
||||
const intersects = raycaster.intersectObjects(this.scene.children);
|
||||
|
@ -56,54 +146,169 @@ export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
|
|||
|
||||
return Result.error(undefined);
|
||||
}
|
||||
addCube(num: number, translateTo: string = "X") {
|
||||
const geometry = new BoxGeometry(1, 1, 1);
|
||||
const material = new MeshBasicMaterial({ color: 0x00ff00 });
|
||||
const cube = new Mesh(geometry, material);
|
||||
cube.name = "Cube" + String(num);
|
||||
|
||||
eval(`cube.translate${translateTo}(${num * 10})`);
|
||||
this.scene.add(cube);
|
||||
setTransformControlsAttach(object: Object3D<Object3DEventMap>) {
|
||||
if (object instanceof CameraHelper) {
|
||||
this.transformControls.attach(object.camera);
|
||||
return;
|
||||
}
|
||||
this.transformControls.attach(object);
|
||||
}
|
||||
init() {
|
||||
|
||||
setRayCastAndGetFirstObject(vector: Vector2): Result<void, Object3D<Object3DEventMap>> {
|
||||
try {
|
||||
const result = this.setRayCast(vector);
|
||||
return result.fold(
|
||||
(intersects) => {
|
||||
const result = intersects.find((element) => element.object.userData[UserData.selectedObject] !== undefined);
|
||||
if (result === undefined) {
|
||||
return Result.error(undefined);
|
||||
}
|
||||
return Result.ok(result.object);
|
||||
},
|
||||
(_error) => {
|
||||
return Result.error(undefined);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
return Result.error(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
setRayCast(vector: Vector2): Result<void, Intersection<Object3D<Object3DEventMap>>[]> {
|
||||
const raycaster = new Raycaster();
|
||||
raycaster.setFromCamera(vector, this.camera);
|
||||
const intersects = raycaster.intersectObjects(this.scene.children);
|
||||
if (intersects.length > 0) {
|
||||
return Result.ok(intersects);
|
||||
}
|
||||
|
||||
return Result.error(undefined);
|
||||
}
|
||||
|
||||
setRayCastAndGetFirstObjectAndPointToObject(vector: Vector2): Result<void, Vector3> {
|
||||
this.setRayCast(vector).map((intersects) => {
|
||||
if (intersects.length > 0) {
|
||||
return Result.ok(intersects[0].point);
|
||||
}
|
||||
});
|
||||
return Result.error(undefined);
|
||||
}
|
||||
|
||||
light() {
|
||||
const directionalLight = new DirectionalLight(0xffffff, 0.2);
|
||||
directionalLight.castShadow = true;
|
||||
directionalLight.position.set(-1, 2, 4);
|
||||
this.scene.add(directionalLight);
|
||||
const ambientLight = new AmbientLight(0xffffff, 0.7);
|
||||
this.scene.add(ambientLight);
|
||||
|
||||
this.addCube(1);
|
||||
this.addCube(2);
|
||||
this.addCube(3);
|
||||
this.addCube(4);
|
||||
|
||||
const onResize = () => {
|
||||
this.camera.aspect = window.outerWidth / window.outerHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.webGlRender.setSize(window.outerWidth, window.outerHeight);
|
||||
};
|
||||
window.addEventListener("resize", onResize, false);
|
||||
new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
() => {
|
||||
this.camera.aspect = this.htmlSceneWidth / this.htmlSceneHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
this.webGlRender.setSize(this.htmlSceneWidth, this.htmlSceneHeight);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
this.transformControls.addEventListener("dragging-changed", (event) => {
|
||||
const e = event as unknown as IEventDraggingChange;
|
||||
this.orbitControls.enabled = !e.value;
|
||||
});
|
||||
this.transformControls.addEventListener("objectChange", (event) => {
|
||||
//@ts-expect-error
|
||||
const sceneObject = event.target.object;
|
||||
//TODO:(IDONTSUDO) Trotting doesn't work, need to figure out why
|
||||
const fn = () => this.watcherSceneEditorObject(sceneObject);
|
||||
const [throttleFn] = throttle(fn, 1000);
|
||||
throttleFn();
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
this.light();
|
||||
this.addListeners();
|
||||
const floor = new GridHelper(100, 100, 0x888888, 0x444444);
|
||||
floor.userData = {};
|
||||
floor.userData[UserData.cameraInitialization] = true;
|
||||
this.scene.add(floor);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.webGlRender.setSize(window.outerWidth, window.outerHeight);
|
||||
this.webGlRender.setSize(this.htmlSceneWidth, this.htmlSceneHeight);
|
||||
this.webGlRender.setAnimationLoop(() => {
|
||||
this.webGlRender.render(this.scene, this.camera);
|
||||
});
|
||||
}
|
||||
|
||||
getAllSceneModels(): BaseSceneItemModel[] {
|
||||
return this.getAllSceneNameModels().map((e) => new StaticAssetItemModel(e));
|
||||
return this.getAllSceneNameModels().map(
|
||||
(name) =>
|
||||
new StaticAssetItemModel(name, this.getObjectsAtName(name).position, this.getObjectsAtName(name).quaternion)
|
||||
);
|
||||
}
|
||||
|
||||
getAllSceneNameModels(): string[] {
|
||||
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
|
||||
}
|
||||
|
||||
getObjectsAtName(name: string): Object3D<Object3DEventMap> {
|
||||
return this.scene.children.filter((el) => el.name === name)[0];
|
||||
}
|
||||
loader(urls: string[], callBack: Function) {}
|
||||
|
||||
loader(urls: string[], callBack: Function, name: string) {
|
||||
urls.map((el) => {
|
||||
const ext = el.split(/\./g).pop()!.toLowerCase();
|
||||
|
||||
switch (ext) {
|
||||
case "gltf":
|
||||
case "glb":
|
||||
this.glbLoader.load(
|
||||
el,
|
||||
(result) => {},
|
||||
(err) => {}
|
||||
);
|
||||
break;
|
||||
case "obj":
|
||||
this.objLoader.load(
|
||||
el,
|
||||
(result) => {
|
||||
result.userData[UserData.selectedObject] = true;
|
||||
result.children.forEach((el) => {
|
||||
el.userData[UserData.selectedObject] = true;
|
||||
el.name = name;
|
||||
this.emit(new StaticAssetItemModel(el.name, el.position, el.quaternion));
|
||||
this.scene.add(el);
|
||||
});
|
||||
},
|
||||
(err) => {}
|
||||
);
|
||||
break;
|
||||
case "dae":
|
||||
this.daeLoader.load(
|
||||
el,
|
||||
(result) => {},
|
||||
(err) => {}
|
||||
);
|
||||
break;
|
||||
case "stl":
|
||||
this.stlLoader.load(
|
||||
el,
|
||||
(result) => {},
|
||||
|
||||
(err) => {}
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fitCameraToCenteredObject(objects: string[], offset = 4) {
|
||||
// https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/
|
||||
const boundingBox = new Box3().setFromPoints(
|
||||
objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position)
|
||||
);
|
||||
|
@ -129,8 +334,9 @@ export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
|
|||
let orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
|
||||
orbitControls.maxDistance = cameraToFarEdge * 2;
|
||||
new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
this.orbitControls = orbitControls;
|
||||
}
|
||||
|
||||
switchObjectEmissive(name: string) {
|
||||
const mesh = this.getObjectsAtName(name);
|
||||
const result = this.objectEmissive.get(mesh.name);
|
||||
|
@ -150,9 +356,8 @@ export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
|
|||
if (mesh instanceof Mesh) {
|
||||
const newMesh = new LineSegments(mesh.geometry, new LineBasicMaterial({ color: 0x000000 }));
|
||||
newMesh.name = mesh.name;
|
||||
newMesh.translateX(mesh.position.x);
|
||||
newMesh.translateY(mesh.position.y);
|
||||
newMesh.translateZ(mesh.position.z);
|
||||
newMesh.position.copy(mesh.position);
|
||||
|
||||
this.scene.remove(mesh);
|
||||
this.scene.add(newMesh);
|
||||
}
|
||||
|
@ -180,10 +385,8 @@ export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
|
|||
|
||||
let newCameraPos = bsWorld.clone().add(cameraOffs);
|
||||
|
||||
this.camera.translateX(newCameraPos.x);
|
||||
this.camera.translateY(newCameraPos.y);
|
||||
this.camera.translateZ(newCameraPos.z);
|
||||
this.camera.position.copy(newCameraPos);
|
||||
this.camera.lookAt(bsWorld);
|
||||
new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ClassConstructor, plainToInstance } from "class-transformer";
|
||||
import { Result } from "../helper/result";
|
||||
|
||||
export enum HttpMethod {
|
||||
|
@ -16,7 +17,7 @@ export class HttpError extends Error {
|
|||
|
||||
export class HttpRepository {
|
||||
private server = "http://localhost:4001";
|
||||
public async formDataRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
public async _formDataRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
let formData = new FormData();
|
||||
formData.append("file", data);
|
||||
|
||||
|
@ -25,23 +26,19 @@ export class HttpRepository {
|
|||
method: method,
|
||||
};
|
||||
|
||||
if (data !== undefined) {
|
||||
reqInit["body"] = data;
|
||||
}
|
||||
const response = await fetch(this.server + url, reqInit);
|
||||
if (response.status !== 200) {
|
||||
throw Result.error(new Error(await response.json()));
|
||||
}
|
||||
return Result.ok(response.text as T);
|
||||
}
|
||||
public async jsonRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
public async _jsonRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
try {
|
||||
const reqInit = {
|
||||
body: data,
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
};
|
||||
console.log(reqInit);
|
||||
if (data !== undefined) {
|
||||
reqInit["body"] = JSON.stringify(data);
|
||||
}
|
||||
|
@ -57,7 +54,7 @@ export class HttpRepository {
|
|||
}
|
||||
}
|
||||
|
||||
public async request<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
public async _request<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
|
||||
const reqInit = {
|
||||
body: data,
|
||||
method: method,
|
||||
|
@ -72,4 +69,29 @@ export class HttpRepository {
|
|||
}
|
||||
return Result.ok(response.text as T);
|
||||
}
|
||||
public async _jsonToClassInstanceRequest<T>(
|
||||
method: HttpMethod,
|
||||
url: string,
|
||||
instance: ClassConstructor<T>,
|
||||
data?: any
|
||||
) {
|
||||
try {
|
||||
const reqInit = {
|
||||
body: data,
|
||||
method: method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
};
|
||||
if (data !== undefined) {
|
||||
reqInit["body"] = JSON.stringify(data);
|
||||
}
|
||||
const response = await fetch(this.server + url, reqInit);
|
||||
|
||||
if (response.status !== 200) {
|
||||
return Result.error(new HttpError(this.server + url, response.status));
|
||||
}
|
||||
return Result.ok(plainToInstance(instance, await response.json()) as T);
|
||||
} catch (error) {
|
||||
return Result.error(new HttpError(error, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
CreateProjectInstancePath,
|
||||
CreateProjectInstanceScreen,
|
||||
} from "../../features/create_project_instance/create_project_instance";
|
||||
import { SceneManger, SceneManagerPath } from "../../features/scene_manager/scene_manager";
|
||||
import { SceneManger, SceneManagerPath } from "../../features/scene_manager/presentation/scene_manager";
|
||||
|
||||
const idURL = ":id";
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "reflect-metadata";
|
||||
|
||||
import "antd/dist/antd.min.css";
|
||||
|
||||
|
@ -7,14 +8,19 @@ import { extensions } from "./core/extensions/extensions";
|
|||
import { SocketLister } from "./features/socket_lister/socket_lister";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { router } from "./core/routers/routers";
|
||||
import { SceneManger } from "./features/scene_manager/presentation/scene_manager";
|
||||
|
||||
extensions();
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
|
||||
root.render(
|
||||
<>
|
||||
<SocketLister>
|
||||
<RouterProvider router={router} />
|
||||
</SocketLister>
|
||||
{/* <SocketLister> */}
|
||||
{/* <RouterProvider router={router} /> */}
|
||||
|
||||
{/* </SocketLister> */}
|
||||
<>
|
||||
<SceneManger></SceneManger>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"useDefineForClassFields": true
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue