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

added createModel() to enable loading stl/obj files from a plaintext string within editor #6936

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
added createModel() to enable loading stl/obj files from a plaintext …
…string
  • Loading branch information
mathewpan2 committed Apr 3, 2024
commit e036ea0605c51c46dcd1e48007aaf806fb756d7f
198 changes: 198 additions & 0 deletions src/webgl/loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,204 @@
import p5 from '../core/main';
import './p5.Geometry';

/**
* Load a 3d model from an OBJ or STL string.
*
* <a href="#/p5/loadModel">createModel()</a> should be placed inside of <a href="#/p5/preload">preload()</a>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably take this sentence out since I think we don't do any preloading in this method?

* This allows the model to load fully before the rest of your code is run.
*
* One of the limitations of the OBJ and STL format is that it doesn't have a built-in
* sense of scale. This means that models exported from different programs might
* be very different sizes. If your model isn't displaying, try calling
* <a href="#/p5/loadModel">loadModel()</a> with the normalized parameter set to true. This will resize the
* model to a scale appropriate for p5. You can also make additional changes to
* the final size of your model with the <a href="#/p5/scale">scale()</a> function.
*
* Also, the support for colored STL files is not present. STL files with color will be
* rendered without color properties.
*
* Options can include:
* - `path`: Specifies the plain text string of either an stl or obj file to be loaded.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than specifying a path, maybe we can make the fileType mandatory and place it after the source string for all the different overloads for this method?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the filetype to the argument and fixed some of the overloads, let me know if you like it more now.

* - `normalize`: Enables standardized size scaling during loading if set to true.
* - `successCallback`: Callback for post-loading actions with the 3D model object.
* - `failureCallback`: Handles errors if model loading fails, receiving an event error.
* - `fileType`: Defines the file extension of the model.
* - `flipU`: Flips the U texture coordinates of the model.
* - `flipV`: Flips the V texture coordinates of the model.
*
* @method createModel
* @param {String} object String of the object to be loaded
* @param {Boolean} normalize If true, scale the model to a
* standardized size when loading
* @param {function(p5.Geometry)} [successCallback] Function to be called
* once the model is loaded. Will be passed
* the 3D model object.
* @param {function(Event)} [failureCallback] called with event error if
* the model fails to load.
* @param {String} [fileType] The file extension of the model
* (<code>.stl</code>, <code>.obj</code>).
* @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
*
* @example
* <div>
* <code>
* const octahedron_model = `
* v 0.000000E+00 0.000000E+00 40.0000
* v 22.5000 22.5000 0.000000E+00
* v 22.5000 -22.5000 0.000000E+00
* v -22.5000 -22.5000 0.000000E+00
* v -22.5000 22.5000 0.000000E+00
* v 0.000000E+00 0.000000E+00 -40.0000
* f 1 2 3
* f 1 3 4
* f 1 4 5
* f 1 5 2
* f 6 5 4
* f 6 4 3
* f 6 3 2
* f 6 2 1
* f 6 1 5
* `;
* //draw a spinning octahedron
* let octahedron;
*
* function preload() {
* octahedron = createModel(octahedron_model);
* }
*
* function setup() {
* createCanvas(100, 100, WEBGL);
* describe('Vertically rotating 3-d octahedron.');
* }
*
* function draw() {
* background(200);
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* model(octahedron);
*}
* </code>
* </div>
*/
/**
* @method createModel
* @param {String} modelString
* @param {function(p5.Geometry)} [successCallback]
* @param {function(Event)} [failureCallback]
* @param {String} [fileType]
* @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
*/
/**
* @method createModel
* @param {String} modelString
* @param {Object} [options]
* @param {function(p5.Geometry)} [options.successCallback]
* @param {function(Event)} [options.failureCallback]
* @param {String} [options.fileType]
* @param {boolean} [options.normalize]
* @param {boolean} [options.flipU]
* @param {boolean} [options.flipV]
* @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
*/
p5.prototype.createModel = function(modelString, options) {
p5._validateParameters('loadModel', arguments);
let normalize= false;
let successCallback;
let failureCallback;
let flipU = false;
let flipV = false;
let fileType = 'obj';
if (options && typeof options === 'object') {
normalize = options.normalize || false;
successCallback = options.successCallback;
failureCallback = options.failureCallback;
fileType = options.fileType || fileType;
flipU = options.flipU || false;
flipV = options.flipV || false;
} else if (typeof options === 'boolean') {
normalize = options;
successCallback = arguments[2];
failureCallback = arguments[3];
if (typeof arguments[4] !== 'undefined') {
fileType = arguments[4];
}
} else {
successCallback = typeof arguments[1] === 'function' ? arguments[1] : undefined;
failureCallback = arguments[2];
if (typeof arguments[3] !== 'undefined') {
fileType = arguments[3];
}
}
const model = new p5.Geometry();
model.gid = `octa.obj|${normalize}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might lead to conflicting IDs if you make more than one. Can we maybe make a global counter that increments each time this is called so that every object gets a unique gid?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah definitely something I missed, took your suggestion and added the global counter. Set up the gid as model.gid = `${fileType}|${normalize}|${modelCounter++}`;, let me know if you think this is clear enough.


if (fileType.match('stl')) {
try {
let uint8array = new TextEncoder().encode(modelString);
let arrayBuffer = uint8array.buffer;
parseSTL(model, arrayBuffer);
if (normalize) {
model.normalize();
}

if (flipU) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these steps get called regardless of the type of the model. Maybe we can make the if statement by inside the try so that we can call flipU(), flipV(), and the success callback in one spot instead of duplicating the code?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to follow the convention of loadModel(), where they also call those statements twice, but I agree that it's cleaner if it's only called once at the end.

model.flipU();
}

if (flipV) {
model.flipV();
}

if (typeof successCallback === 'function') {
successCallback(model);
}
} catch (error) {
if (failureCallback) {
failureCallback(error);
} else {
p5._friendlyError('Error during parsing: ' + error.message);
}
return;
}
} else if (fileType.match('obj')) {
try {
const lines = modelString.split('\n');
parseObj(model, lines);

if (normalize) {
model.normalize();
}

if (flipU) {
model.flipU();
}

if (flipV) {
model.flipV();
}
if (typeof successCallback === 'function') {
successCallback(model);
}
} catch (error) {
if (failureCallback) {
failureCallback(error);
} else {
p5._friendlyError('Error during parsing: ' + error.message);
}
return;
}
} else {
p5._friendlyFileLoadError(3, modelString);
if (failureCallback) {
failureCallback();
} else {
p5._friendlyError(
'Sorry, the file type is invalid. Only OBJ and STL files are supported.'
);
}
}
return model;
};
/**
* Load a 3d model from an OBJ or STL file.
*
Expand Down