Prototype of web-based preview for subassemblies

This commit is contained in:
IDONTSUDO 2023-06-18 15:27:23 +00:00 committed by Igor Brylyov
parent 6560a4359d
commit 1fb7077774
55 changed files with 33076 additions and 0 deletions

View file

@ -0,0 +1,69 @@
import express from "express";
import compression from "compression";
import cors from "cors";
import { Routes } from "./core/interfaces/router";
import bodyParser from "body-parser";
import fileUpload from "express-fileupload";
import { DevEnv } from "./core/env/env";
import path from 'path';
import { locator } from "./core/di/register_di";
export const dirname = path.resolve();
const corsOptions = {
origin: process.env.CORS_ALLOW_ORIGIN || '*',
methods: ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};
export class App {
public app: express.Application;
public port: string | number;
public env: string;
constructor(routes: Routes[], port) {
this.app = express();
this.port = port;
this.env = process.env.NODE_ENV || "development";
this.initializeMiddleware();
this.initializeRoutes(routes);
this.loadAppDependencies();
}
public listen() {
this.app.listen(this.port, () => {
console.info(`=================================`);
console.info(`======= ENV: ${this.env} =======`);
console.info(`🚀 App listening on the port ${this.port}`);
console.info(`=================================`);
});
}
public getServer() {
return this.app;
}
private initializeMiddleware() {
this.app.use(
cors(corsOptions)
);
this.app.use(compression());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
console.log(dirname + '/public/')
this.app.use(express.static(dirname + '/public/'));
this.app.use(fileUpload({
createParentPath: true
}));
}
private initializeRoutes(routes: Routes[]) {
routes.forEach((route) => {
this.app.use("/", route.router);
});
}
loadAppDependencies() {
locator(new DevEnv());
}
}

View file

@ -0,0 +1,24 @@
import { override } from "first-di";
import { Env } from "../env/env";
import { AssemblyController } from "../../features/assembly_create/assembly_create_controller";
import { AssemblyPreviewsController } from "../../features/assembly_previews/assembly_previews_controller";
import { EntityRepository } from "../repository/entity_repository";
import { ZipRepository } from "../repository/zip_repository";
export const locator = (env: Env) => {
// override(Env, env)
registerRepository(env)
registerController(env)
};
const registerRepository = (env:Env) => {
override(ZipRepository, ZipRepository);
override(EntityRepository, EntityRepository)
}
const registerController = (env: Env) => {
override(AssemblyController,AssemblyController)
override(AssemblyPreviewsController, AssemblyController)
}

View file

@ -0,0 +1,10 @@
export class HttpException extends Error {
public status: number;
public message: string;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.message = message;
}
}

View file

@ -0,0 +1,202 @@
interface MemoOptions<F extends Fn, S extends unknown[] = unknown[]> {
/**
* Serialize the function call arguments
* This is used to identify cache key
*/
serialize?: (...args: Parameters<F>) => S;
}
interface MemoAsyncOptions<F extends Fn> extends MemoOptions<F> {
external?: {
get: (args: Parameters<F>) => Promise<Awaited<ReturnType<F>> | undefined | null>;
set: (args: Parameters<F>, value: Awaited<ReturnType<F>>) => Promise<void>;
remove: (args: Parameters<F>) => Promise<void>;
clear: () => Promise<void>;
};
}
type Fn = (...params: any[]) => any;
type AsyncFn = (...params: any[]) => Promise<any>;
interface MemoFunc<F extends Fn> {
// Call the target function, if cache is valid, return cache
(...args: Parameters<F>): ReturnType<F>;
// Same with this function
get(...args: Parameters<F>): ReturnType<F>;
// Call the raw function and skip cache
raw(...args: Parameters<F>): ReturnType<F>;
// Clear cache
clear(...args: Parameters<F> | []): void | Promise<void>;
}
export const enum State {
Empty,
Ok,
Waiting,
Error
}
export interface Node<T extends Fn> {
state: State;
value: ReturnType<T> | undefined;
error: unknown;
primitive: Map<any, Node<T>>;
reference: WeakMap<any, Node<T>>;
callbacks?: Set<{ res: (value: ReturnType<T>) => void; rej: (error: unknown) => void }>;
}
function makeNode<T extends Fn>(): Node<T> {
return {
state: State.Empty,
value: undefined,
error: undefined,
primitive: new Map(),
reference: new WeakMap()
};
}
function clearNode<T extends Fn>(node: Node<T> | undefined) {
if (node) {
node.state = State.Empty;
node.value = undefined;
node.error = undefined;
node.primitive = new Map();
node.reference = new WeakMap();
}
}
function isPrimitiveType(value: unknown) {
return (typeof value !== 'object' && typeof value !== 'function') || value === null;
}
function walkBase<T extends Fn, P extends any[] = Parameters<T>>(
node: Node<T>,
args: P,
hooks: { makeNode: () => Node<T> | undefined }
): Node<T> | undefined {
let cur = node;
for (const arg of args) {
if (isPrimitiveType(arg)) {
if (cur.primitive.has(arg)) {
cur = cur.primitive.get(arg)!;
} else {
const newNode = hooks.makeNode();
if (newNode) {
cur.primitive.set(arg, newNode);
cur = newNode;
} else {
return undefined;
}
}
} else {
if (cur.reference.has(arg)) {
cur = cur.reference.get(arg)!;
} else {
const newNode = hooks.makeNode();
if (newNode) {
cur.reference.set(arg, newNode);
cur = newNode;
} else {
return undefined;
}
}
}
}
return cur;
}
function walkAndCreate<T extends Fn, P extends any[] = Parameters<T>>(
node: Node<T>,
args: P
) {
return walkBase(node, args, { makeNode })!;
}
function walkOrBreak<T extends Fn, P extends any[] = Parameters<T>>(node: Node<T>, args: P) {
return walkBase(node, args, { makeNode: () => undefined });
}
export function memoAsync<F extends AsyncFn>(
fn: F,
options: MemoAsyncOptions<F> = {}
): MemoFunc<F> {
const root = makeNode<F>();
const memoFunc = async function (...args: Parameters<F>) {
// Serialize args
const path = options.serialize ? options.serialize(...args) : args;
const cur = walkAndCreate<F, any[]>(root, path);
if (cur.state === State.Ok) {
return cur.value;
} else if (cur.state === State.Error) {
throw cur.error;
} else if (cur.state === State.Waiting) {
return new Promise((res, rej) => {
if (!cur.callbacks) {
cur.callbacks = new Set();
}
cur.callbacks!.add({ res, rej });
});
} else {
try {
cur.state = State.Waiting;
const external = options.external ? await options.external.get(args) : undefined;
const value = external !== undefined && external !== null ? external : await fn(...args);
cur.state = State.Ok;
cur.value = value;
if (options.external) {
await options.external.set(args, value);
}
// Resolve other waiting callbacks
for (const callback of cur.callbacks ?? []) {
callback.res(value);
}
return value;
} catch (error) {
cur.state = State.Error;
cur.error = error;
// Reject other waiting callbacks
for (const callback of cur.callbacks ?? []) {
callback.rej(error);
}
throw error;
}
}
} as MemoFunc<F>;
memoFunc.get = (...args) => {
return memoFunc(...args);
};
memoFunc.raw = (...args) => {
return fn(...args) as ReturnType<F>;
};
memoFunc.clear = async (...args) => {
if (args.length === 0) {
clearNode(root);
if (options.external) {
await options.external.clear();
}
} else {
const cur = walkOrBreak<F>(root, args as Parameters<F>);
clearNode(cur);
if (options.external) {
await options.external.remove(args as Parameters<F>);
}
}
};
return memoFunc;
}

View file

@ -0,0 +1,6 @@
import { Router } from "express";
export interface Routes {
path?: string;
router: Router;
}

View file

@ -0,0 +1,26 @@
import { HttpException } from '../exceptions/HttpException';
import { plainToClass } from 'class-transformer';
import { validate, ValidationError } from 'class-validator';
import { RequestHandler } from 'express';
const validationMiddleware = (
type: any,
value = 'body',
skipMissingProperties = false,
whitelist = true,
forbidNonWhitelisted = true,
): RequestHandler => {
return (req, res, next) => {
console.log(req[value])
validate(plainToClass(type, req[value]), { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
if (errors.length > 0) {
const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', ');
next(new HttpException(400, message));
} else {
next();
}
});
};
};
export default validationMiddleware;

View file

@ -0,0 +1,3 @@
export class ComputeRepository{
}

View file

@ -0,0 +1,50 @@
import { promises as fs } from 'fs';
import { dirname } from '../../app';
import fsSync from "fs";
import { constants } from 'buffer';
export class EntityRepository {
private path: String = dirname + '/public/'
private getFileName(file: String) {
return file.slice(0, file.indexOf('.'))
}
public async getDir(path){
return this._fullPath(await fs.readdir(path + ''), duplicatedDelete(this.path, path))
}
public isExistDirPath(path:String):boolean{
return fsSync.existsSync(path + '')
}
public async saveRootEntity(buffer: Buffer, name: string) {
const filePath = this.path + this.getFileName(name) + '/'
if (this.isExistDirPath(filePath)) {
await fs.rm(filePath, { recursive: true })
}
await fs.mkdir(filePath);
await fs.writeFile(filePath + name, buffer);
}
public async getAllRootEntity() {
return await fs.readdir('' + this.path)
}
public async getEntityStorage(entity: string):Promise<String[]> | undefined {
return this._fullPath(await fs.readdir(this.path + entity), entity + '/' )
}
private _fullPath(folderPath,helpElement = '') {
return folderPath.map((el) => this.path + helpElement + el )
}
public async readJson<T>(path) {
return JSON.parse((await fs.readFile(path)).toString())
}
}
function duplicatedDelete(strChild:String,strMain:String){
let result = ''
for(let i = 0;i < strMain.length; i++){
if(!(strMain[i] === strChild[i])){
result+=strMain[i]
}
}
return result
}

View file

@ -0,0 +1,3 @@
export class ZipRepository {
}

View file

@ -0,0 +1,5 @@
import { AssemblyRoute } from "../../features/assembly_create/assembly_create_route";
import { AssemblyPreviewsRoute } from "../../features/assembly_previews/assembly_previews_route";
export const routes = [new AssemblyRoute(), new AssemblyPreviewsRoute()];

View file

@ -0,0 +1,36 @@
import { NextFunction, Request, Response } from 'express';
import { autowired } from 'first-di';
import "reflect-metadata";
import { dirname } from '../../app';
import { EntityRepository } from '../../core/repository/entity_repository';
import { IFile } from './model/zip_files_model';
export class AssemblyController {
public getAllAssembly = (req: Request, res: Response, next: NextFunction): void => {
throw new Error('Method not implemented.');
}
@autowired()
private readonly fsRepository: EntityRepository;
public createAssembly = (req: Request, res: Response, next: NextFunction): void => {
try {
const file = req.files.freecad as IFile;
const buffer = file.data as Buffer;
console.log(file.data)
// console.log(files.freecad.data)
// const filePath = dirname + '/' + files.freecad.name as string;
this.fsRepository.saveRootEntity(file.data, file.name)
// console.log(filePath)
res.sendStatus(200);
} catch (error) {
next(error);
}
};
}

View file

@ -0,0 +1,26 @@
import express, { Router } from 'express';
import { Routes } from '../../core/interfaces/router';
import { autowired } from 'first-di';
import { AssemblyController } from './assembly_create_controller';
import path from 'path';
import { dirname } from '../../app';
import validationMiddleware from '../../core/middlewares/ValidationMiddleware';
import { CadFilesModel } from './model/zip_files_model';
export class AssemblyRoute implements Routes {
public path = '/assembly';
public router = Router();
@autowired()
private readonly assemblyController: AssemblyController;
constructor() {
this.initializeRoutes();
}
private initializeRoutes() {
// this.router.use(`${this.path}`, express.static(path.join(dirname, '../../public')));
this.router.post(`${this.path}`, validationMiddleware(CadFilesModel, 'files'), this.assemblyController.createAssembly)
this.router.get(`${this.path}`, this.assemblyController.getAllAssembly)
}
}

View file

@ -0,0 +1,20 @@
import { IsArray, IsObject } from "class-validator";
export interface IFile {
name: string,
data: Buffer,
size: Number,
encoding: string,
tempFilePath: string,
truncated: Boolean,
mimetype: string,
md5: string,
}
interface ICadFileModel {
freecad: IFile;
}
export class CadFilesModel implements ICadFileModel {
@IsObject()
public freecad: IFile;
}

View file

@ -0,0 +1,151 @@
import { NextFunction, Request, Response } from "express";
import { autowired } from "first-di";
import { EntityRepository } from "../../core/repository/entity_repository";
import { port } from "../../server";
import { memoAsync } from "../../core/helper/memorization";
import "reflect-metadata";
import { async } from "node-stream-zip";
export class AssemblyPreviewsController {
@autowired()
private readonly entityRepository: EntityRepository;
public getAllAssembly = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
try {
res.send(await this.entityRepository.getAllRootEntity());
} catch (error) {
next(error);
}
};
public getAssemblySubsequenceById = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const entity = await this.entityRepository.getEntityStorage(
req.params.id
);
const aspUsage = Number(req.query.count) - 1;
console.log(aspUsage);
if (entity === undefined) {
res.status(404).json("entity not found");
return;
}
res.json(
await this._assemblyCompute(
aspUsage,
entity,
this.entityRepository,
req.hostname,
req.params.id
)
);
} catch (error) {
next(error);
}
};
public getAssemblyInsertionSequenceById = async (
req: Request,
res: Response,
next: NextFunction
) => {
const entity = await this.entityRepository.getEntityStorage(req.params.id);
const aspUsage = Number(req.query.count);
const assemblyFolder = entity.find((el) => {
return el.match("assembly");
});
const asmCountFolder = "0000" + aspUsage;
const assemblyDirPath = assemblyFolder + "/" + asmCountFolder;
if (!this.entityRepository.isExistDirPath(assemblyDirPath)) {
return res.status(400).json({ error: "bad request" });
}
const assemblyProcessDir = await this.entityRepository.getDir(
assemblyDirPath + "/process/"
);
const firstObj = assemblyProcessDir.find((el) => {
return el.match("1.obj");
});
const zeroObj = await assemblyProcessDir.find((el) => {
return el.match("0.obj");
});
const insertions = await this.entityRepository.readJson(
assemblyDirPath + "/" + "insertion_path.json"
);
if (
insertions === undefined ||
zeroObj === undefined ||
firstObj === undefined
) {
res.status(400).json({ error: "bad" });
return;
}
res.json({
offset: aspUsage,
count: 4,
parent: `http://${req.hostname}:${port}/${
req.params.id
}/assembly/${asmCountFolder}/${0}.obj`,
child: `http://${req.hostname}:${port}/${
req.params.id
}/assembly/${asmCountFolder}/${1}.obj`,
insertions: insertions,
});
return;
};
private async _assemblyCompute(
id: number,
entityFolder: Array<String>,
repository: EntityRepository,
host: string,
entity: string
) {
const assemblySequence = entityFolder.find((el) => {
return el.match("step-structure.json");
});
const assembly: Array<String> = await repository.readJson<Array<String>>(
assemblySequence
);
if (id == 0) {
return {
assembly: [
`http://${host}:${port}/${entity}/sdf/meshes/${assembly[id]}.obj`,
],
offset: 1,
count: assemblySequence.length,
};
} else {
const assemblyIndexed = assembly
.map((_item, index) => {
if (index <= id) {
return index;
}
})
.filter((el) => el != undefined);
return {
assembly: assemblyIndexed.map((el) => {
return `http://${host}:${port}/${entity}/sdf/meshes/${assembly[el]}.obj`;
}),
count: assemblyIndexed.length,
offset: assembly.length,
};
}
}
}

View file

@ -0,0 +1,29 @@
import express, { Router } from 'express';
import { Routes } from '../../core/interfaces/router';
import { autowired } from 'first-di';
// import { AssemblyController } from './assembly_create_controller';
import path from 'path';
import { dirname } from '../../app';
import validationMiddleware from '../../core/middlewares/ValidationMiddleware';
import { AssemblyPreviewsController } from './assembly_previews_controller';
// import { CadFilesModel } from './model/zip_files_model';
export class AssemblyPreviewsRoute implements Routes {
public path = '/assembly/preview/';
public router = Router();
@autowired()
private readonly assemblyController: AssemblyPreviewsController;
constructor() {
this.initializeRoutes();
}
private initializeRoutes() {
this.router.get(`${this.path}`, this.assemblyController.getAllAssembly);
// this.router.get(`${this.path}`)
this.router.get(`${this.path}subsequence/:id`, this.assemblyController.getAssemblySubsequenceById)
this.router.get(`${this.path}insertion_sequence/:id`, this.assemblyController.getAssemblyInsertionSequenceById)
// this.router.post(`${this.path}`, validationMiddleware(CadFilesModel, 'files'), this.assemblyController.createAssembly)
// this.router.get(`${this.path}`, this.assemblyController.getAllAssembly)
}
}

View file

@ -0,0 +1,14 @@
import { App } from "./app";
import { routes } from "./core/routes/routes";
import "reflect-metadata";
export const port = 3002
const app = new App(routes,port);
function main() {
app.listen();
}
main();