MoI discussion forum
MoI discussion forum

Full Version: Loft factory question

Show messages: All  1-20

From: Martin (MARTIN3D)
20 Oct 2012   [#1]
I don't get "lofting" by script to work. I try to put a loft between two ellipses. Manually it's so simple. Do I need a orientations list and if yes, how is this used?



code:
var list = moi.createList();
list.add( moi.vectorMath.createPoint( 0, 0, 0 ) );
list.add( moi.vectorMath.createPoint( 0, 1, 0 ) );
factory = moi.command.createFactory( 'loft' );
factory.setInput( 0, moi.geometryDatabase.getSelectedObjects() ); //ObjectList
//factory.setInput( 1, list ); //Orientations List ???
factory.setInput( 2, 'straight' ); //Loft style
factory.setInput( 3, true ); // Cap ends
//factory.setInput( 4, false ); // closed ????
factory.setInput( 5, 'Exact' ); // Profiles
factory.commit;

Image Attachments:
loft.png 


From: bemfarmer
20 Oct 2012   [#2] In reply to [#1]
Hi Martin.
I do not know the answer offhand.

You can copy the two loft.js and loft.htm files from the Moi command folder, (to a practice directory,) and examine them with
Notepad++. I do this quite a lot with other commands, to see how Michael does this magic programming.

(I recall one of the arc commands could not be scripted, due to the selection mechanism not being scriptable, yet.)
From: bemfarmer
20 Oct 2012   [#3]
Your objectlist needs to be the two ellipses, which could be done by .calculate.
From: bemfarmer
20 Oct 2012   [#4]
var ellipse1 = ellipse1factory.calculate();
var ellipse2 = ellipse2factory.calculate();
ellipse1factory cancel;
ellipse2factory cancel;

Use javascript concat() method to combine the two object lists.

(did some of this in propdesigngeo script...)

(Or ignore my scribblings and wait for Michaels answer.) :-)
From: bemfarmer
20 Oct 2012   [#5]
See EditOrientations.js, re List. Which I do not understand.
From: Michael Gibson
20 Oct 2012   [#6] In reply to [#1]
Hi Martin, your loft code has a typo on the very last line, the one I've marked here with a <<<<

code:
var list = moi.createList();
list.add( moi.vectorMath.createPoint( 0, 0, 0 ) );
list.add( moi.vectorMath.createPoint( 0, 1, 0 ) );
factory = moi.command.createFactory( 'loft' );
factory.setInput( 0, moi.geometryDatabase.getSelectedObjects() ); //ObjectList
//factory.setInput( 1, list ); //Orientations List ???
factory.setInput( 2, 'straight' ); //Loft style
factory.setInput( 3, true ); // Cap ends
//factory.setInput( 4, false ); // closed ????
factory.setInput( 5, 'Exact' ); // Profiles
factory.commit;    <<<<<<<<<<<<<<<<<<<<<<<< bug here


The bug is that you need to be executing the commit function, you're missing the parenthesis on it () which is needed for JavaScript to know that you want to invoke that property name as a function call. So the least line should read factory.commit();

This is a bit of a tricky area in JavaScript, because functions can be referred to by name as well in which case they're treated as property values, so what you have written there does not happen to be a syntax error, it just doesn't do anything, you need the () to be there to make it do what you wanted.

The orientation list can be left out - if it's missing a default one will be created. The orientation list is not just a list of points, it holds one IMoiCurveOrientation object for each input curve, and that orientation object has a "flipped" property and a "seam" value, which store whether the loft should use the curve reversed from its natural direction and what spot it should use as the start/end location on a closed curve for where to relocate its seam point, the seam is stored as a normalized parameter value with 0.0 being the start of the curve and 1.0 as the end of the curve.

If you leave out the orientation data, it will do the default calculation to try and figure out a whether it should flip and relocate the seam to a good spot to reduce twisting of the generated loft. If the user specifically adjusts the twist or flips which they can do at the last prompt in the Loft command, it updates those values. But for normal use unless you want to have the auto-twist-minimization mechanism specifically turned off you could just leave that to be blank and let it get calculated automatically.

- Michael
From: Michael Gibson
20 Oct 2012   [#7] In reply to [#5]
Hi Brian,

> See EditOrientations.js, re List. Which I do not understand.

EditOrientations.js is used in the last stage of the Loft command where in addition to changing various settings in the command options you can also drag the "seam" points and also click on a loft curve inside the viewport to alter how the loft handles the connections between each profile.

That mechanism that displays the seam locations and responds to clicks is the "orientation editor" component, it's kind of similar to the point picker or object picker, it just has this specific job to deal with seam locations and flipping curves.

You can just leave out anything having to do with the orientation when scripting the Loft command and it will do some calculations internally to initialize them to a value that tries to minimize the amount of twist in the generated surface.

- Michael
From: bemfarmer
20 Oct 2012   [#8] In reply to [#7]
Thank you Michael.
Still learning a lot :-)
Did a simple loft (interactively) of two ellipses, and now see that clicking on one ellipse
causes a change in the loft.


Image Attachments:
SquishedLoft.PNG 


From: Michael Gibson
20 Oct 2012   [#9] In reply to [#8]
Hi Brian, yeah there could possibly be some situation (particularly if you have kind of sparsely distributed sections all at different angles to one another), where the automatic orientation might not be correct, so that's why you are able to adjust it manually so there's some way to fix it if it didn't do it properly.

Basically clicking on one section will reverse the direction of that curve in the loft.

Also at that same stage if you look in the viewport you should also see 2 points in there as well, those are marking the "seam" locations of where the closed curves will connect up to one another, you can click down and drag the mouse on one of those points to adjust the seam location, either to fix it if the auto untwister did not do a proper job, or also you can introduce twist by changing that seam location as well.

Usually there should not be any need to mess with those adjustments since usually the automatic "twist minimization" is pretty good.

- Michael
From: bemfarmer
20 Oct 2012   [#10]
One more post for tonight:

Added the double braces to Martins script, and saved it in the command folder, with shortcut Alt+5.

Note: Loft does not work with points. (?)

Anyway, I drew two vertical circles in Moi, highlighted them, and hit Alt+5, and Voila, Martins script did a loft between them.

So the script needs the input curves... IMHO

Edit again: But it is less able than the official loft. (Try 3 circles in different order.)
From: Michael Gibson
20 Oct 2012   [#11] In reply to [#10]
Hi Brian,

> Note: Loft does not work with points. (?)

Yes, that's correct - someday I'd like to allow for points, but they'd have to go just at the start or end.


> Edit again: But it is less able than the official loft. (Try 3 circles in different order.)

The regular loft command calls moi.geometryDatabase.sortCurves( curves ) on line 18 - that where all of the automatic ordering logic comes into play, like handling a bunch of window selected curves on parallel planes by putting them in the plane stacking order. A different script should be able to call the same sort function if it wants to get the same behavior as the regular loft command too.

- Michael
From: Martin (MARTIN3D)
21 Oct 2012   [#12]
Good morning. Lots of activity while I was asleep. Thank you.

Thanks Brian, for your help. I do examine the command files and even looked into EditOrientations.js but like you didn't understand. Fortunately in my case the script can stay simple without the need for a list. I like simple and compared to your scripts mine are humble :-)

Thanks Michael, of course it must be .commit()! I used it a hundred times before but that comes from typing from memory.


What I don't understand is the difference between factory.update() and factory.calculate().

Is the following correct?
factory.commit() calculates the result and puts it into the scene (geometryDatabase).
factory.update() just calculates the result and (with the exception of asynchronous factories like sweep or boolean) it can be assigned to a variable using factory.getCreatedObjects();
factory.calculate() also just calculates the result and it can be directly assigned to a variable.

Why can't I just write var x = factory.commit();?
Why is there an .update() and a .calculate() method when it looks like they do the same?

I hope it's not too complicated to explain.
From: bemfarmer
21 Oct 2012   [#13] In reply to [#12]
Here is an old post with some info:
http://50.56.177.237/forum/index.php?webtag=MOI&msg=3541.1
I only vaguely grasp it, more or less. :-)
From: Martin (MARTIN3D)
21 Oct 2012   [#14] In reply to [#13]
Thanks Brian, thats an interesting read that should answer my question. But I fear I also have to read it many, many more times to get the most out of it. Very funny is the comment by Burrman in post 7 :-)
From: Michael Gibson
21 Oct 2012   [#15] In reply to [#12]
Hi Martin,

quote:

Is the following correct?
factory.commit() calculates the result and puts it into the scene (geometryDatabase).
factory.update() just calculates the result and (with the exception of asynchronous factories like sweep or boolean) it can be assigned to a variable using factory.getCreatedObjects();
factory.calculate() also just calculates the result and it can be directly assigned to a variable.

Why can't I just write var x = factory.commit();?
Why is there an .update() and a .calculate() method when it looks like they do the same?


So it will probably make more sense if you consider that the entire factory mechanism is primarily designed to be used in an interactive command and not so much for use with custom scripting like you're doing.

The way a factory works is that you fill in the inputs, then when the inputs change you can either call .update() directly on the factory or there are also various binding mechanisms that binds a changing value to the input (like a point picker can bind its result to a factory input to push the current point the pointpicker is tracking onto the input) and those binding based methods usually automatically call factory.update() as part of its mechanisms so you don't have to call it manually in those cases.


> factory.update() just calculates the result and (with the exception of asynchronous factories like sweep or boolean)
> it can be assigned to a variable using

This part is not quite right - update calculates the result and also puts the generated output into the geometry database and deletes input objects if it's the type of command that removes inputs as well. It does all that work because it's meant to show the current result of the factory's calculation in response to a changed UI control or changed pointpicker location or things like that, and in order to display any geometry involves not just doing an isolated calculation but also putting the new objects into the geometry database. It's all the contents of the geometry database that are displayed inside of viewports, so if you want something to show up on the screen it has to not just be internally calculated but also has to go onto the geometry database.

The way regular commands are set up you can usually interactively adjust many parameters and point locations and stuff like that and see the result on the screen update, so normally there are many calls to factory.update() through this interactive part of a command.

Then usually a command can either be finished by pushing Done or it can be canceled. The way the factory mechanism works is that if you push cancel any active factory that has been created during the command will have any of its changes reverted - any generated objects will be removed from the geometry database and the original objects restored to be visible again if it was a command that removed existing objects. In order to not have that canceling mechanism be triggered you must call factory.commit() - that tells the command factory mechanism that it should consider that factory to be successfully and fully completed by the user pushing "Done" and finishing the command instead of being canceled in any way (there are various ways that things can get canceled, like with a new command being started, or the program exiting, not only just from "Cancel" being pushed).

When .commit() is called it also checks to see if there has been any previous .update() ever called and if there has not it will automatically call update() itself. It's only actually in .update() that things are actually calculated.


So anyway this whole update/cancel/commit mechanism is set up to be used in this way mostly with a command using one single factory for the command.

You're using it in a pretty different way from that, wanting to chain the results of several different factories together instead of just using one factory that was tailor-made for the command.

I've tried to add in a .calculate() method in order to help with the kind of use that you're doing - when you call .calculate() on a factory, it only creates the output objects and does not try to remove or put anything in the geometry database at all, unlike update(). So while .update() is primarily a method that gives visual results (it basically updates the screen to reflect the new factory input values), .calculate() does not do anything visual at all and only generates geometry that could then be used for other purposes in the script like intermediate calculations.

At one point I had been hoping to just have one single script mechanism for geometry creation and to be able to use the factory method for both interactive commands (with .update()/.commit()/.cancel() ) as well as using the same infrastructure for custom scripting like you're trying to do. But I think that it's not a great fit for both methods, I think that instead I'm going to implement some more direct functions in the script API to generate objects, stuff like moi.geometryDatabase.createLine( from, to ), and things like that rather than only have everything go through the factory mechanism only. But like a lot of feature areas I simply have not had enough time available yet to focus on making that new API layer. APIs are not great areas to slap together quickly because once people start using them it becomes quite difficult to make any changes in them which break existing scripts. Things that require extra care to do right tend to be time consuming and with time in short supply it can be difficult to implement time consuming areas of work...

- Michael
From: Martin (MARTIN3D)
22 Oct 2012   [#16] In reply to [#15]
Hi Michael,

I'm actually quite happy with the way scripting MoI works right now. When I use it in the same way as I would do things manually i.e. just use .commit() and a .selectAll() / .invertSelection() / deselectAll() combo to put things into the next factory I already can do anything I want. It just requires careful planning and keeping track of whats selected and whats not.

If in the next beta the .update() / getCreatedObjects() / .cancel() combo works for sweeps and booleans using variables will make scripting even easier because I can use object.selected = true; or objectlist.setProperty('selected', true);


And if its just for easier coding I can already have that with an external Javascript file.
Your line example could already be as simple as

code:
#include "scriptLibrary.js"
drawLine( pt(0,0,0), pt(10,10,0) );


by using an external scriptLibrary.js file:
code:
function pt(x, y, z) {
	var pt = moi.vectorMath.createPoint( x, y, z );
	return pt;
}

function drawLine(pt1, pt2) {
	var factory = moi.command.createFactory( "line" );
	factory.setInput( 0, pt1 );
	factory.setInput( 1, pt2 );
	factory.commit();
}


or a circle:
code:
function pt(x, y, z) {
	var pt = moi.vectorMath.createPoint( x, y, z );
	return pt;
}

function drawCircle(orientation, center, radius) {
	switch (orientation) {
	case "Top":
		var frame = moi.vectorMath.createTopFrame();
		break;
	case "Front":
		var frame = moi.vectorMath.createFrontFrame();
		break;
	case "Right":
		var frame = moi.vectorMath.createRightFrame();
		break;
}
	
	frame.origin = center;
	var factory = moi.command.createFactory( 'circle' );
	factory.setInput( 1, frame );
	factory.setInput( 3, radius );
	factory.commit();
}

drawCircle("Front", pt(0,0,0), 100);


I can see many factories simplyfied like this and not neccessarily a need for an additional simplyfied scripting layer that would break backwards compatibility.
From: bemfarmer
4 Feb 2018   [#17] In reply to [#6]
Hi Michael,
I'd forgotten about this old subject, but have a question.

Rather than dragging the marker point of the seam, shown during Loft, is it possible to just enter a point, or coordinates or object list of a previously picked
point on the curve for, say, the orientations list input of Loft, or say the EditOrientations.js?

I'm trying to script changing the start point of an ellipse, using the loft method that has been previously posted.
http://moi3d.com/forum/index.php?webtag=MOI

- Brian
From: Michael Gibson
4 Feb 2018   [#18] In reply to [#17]
Hi Brian,

> Rather than dragging the marker point of the seam, shown during Loft, is it possible to just enter
> a point, or coordinates or object list of a previously picked
> point on the curve for, say, the orientations list input of Loft, or say the EditOrientations.js?

So you mean in your own script that you're making, not typing in coordinates in the regular Loft command, right?

I'm not sure if that's possible currently - one thing you can try though is if you run .update() on the Loft factory, the curve orientation input (input index 1 on the loft factory) should get filled in with a default orientation list. It should then be possible to modify that list, you can get it I think by factory.getInput(1).getValue(); that will be a list object, each item in the list is a CurveOrientation object which has 2 properties: flipped and seam which can be modified.

The seam point though is a normalized parameter value, with 0.0 meaning the start of the original curve and 1.0 meaning the end of the curve, and 0.5 meaning the middle of the curve's parameter space, not necessarily the middle of the arc length distance of the curve.

- Michael
From: bemfarmer
4 Feb 2018   [#19] In reply to [#18]
Thank you Michael.

I'll do some tests.

I guess that the "curve's parameter space" has to do with the NURBS representation?

- Brian

Using cPlane at the centerpoint, I'm intersecting the "waist" of an extruded (or lofted) pair of circles, (an oblique cylinder), with
a plane, which is a "belt" ellipse. (Like a person with a belt around the waist.)
The ellipse formed often has a different start point than the starting cylinder. I'm trying to move the start point to match the cylinder seam.
It is easy interactively, with a specific cylinder...
From: Michael Gibson
4 Feb 2018   [#20] In reply to [#19]
Hi Brian,

> I guess that the "curve's parameter space" has to do with the NURBS representation?

Yup, there is a range of numbers called the "knot vector" which sets a minimum and maximum value for the parameter space range. Evaluating the curve at the start parameter yields the start point of the curve, evaluating the curve at the end parameter gives the end point of the curve.

- Michael

Show messages: All  1-20