Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DPO3DPKRT-771/manual voyager scene regen #625

Merged
merged 17 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum API_ROUTES {
LOGIN = 'auth/login',
LOGOUT = 'auth/logout',
GEN_DOWNLOADS = 'api/scene/gen-downloads',
GEN_SCENE = 'api/workflow/gen-scene',
PROJECTS = 'api/project',
}

Expand Down Expand Up @@ -57,6 +58,24 @@ export default class API {

return this.request(uri, options);
}
static async generateScene(idSystemObject: number[], statusOnly: boolean = false, includeExisting: boolean = false): Promise<RequestResponse> {
// initiates the generate scene routine and either runs the recipe with the given id or returns its status.
// idSystemObject = the SystemObject id for the Packrat Model/Scene making this request
const body = JSON.stringify({ statusOnly, includeExisting, idSystemObject });
let uri: string = API_ROUTES.GEN_SCENE;
console.log('[PACKRAT:DEBUG] body: ',body);
console.trace('API.generateScene');

let options;
if(statusOnly) {
options = { method: 'GET' };
uri += `?id=${idSystemObject[0]}`; // add our index. only get status for single element
} else {
options = { method: 'POST', body };
}

return this.request(uri, options);
}

// project operations
static async getProjects(): Promise<RequestResponse> {
Expand Down
4 changes: 3 additions & 1 deletion client/src/pages/Admin/components/AdminToolsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,9 @@ function AdminToolsView(): React.ReactElement {
const authorizedUsers: number[] = [
2, // Jon Blundell
4, // Jamie Cope
5 // Eric Maslowski
5, // Eric Maslowski
6, // Megan Dattoria
11, // Katie Wolfe
];

// if our current user ID is not in the list then return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function DetailsThumbnail(props: DetailsThumbnailProps): React.ReactElement {
document.head.appendChild(css);

const script = document.createElement('script');
script.src = 'https://www.egofarms.com/temp/voyager-story.min.js'; //TODO: replace with config once bug fix applied: Config.voyager.storyJS;
script.src = Config.voyager.storyJS;
script.async = true;
document.body.appendChild(script);

Expand Down
39 changes: 39 additions & 0 deletions client/src/pages/Repository/components/DetailsView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,37 @@ function DetailsView(): React.ReactElement {
return true;
};

const generateScene = async (): Promise<boolean> => {
console.log('[PACKRAT] Generating Scene...', data.getSystemObjectDetails);

// make a call to our generate scene endpoint with the current scene id
// return sucess when the job is started or if one is already running
const response: RequestResponse = await API.generateScene([idSystemObject]);
if(response.success === false) {

// get our message from our first response
const responseMessage: string = response.data?.[0]?.message ?? 'undefined';

// if the job is running then handle differently
if(responseMessage.includes('already running')) {
console.log(`[Packrat - WARN] cannot generate scene. (${responseMessage})`);
toast.warn('Not generating scene. Job already running. Please wait for it to finish.');
} else {
console.log(`[Packrat - ERROR] cannot generate scene. (${responseMessage})`);
toast.error('Cannot generate scene. Check the report.');
}

// update our button state
// setCanGenerateDownloads(true);

// set to false to stop 'loading' animation on button. doesn't (currently) represent state of job on server
// setIsGeneratingDownloads(false);
return false;
}

return true;
};

const immutableNameTypes = new Set([eSystemObjectType.eItem, eSystemObjectType.eModel, eSystemObjectType.eScene]);

return (
Expand Down Expand Up @@ -719,6 +750,14 @@ function DetailsView(): React.ReactElement {
onClick={generateDownloads}
style={{ marginLeft: 5, width: '200px' }}
>Generate Downloads</LoadingButton>}

{(objectType === eSystemObjectType.eModel) &&
<LoadingButton className={classes.updateButton}
loading={false}
disabled={false}
onClick={generateScene}
style={{ marginLeft: 5, width: '200px' }}
>Generate Scene</LoadingButton>}
</Box>

<Box display='flex'>
Expand Down
8 changes: 7 additions & 1 deletion server/collections/impl/EdanCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class EdanCollection implements COL.ICollection {
}

// in addition to URI encoding the query, also replace single quotes with %27
const params: string = `q=${encodeURIComponent(query).replace(/'/g, '%27')}${filter}&rows=${rows}&start=${start}`;
const params: string = `q=${encodeURIComponent(query).replace(/'/g, '%27')}${filter}&rows=${rows}&start=${start}`;
const reqResult: HttpRequestResult = await this.sendRequest(eAPIType.eEDAN, eHTTPMethod.eGet, path, params);
if (!reqResult.success) {
LOG.error(`EdanCollection.queryCollection ${query}`, LOG.LS.eCOLL);
Expand Down Expand Up @@ -389,6 +389,12 @@ export class EdanCollection implements COL.ICollection {
LOG.info(`EdanCollection.sendRequest: ${url}`, LOG.LS.eCOLL);
const init: RequestInit = { method, body: body ?? undefined, headers: this.encodeHeader(params, contentType) };
const res = await fetch(url, init);

// debug statement for response
// const headers: string[] = [];
// res.headers.forEach((value, name) => { headers.push(`${name}: ${value}`); });
// LOG.info(`EdanCollection.sendRequest [${res.status}:${res.statusText}] response from ${url} (params: ${params})\n${headers.join('\n\t')}`,LOG.LS.eDEBUG);

return {
output: await res.text(),
statusText: res.statusText,
Expand Down
18 changes: 18 additions & 0 deletions server/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export type ConfigType = {
checkPeriod: number;
};
ldap: LDAPConfig;
users: {
admin: number[]; // users that are full admin
tools: number[]; // users that can access tools like batch processing
}
},
collection: {
type: COLLECTION_TYPE;
Expand Down Expand Up @@ -139,6 +143,20 @@ export const Config: ConfigType = {
DC: process.env.PACKRAT_LDAP_DC ? process.env.PACKRAT_LDAP_DC : 'DC=US,DC=SINET,DC=SI,DC=EDU',
CA: process.env.PACKRAT_LDAP_CA ? process.env.PACKRAT_LDAP_CA : '/etc/ssl/certs/ldaps.si.edu.cer',
},
users: {
admin: [
2, // Jon Blundell
4, // Jamie Cope
5, // Eric Maslowski
],
tools: [
2, // Jon Blundell
4, // Jamie Cope
5, // Eric Maslowski
6, // Megan Dattoria
11, // Katie Wolfe
]
},
},
collection: {
type: COLLECTION_TYPE.EDAN,
Expand Down
14 changes: 0 additions & 14 deletions server/db/api/Asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,6 @@ export class Asset extends DBC.DBObject<AssetBase> implements AssetBase, SystemO
`,Asset);
}

static async fetchVoyagerSceneFromScene(idScene: number): Promise<Asset[] | null> {
// get the voyager scene asset associated with the Packrat scene (if any)
// TODO: get asset type id from VocabularyID
const idvAssetType: number = 137;

return DBC.CopyArray<AssetBase, Asset>(
await DBC.DBConnection.prisma.$queryRaw<Asset[]>`
SELECT a.* FROM Scene AS scn
JOIN SystemObject AS scnSO ON scn.idScene = scnSO.idScene
JOIN Asset AS a ON a.idSystemObject = scnSO.idSystemObject
WHERE scn.idScene = ${idScene} AND a.idVAssetType = ${idvAssetType};
`,Asset);
}

/** Fetches assets that are connected to the specified idSystemObject (via that object's last SystemObjectVersion,
* and that SystemObjectVersionAssetVersionXref's records). For those assets, we look for a match on FileName, idVAssetType */
static async fetchMatching(idSystemObject: number, FileName: string, idVAssetType: number): Promise<Asset | null> {
Expand Down
24 changes: 24 additions & 0 deletions server/db/api/AssetVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,30 @@ export class AssetVersion extends DBC.DBObject<AssetVersionBase> implements Asse
}
}

/** Fetches the the active asset version that is being used by the given scene for Voyager SVX scene */
static async fetchActiveVoyagerSceneFromScene(idScene: number): Promise<AssetVersion | null> {
try {
// get the (active) voyager scene asset associated with the Packrat scene (if any)
// TODO: get asset type id from VocabularyID
const idvAssetType: number = 137;
const assetVersion: AssetVersion[] | null = DBC.CopyArray<AssetVersionBase, AssetVersion>(
await DBC.DBConnection.prisma.$queryRaw<AssetVersion[]>`
SELECT * FROM Scene AS scn
JOIN SystemObject AS scnSO ON (scnSO.idScene=scn.idScene)
JOIN SystemObjectVersion AS scnSOV ON (scnSOV.idSystemObject=scnSO.idSystemObject)
JOIN SystemObjectVersionAssetVersionXref as sovAssetXref ON (sovAssetXref.idSystemObjectVersion=scnSOV.idSystemObjectVersion)
JOIN AssetVersion AS av ON (av.idAssetVersion=sovAssetXref.idAssetVersion)
JOIN Asset AS a ON (a.idAsset=av.idAsset AND a.idVAssetType=${idvAssetType})
WHERE scn.idScene=${idScene}
ORDER BY scnSOV.DateCreated DESC
LIMIT 1`,AssetVersion);
return (!assetVersion || assetVersion.length===0) ? null : assetVersion[0];
} catch (error) /* istanbul ignore next */ {
LOG.error('DBAPI.AssetVersion.fetchVoyagerSceneFromScene', LOG.LS.eDB, error);
return null;
}
}

static async fetchFromUser(idUserCreator: number): Promise<AssetVersion[] | null> {
if (!idUserCreator)
return null;
Expand Down
18 changes: 18 additions & 0 deletions server/db/api/JobRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ export class JobRun extends DBC.DBObject<JobRunBase> implements JobRunBase {
return jobs;
}

static async fetchActiveByModel(idJob: number, idModel: number): Promise<JobRun[] | null> {
const jobs: JobRun[] = [];
const activeJobs: JobRun[] | null = await JobRun.fetchByJobFiltered(idJob,true);
if(!activeJobs) {
LOG.error('DBAPI.JobRun.fetchActiveByModel failed to get filtered jobs',LOG.LS.eDB);
return null;
}

// see if it's ours and if so, store the job's id
for(let i=0; i< activeJobs.length; i++){
if(activeJobs[i].usesModel(idModel)) {
jobs.push(activeJobs[i]);
}
}

return jobs;
}

static async fetchFromWorkflow(idWorkflow: number): Promise<JobRun[] | null> {
// direct get of JubRun(s) from a specific workflow
try {
Expand Down
21 changes: 21 additions & 0 deletions server/db/api/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,27 @@ export class Model extends DBC.DBObject<ModelBase> implements ModelBase, SystemO
}
}

static async fetchBySystemObject(idSystemObject: number): Promise<Model | null> {
if (!idSystemObject)
return null;
try {
const scenes: Model[] | null = DBC.CopyArray<ModelBase, Model>(
await DBC.DBConnection.prisma.$queryRaw<Model[]>`
SELECT * FROM Model AS mdl
JOIN SystemObject AS so ON (mdl.idModel = so.idModel)
WHERE so.idSystemObject = ${idSystemObject};`, Model);

// if we have scenes just return the first one, else null
if(scenes)
return scenes[0];

return null;
} catch (error) /* istanbul ignore next */ {
LOG.error('DBAPI.Model.fetchBySystemObject', LOG.LS.eDB, error);
return null;
}
}

/** fetches models which are children of either the specified idModelParent or idSceneParent, and have matching AutomationTag values */
static async fetchChildrenModels(idModelParent: number | null, idSceneParent: number | null, AutomationTag: string): Promise<Model[] | null> {
try {
Expand Down
27 changes: 27 additions & 0 deletions server/db/api/ModelSceneXref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class ModelSceneXref extends DBC.DBObject<ModelSceneXrefBase> implements
&& this.S1 === MSX.S1
&& this.S2 === MSX.S2;
}

/** return true if transform is updated */
public updateIfNeeded(MSX: ModelSceneXref): { transformUpdated: boolean, updated: boolean } {
let updated: boolean = false;
Expand Down Expand Up @@ -221,5 +222,31 @@ export class ModelSceneXref extends DBC.DBObject<ModelSceneXrefBase> implements
return null;
}
}

/** is this a downloadable asset? returns the idSystemObject if so */
public isDownloadable(): boolean {
if(!this.Name || !this.Usage) {
LOG.error(`DBAPI.ModelSceneXref.isDownloadable cannot run check due to missing info (${this.Name} | ${this.Usage})`, LOG.LS.eDB);
return false;
}

const downloadSuffixes: string[] = [
'4096_std.glb', // webAssetGlbLowUncompressed
'4096-gltf_std.zip', // gltfZipLow
'2048_std_draco.glb', // AR: App3D
'2048_std.usdz', // AR: iOSApp3D
'full_resolution-obj_std.zip', // objZipFull
'4096-obj_std.zip' // objZipLow
];

for(let i=0; i<downloadSuffixes.length; i++) {
if(this.Name.includes(downloadSuffixes[i])===true) {
// make sure usage matches
if(this.Usage.startsWith('Download:') || this.Usage.startsWith('App3D') || this.Usage.startsWith('iOSApp3D'))
return true;
}
}
return false;
}
}

2 changes: 1 addition & 1 deletion server/db/api/WorkflowReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class WorkflowReport extends DBC.DBObject<WorkflowReportBase> implements
try {
return DBC.CopyArray<WorkflowReportBase, WorkflowReport> (
await DBC.DBConnection.prisma.$queryRaw<WorkflowReport[]>`
SELECT w.* FROM JobRun AS jRun
SELECT wReport.* FROM JobRun AS jRun
JOIN WorkflowStep AS wStep ON wStep.idJobRun = jRun.idJobRun
JOIN WorkflowReport AS wReport ON wReport.idWorkflow = wStep.idWorkflow
WHERE jRun.idJobRun = ${idJobRun};`,WorkflowReport);
Expand Down
3 changes: 2 additions & 1 deletion server/db/sql/scripts/Packrat.DATA.sql
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (7, 5, 'Focal S
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (8, 1, 'Raw');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (8, 2, 'Processed');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (8, 3, 'From Camera');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (8, 4, 'Masks');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (9, 1, 'Scan To Mesh');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (9, 2, 'CAD');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (10, 1, 'Point Cloud');
Expand Down Expand Up @@ -237,6 +236,8 @@ INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (29, 16, 'Place
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (29, 17, 'Taxonomic Name (FT)');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (29, 18, 'Notes (FT)');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (29, 19, 'Physical Description (FT)');

INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (8, 4, 'Masks');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (30, 1, 'Alignment');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (30, 2, 'Reconstruction');
INSERT INTO Vocabulary (idVocabularySet, SortOrder, Term) VALUES (30, 3, 'Texture Generation');
Expand Down
4 changes: 4 additions & 0 deletions server/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ApolloServer } from 'apollo-server-express';
import cookieParser from 'cookie-parser';
import { v2 as webdav } from 'webdav-server';
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
import { generateScene } from './routes/api/generateVoyagerScene';

require('json-bigint-patch'); // patch JSON.stringify's handling of BigInt

Expand Down Expand Up @@ -127,6 +128,9 @@ export class HttpServer {
this.app.get('/api/scene/gen-downloads', generateDownloads);
this.app.post('/api/scene/gen-downloads', generateDownloads);

this.app.get('/api/workflow/gen-scene', generateScene);
this.app.post('/api/workflow/gen-scene', generateScene);

// if we're here then we handle any errors that may have surfaced
this.app.use(errorhandler); // keep last

Expand Down
8 changes: 4 additions & 4 deletions server/http/routes/api/generateDownloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ export async function generateDownloads(req: Request, res: Response): Promise<vo
}

// TEMP: limit IDs to a number that can be handled by Cook/Packrat
const maxIDs: number = 10;
const maxIDs: number = 50;
if(idSystemObjects.length>maxIDs) {
LOG.info('API.generateDownloads too many scenes submitted. limiting to 10',LOG.LS.eHTTP);
idSystemObjects.splice(10);
LOG.info(`API.generateDownloads too many scenes submitted. limiting to ${maxIDs}`,LOG.LS.eHTTP);
idSystemObjects.splice(maxIDs);
}

// cycle through IDs
Expand Down Expand Up @@ -350,7 +350,7 @@ export async function generateDownloads(req: Request, res: Response): Promise<vo

// if we want to republish the scene then we fire off the promise that checks the status
// and when done re-publishes the scene (non-blocking)
if(rePublish===true) {
if(result.success===true && rePublish===true) {
if(currentPublishedState===COMMON.ePublishedState.eNotPublished) {
LOG.error(`API.GenerateDownloads cannot publish unpublished scenes (${idSystemObject ?? -1})`,LOG.LS.eDB);
continue;
Expand Down
Loading
Loading