Lscript Question

Kryslin

Active member
I'd like to thank those who gave me some ideas for this... Here's the current result:

Nearly all the fibers are now pointing the right way now. Instead of using a single method to compute the combing vector, I compute it two different ways, and use the result that has the smaller dot product (the closer to perpendicular they get, the smaller the dot product) with the vertex normal. I also check to see if the result has a great Y value than vertex + vertex normal - if it does, I swap the two before computing the perpendicular.

The next set of tests will involve a different mesh in a different orientation. It will introduce the requester for which surface to comb away from.
 

Kryslin

Active member
lScript question:

I'm trying to use SelectByName(Name) to select items in my scene. However, it is returning the wrong results!

Ie: Selecting Lft_Leg1_IKG_mel by name returns the expected Object Agent; selecting Lft_IKRevFoot1_mel by name returns an incorrect match - Lft_IKRevFoot1_mel_MARKER.

It is as if 2 commands - SelectByName() and SelectByPartialName() return the same results.

Is this a bug that should be reported? It kind of puts a damper on my experiment, but I think I can code a work around...
 

Kryslin

Active member
I figured out what was going on... It's the 1000 item limit. When using a scene that had less than 1000 items in it,SelectByName and SelectByPartialName work as expected... Over 1000 items, erroneous results.

Great. Time to learn how to load all the scene item ID's, name and type into an arrays. Not as easy as it seems, because the top level items are not children of the scene...
 

jeric_synergy

Axes grinder- Dongle #99
Thanks for taking the arrows in the back, Kryslin. If you could mention this to whoever is supporting the LScript documentation (BeeVee??), that'd probably save some other coder a headache.
 

ernpchan

Active member
I figured out what was going on... It's the 1000 item limit. When using a scene that had less than 1000 items in it,SelectByName and SelectByPartialName work as expected... Over 1000 items, erroneous results.

Great. Time to learn how to load all the scene item ID's, name and type into an arrays. Not as easy as it seems, because the top level items are not children of the scene...

You should see if python has this same limitation.
 

Kryslin

Active member
I suspect it does, because it's a limit in the SDK itself. I remember this from beta testing Rhiggit2 - this limitation was the cause of many, many weird problems.
(The UI can generate selections > 1K items, but you can't using scripting or the SDK...)

For my experiment, grabbing everything under the "GLOBAL_ROOT_(charname)" null should work. I don't need lights, or the ground plane, etc.

visitnodes() works wonderfully now. Last time I tried using it (9.5 or so...), it crashed the OS.
 
Last edited:

Sensei

TrueArt Support
LWSDK does not have "SelectByName" *function*.
It's Layout *command*.
You can enter it manually.
Go to Utilities
press Command Input,
and try SelectByName [name]
And see whether it has the same issue as with LScript version of it.

You can also check commands using above Cmd History.

Then make another test:

execute command by
CommandInput( "SelectByName [name]" );
or so

Maybe names are too long (too short internal string buffer)?
 
Last edited:

Kryslin

Active member
I get the same result - it selects the wrong item, on both tries.

Here's what came up in the Rhiggit2 beta testing cycle...
RebelHill said:
In fairness, they're pretty good at responding to bugs and problems... at least when those are problems of the code base rather than the "system"... And especially so if you include the sentence... "Ahhhhh wtfffff??? Help meeeee!!!!!" in your correspondence.

So here's the news...

This isnt a "bug", but part of a hard coded limit underpinning the whole SDK... whilst items selection in the visual interface over 1K are supported... those in ANY code interface are not... that obv makes it a change, rather than a fix. Therefore... a "fix" will not be forthcoming, and whilst a change could be, due to the potential impact that could have (for instance with existing plugins)... that;s something that'd have to be worked through much more carefully... which is quite understandable.

So... for the immediate future, that leaves me only the option of trying to code out a different system for performing "massive" item selection in code. As I say, I *think* I might have a solution, so Im working it in now... Tbh, if it works out, then its not something that's likely to get broken if the mentioned change were to get made... so it should keep the plugin future proof anyway.

As usual... Im trying to do more with LW than it was designed for... typical me.

So here's hoping.

As I said, I've got my work aropund in place; Simply put - I load what I want into a large array in lscript, using visitnodes() to do so. I can use indexOf and regular expressions to duplicate any select() functionality I need.
 
Last edited:

Kryslin

Active member
Ok, another question:

I'm working on a bias combing tool that take a bias map information, generates a 2 pt poly which represents the bias vector. Now, I've managed to add a vertex map so that one end of the 2 pt poly is selectable (for use in the weight map fall off in some tools), but what I need to do is some how embed the point ID of the point the bias map information came from, so it can be transferred back, with another tool.

The problem I'm running into is this : While the debugger shows a point id as a hexadecimal number (0x0BC14CA0, for example), it isn't treated as a number - math operations return a 0, trying to assign it as a weight map value crashes lScript. I can convert it to a string, which works, but the conversion back to a number returns 0. And it doesn't matter what the value of the point id is - the results are invariably the same.

Since I can convert it to a string, I figured I try to break it down into 4 1 byte hexadecimal digits, and assign those to a RGBA vertex map, using each byte group as a color component. The problem is, will this work in reverse - taking the values and reassembling them, giving me the original value of the point id?
 

Kryslin

Active member
Ok, another question...

I have an lscript that assigns weights symmetrically, based on a string match between bones and weight maps. Works wonderfully, even if you are doing a lot of clicking to get things done, and surprisingly, while not saving half the time, it does save about 1/3rd the time. So, I decided to improve it, by seeing if I could use buttons to move up and down the hierarchy of the bones. While the assign button works, using the parent and child buttons do not.

I know I'm not doing something right, but I'm not quite sure what.

I suspect it may be the type of lscript I'm using; It's written as a generic type.
Here's my code, I'd like to know what I'm not doing right...
Code:
@warnings
@script generic
@name SymWeightAssign3

//Globals for UI Helper function
var GRIDSIZE = 24;
var SKEW = floor(GRIDSIZE / 2);
var OFFSET = floor(SKEW / 2);
var nullflag = true;	
var LW_CONTROL = 0;
var LW_MAIN = 1;
var LW_SEPARATOR = 2;

var c1..8;
var wMapList = nil;
var wMapNames = nil;
var objlist = nil;
var wMap = VMap(VMWEIGHT);

var rtbn, rtwt, ltbn, ltwt;
var objcnt;

generic{
	
	//This script symmetrically assigns wt map names to selected
	//Bones. , based on User set tags for right and left on bones
	//and weight maps.
	
	//Variables
	var a[4];

	//Get Weight Maps
	while(wMap){
        if(wMap.type == VMWEIGHT){
            wMapList += wMap;
            wMapNames += wMap.name;
        }
        wMap = wMap.next();
    }
	
	//Let's see what the user has selected...
	var objs = Scene().getSelect();
	
	//If it isn't a bone, then we'll need to error out...
	for(i=1; i<= objs.count();i++){
		if(objs[i].genus == 4) objlist += objs[i];
	}
	
	if(!objlist) error("No bones selected to work on!");
	objcnt = objlist.count();
	
	//Now that we've got a list of our bones, let's open our interface\
	//and do some work...
	
	//Initialize Requester
	//recall what got entered so the user doesn't have to type in things again...
	//or assign it a default value if there is no entry...
	rtwt = recall("smp_SWA_rtwt","_RT");
    ltwt = recall("smp_SWA_ltwt","_LT");
    ltbn = recall("smp_SWA_ltbn","Lft_");
    rtbn = recall("smp_SWA_rtbn","Rgt_");
    wtidx = 1;
	
	reqbegin("Sym. Weight Assign"); a=gbox2(LW_MAIN,0,0,12,10);   reqsize(a[3],a[4]);
	
	t1 = ctltext("","Weight Map"); a = gbox2(LW_CONTROL, 0,0,5,1); ctlposition(t1,a[1],a[2],a[3],a[4]);
	c1 = ctlstring("Right",rtwt); a = gbox2(LW_CONTROL,0,1,5,1); ctlposition(c1,a[1],a[2],a[3],a[4]);
	c2 = ctlstring("Left",ltwt); a = gbox2(LW_CONTROL,0,2,5,1); ctlposition(c2,a[1],a[2],a[3],a[4]);
	
	t2 = ctltext("", "Bone Name"); a = gbox2(LW_CONTROL, 6,0,5,1); ctlposition(t2,a[1],a[2],a[3],a[4]);
	c3 = ctlstring("Right",rtbn); a= gbox2(LW_CONTROL,6,1,5,1); ctlposition(c3,a[1],a[2],a[3],a[4]);
	c4 = ctlstring("Left",ltbn); a = gbox2(LW_CONTROL,6,2,5,1); ctlposition(c4,a[1],a[2],a[3],a[4]);
	
	a = gbox2(LW_SEPARATOR,0,3,11,1); s1 = ctlsep(a[1],a[3]); ctlposition(s1,a[1],a[2],a[3],a[4]);
	
	c5 = ctlpopup("Weight Map",wtidx,wMapNames); a= gbox2(LW_CONTROL, 0,4,11,1); ctlposition(c5,a[1],a[2],a[3],a[4]);
	
	a = gbox2(LW_SEPARATOR,0,5,11,1); s2 = ctlsep(a[1],a[3]); ctlposition(s2,a[1],a[2],a[3],a[4]);
	
	a = gbox2(LW_CONTROL,0,6,3,1); c6 = ctlbutton("Parent",a[3],"parentbutton");ctlposition(c6,a[1],a[2],a[3],a[4]);
	a = gbox2(LW_CONTROL,4,6,3,1); c7 = ctlbutton("Assign",a[3],"assignbutton");ctlposition(c7,a[1],a[2],a[3],a[4]);
	a = gbox2(LW_CONTROL,8,6,3,1); c8 = ctlbutton("Child ",a[3],"childbutton");ctlposition(c8,a[1],a[2],a[3],a[4]);
	
	return if !reqpost();
//-->	
	rtwt = getvalue(c1);
	ltwt = getvalue(c2);
	rtbn = getvalue(c3);
	ltbn = getvalue(c4);
	wtidx = getvalue(c5);
//<--	
	reqend();
/

	//Store what got entered so the user doesn't have to type in things again...
	store("smp_SWA_rtwt",rtwt);
	store("smp_SWA_ltwt",ltwt);
	store("smp_SWA_ltbn",ltbn);
	store("smp_SWA_rtbn",rtbn);
}

gbox2 : w_mode, w_x, w_y, w_h, w_w
{
	//w_mode 0 = normal, 1 = main box, 2 = Separator
	//All values are GRID coordinates
	//w_x, w_y = (x,y) coordinates of control;
	//w_h, w_w = (height, width) of control;
	
	//Return Values array
	var retv[4];
	
	//Convert from Grid coordinates to screen coordinates
	retv[1] = (w_x * GRIDSIZE) + OFFSET;
	retv[2] = (w_y * GRIDSIZE) + OFFSET;
	retv[3] = (w_h * GRIDSIZE);
	retv[4] = (w_w * GRIDSIZE);
	
	//Mode Handling
	if (w_mode == 1){
		retv[3] += SKEW;
		retv[4] += OFFSET;
	}
	if (w_mode == 2) retv[2] += SKEW;
	
	return(retv);
}

dorefreshstuff : q_obj
{
	obj_list = nil;
	objlist += q_obj;
	objcnt = 1;
	SelectByName(objlist[1].name);
	RefreshNow();
}

parentbutton
{
	tmp_obj = objlist[1];
	if(tmp_obj.parent) tmp_obj = tmp_obj.parent;
	dorefreshstuff(tmp_obj);
}

assignbutton
{
//-->
	rtwt = getvalue(c1);
	ltwt = getvalue(c2);
	rtbn = getvalue(c3);
	ltbn = getvalue(c4);
	wtidx = getvalue(c5);
	//debug();
	
	//Ok, we've got our info from the user, let's add some wt. maps to bones...
	for(i=1; i<=objcnt;i++){
		//Symmetry flags
		var mode1 = 0;	// if this is true, the bone has a match for symmetry
		var mode2 = 0;  // If this is true, the wt. map has a match for symmetry.
		
		var bone1 = objlist[i].name;
		var bone2 = bone1;
		var wmap1 = wMapNames[wtidx];
		var wmap2 = wmap1;
		
		var expr1 = regexp(rtwt);
		var expr2 = regexp(ltwt);
		var expr3 = regexp(rtbn);
		var expr4 = regexp(ltbn);
		var repl3;
		var repl4;
		
		//first, check for symmetric bones... and set mode1 if they exist.
		if(bone1 == expr3){
			mode1 = 1;
			repl3 = regexp(rtbn,ltbn);	
		}
		if(bone1 == expr4){
			mode1 = 1;
			repl3 = regexp(ltbn,rtbn);

		}
		//likewise, check for symmetric wt maps, and set mode2 if they exist.
		if(wmap1 == expr1){
			mode2 = 1;
			repl4 = regexp(rtwt,ltwt);
		}
		if(wmap1 == expr2){
			mode2 = 1;
			repl4 = regexp(ltwt,rtwt);
		}
		
		//Now, let's assign weights based on modes
		modenum = (2 * mode2) + mode1;
		
		switch(modenum)
		{
			case 1:
				//Symmetric Bones, but no symmetric weights.
				bone2 ~= repl3;
				//Set Weight Maps on symm. bones...
				SelectByName(bone1);
				BoneWeightMapName(wmap1);
				SelectByName(bone2);
				BoneWeightMapName(wmap2);
				break;
				
			case 3:
				//We have symmetric bones and weights!
				//If Symmetry exists, generate names for Symm. Bones and Weights
				bone2 ~= repl3;
				wmap2 ~= repl4;
				//Set Weight Maps on symm. bones...
				SelectByName(bone1);
				BoneWeightMapName(wmap1);
				SelectByName(bone2);
				BoneWeightMapName(wmap2);
				break;
			
			default:
				SelectByName(bone1);
				BoneWeightMapName(wmap1);
				break;
		}
	}
	//Let's restore the selected objects from the start...
	for(i=1; i<=objcnt;i++){
		if(i==1){
			SelectByName(objlist[i].name);
		} else {
			AddToSelection(objlist[i].name);
		}
	} 
//<--
}

childbutton
{
	tmp_obj = objlist[1];
	tmp_obj = tmp_obj.firstChild();
	if(!tmp_obj) tmp_obj = tmp_obj.nextChild();
	if(!tmp_obj) tmp_obj = objlist[1];
	dorefreshstuff(tmp_obj);
}
 

Kryslin

Active member
Well, I've discovered how to crash layout.

It appears you can't have a ctl* call outside of a reqbegin()/reqend() pair.

I wrote a UDF which took all the parameters for a control (type, data, index, optionals, position and size) and tried to return the control object agent via a UDF's return, and called it in between a reqbegin() and reqend() pair. Crashes, straight to error reporting. It's not even initializing; with the first statement being a debug(), it won't even make it that far.

The idea was to take this:
t1 = ctltext("","Weight Map"); a = gbox2(LW_CONTROL, 0,0,5,1); ctlposition(t1,a[1],a[2],a[3],a[4]);
and make it into this...
t1 = addcontrol(lw_text,"","Weight Map",0,0,5,1,,,,);
 

Kryslin

Active member
I've kinda got a handle on Sym. Weight Assign 3's issue of not advancing down the tree properly (up is no problem); The problem is that most tree walking routines are recursive, and I don't want that in a button call back... So, I'm going to try using visitnodes to load the entire hierarchy, in order from main_root down to the ends, and then find the index of the selected member, and move up and down the list. It should work predictably, but the way things are going around here, who knows...
 

Kryslin

Active member
More lScript questions...

I've got a neat lscript - generates a bias map for use in FiberFX with similar results to Sasquatch's Surface Combing. However, it is a dog of a script, taking a long time to process through a (oft times) large point selection, generate vertex normal information, and then do a set of relatively simple calculations to generate another vector and either simply add them together, or interpolate between the two. What can I do to optimize something in lScript? I realize I'm probably going to have to post the critical loop in question, along with some of the functions, so...

Not that this is not the production code (which is slightly different, allowing for the creation of one or both vmaps, basically), but will serve to illustrate the problem.
Code:
undogroupbegin();

	tvc = editbegin();
		tpc = polygons.count();
				
		mymap = VMap(VMRGB, mapname, 3);
		mymapalt = VMap(VMRGB, mapname+"_rm",3);
		moninit(tpc,"Combing...");
		
		foreach(vtx,points){
			//Get the polygon infomation for the point...
			p = nil;
			p = vtx.polygon();
			//Trap points with no polygons, don't process them			
			if(p != nil) {
				pn = <0,0,0>;
				pc = p.count();
				foreach(f,p) {
					pn += polynormal(f);
				}
				pn = normalize(pn/pc);
		
				// check for down pointing hairs, lessen blend strength
				bs = 1 - clamp(dot3d(pn,<0,-1,0>),0,1);
				bs *= blend_strength;
				//Generate Map Values
				pt = pointinfo(vtx);
				
				bv = makebiasvector3(pt,pn,away_s,bs, mth_idx);

				if(ops_idx == 2)
					{
					if (mymap.isMapped(vtx)) 
						{
							tmpv = mymap.getValue(vtx);
							tmpw = <tmpv[1], tmpv[2], tmpv[3]>;
							tmpv = normalize(bv + tmpw);
							bv = tmpv;
						}
					}
				bv = normalize(bv);	
				mymap.setValue(vtx, bv);
				mymapalt.setValue(vtx,biasremap(bv));
			}
			monstep();
		}
		monend();
	editend();
	undogroupend();
	selpolygon(CLEAR);
//<--
}

makeperp : vector_p, vector_q
{
	var retv = <0,0,0>;
	var v_p = normalize(vector_p);
	var v_q = normalize(vector_q);
	//variation on perpendicular projection;
	//since v_q is normalized, ||v_q|| = 1, ||v_q||^2 = 1;
	//Let's see if my assumption is correct...
	retv = normalize(v_p - (dot3d(v_p,v_q) * v_q));
	return(retv);
}

makebiasvector3 :qV, qN, qB, qS, qM
{
	vn = qN;
	p1 = qV;
	p2 = qB;
	qs = qS;
	method = qM;
	//Let's Begin!
	//Generate a vector, from the brushing point to the current vertex
	bv = normalize(p1 - p2);
	
	//No more brushing vector pointing "inside" the normal vector
	tx1 = abs(bv.x);
	tx2 = abs(vn.x);
	if (tx1 < tx2) bv.x = vn.x;
	
	//Generate our perpendicular vector
	bv = normalize(bv);
	pv = makeperp(bv,vn);
	
	//Not so simple test to make sure that the perpendicular vector isn't
	//pointing in the wrong direction.  The dot product is there to keep the
	//flip from happening in the wrong areas of the model.
	if((p1.y + vn.y + pv.y) > (p1.y + vn.y) && (dot3d(vn.z,bv.z) > 0)) pv.y = -pv.y;
	pv = normalize(pv);
		
	//Generate our bias vector
	if (method == 1){
		//Method 1 : Simple Vector Addition.
		nv = normalize(vn + qs * pv);
	} else {
		//Method 2 : Interpolate between vertex normal and perpendicular
		tv = nlerp(vn,pv,qs);
		nv = normalize(tv); /* This normalize is unnecessary, line above should be nv = nlerp(vn, pv, qs); and this one deleted.
	}
	return(nv);
}

nlerp : qA, qB, qt
{
	//Normalized Linear Interpolation, keeps the interpolated vectors
	//all the same length while being interpolated.
	//from "Math for 3D Game Programming and Computer Graphics, 3rd Edition"
	//by Eric Lengyel
	var retv = <0,0,0>;
	var a = qA;
	var b = qB;
	var t = qt;
	var tmp1 = normalize((1-t)*a + t * b);
	retv = tmp1; 
	return(retv);
}

One way I know that will make this run much faster is to load everything into memory, in some (very) large arrays, and access those in the loop. The first, primitive versions of this script did exactly that, but I decided to switch to a more memory friendly approach. I can also see the opportunity to remove some redundant function calls (namely normalize() calls, I get kind of paranoid about keeping everything as normals).

Any suggestions? I've looked over some web pages for optimizing code, and I don't know how much of that I can apply to lScript...
 

Kryslin

Active member
Ok, Another question... Probably a stupid one, too.

Is there any way to get polygon ID's, outside of an editbegin(); polys = polygons; editend(ABORT); seqence?
 

Sensei

TrueArt Support
In LWSDK, you can get LWPolID when you will use scanPolys() after getting MeshInfo..
How to get MeshInfo depends on whether you're in Modeler or Layout.
Check LWSDK archive that you have on disk.

Python should allow this, because it exposes the all LW globals. Lscript I am not sure.
But it's very bad idea to get LWPolID and then store, reuse.. Because you won't be sure it's still valid.. And crashes will be inevitable.
 
Last edited:

Kryslin

Active member
Many Thanks, both Sensei and Denis.

The why I need to get the ID's is that I'm tackling the issue of Un-Subdividing a mesh one level; In this case, a Daz Studio import of some 64K Polygons (It's had one pass for smoothing, and my results thus far are promising). I was looking for ways to cut down on memory consuming calls, and I though I'd ask if there was a way to get the poly (and hopefully, point) ID's without resorting to an editbegin(); / editend(); pair... Adding ABORT to the editend(); call helps cut down the amount of memory in use dramatically, and appears to be working well enough for now.
 
Top Bottom