Problem scripting a 'trim' operation
 1-5  6-25  26-38

Previous
Next
 From:  Michael Gibson
3541.6 In reply to 3541.1 
Hi Dave, I'm not sure if I answered this question:

quote:
Finally, another question I have is whether factory methods can only work on 'committed' geometry, or can they also work on any valid geometry passed to them. I've been assuming they can work on any geometry, but would like to have that point clarified.


But I may need some clarification - which factory methods are you referring to here, do you mean a custom method that a particular factory implements like those special ones on Trim, or do you mean one of the regular methods common to all factories?

So one thing to note is that geometry itself does not really have a "committed" state, it's actually the factory that has that state.

When a factory is being used in an interactive command, the newly created geometry (created during a call to .update() ) is actually added to the geometry database as well, otherwise it would not show up on the screen. But when you do a factory.commit(), it then knows not to remove that geometry during the cleanup stages when a command exits, otherwise that geometry is removed if a factory is torn down before a commit() happened.

If you do a factory.calculate(), then that generates a list of output objects but does not process them in any of the other ways that happen during the normal interactive update/commit/cancel type stuff. That's so you could potentially use those objects for other intermediate calculations instead of forcing them to be displayed on the screen.

So the results of .calculate() are not added automatically to the geometry database. But you should be able to use them for inputs into other factories, however if you need them to be selected and show up in the geometry database you will need to add them to the geometry database yourself.


Geometry does have a flag for whether it is "in the database" or not in the database list, but that's a different thing than a factory's committed state.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  BurrMan
3541.7 In reply to 3541.6 
Well....THAT explains it!!! And I thought it was going to be something I didnt understand :o
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.8 In reply to 3541.2 
Michael,

Thanks for the detailed explanation of the factory execution model! I hope you don't mind if I use bits and pieces of your explanation in the API documentation I am writing?

For the most part, it sounds like the mental model I had developed in the course of reading through a lot of the MoI command scripts was more or less correct, but having it spelled out is really very helpful. The bit about the binders automatically calling 'update' helps clarify how the binders operate quite a bit, since deducing how they worked just from the Javascript code examples was quite a bit of guesswork on my part (although it turns out the actual function as you described it is nearly identical to the mechanism I use in the Python Traits framework I wrote that I'm using to do a lot of the external scripting of MoI with).

Now, just to nail this thing down a little better, let me summarize what I think I understand about MoI geometry and its life cycle:

- In order to be displayed on the screen, all geometry must be in the geometry database.
- It is possible to create, maintain and use geometry that is not displayed to the user (i.e. not in the geometry database).
- Factories can work with any geometry objects, whether or not they are in the geometry database.

Now, the big question: when/how are geometry objects destroyed?

Since C++ does not do automatic garbage collection, my guess is that you are using some form of reference counting, probably based upon the geometry objects being added to/removed from ObjectList objects. Based upon this assumption, adding geometry to the geometry database automatically keeps geometry alive, because it become part of one or more lists that the database manages. Sub-objects are kept alive by lists maintained by parent objects. Geometry that is not in the database is kept alive by being part of one or more ObjectLists maintained outside of the database.

Destruction of geometry not in the database can be accomplished by the removal of the geometry objects from all ObjectLists being maintained by a script (talking from a scripting perspective).

If you can confirm or clarify this description, I think it will go a long way towards helping me to understand the MoI internals better...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.9 In reply to 3541.3 
>Hi Dave, then also some factories have custom properties or methods exposed on them
>which the command script can call in order to get some value to put in the UI, or to trigger
>some particular stage of the command.

>As you noticed, Trim has a couple of these.

Yes, so I discovered from reading the Trim code yesterday :-) This caused me to refactor the way I was creating factory objects. Previously, there was just a single 'create' function, which took the factory type and all of the inputs to the factory as arguments. It would then create the factory, do a 'setInput' for each provided argument, then call 'factory.calculate()' and return the result.

Now I have added a separate 'factory' function (shown in the example code I posted previously) which simply returns the factory object without calling 'calculate'. This allows the caller to issue some of the 'special' methods (like 'generateFragments') first.

I do not believe the IDL file contains anything for these special methods (since they are not showing up in the documentation I am automatically generating). There only seems to be descriptions for the base GeometryFactory class. Does that mean the only way to discover them is to pore over the xxx.js files looking for them?

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.10 In reply to 3541.4 
> Trim could be a good example where you would probably want a different
> script interface to it rather than the current factory one which is so set up
> to cater to the way the interactive command works.

> When the interactive command works in some distinct stages like Trim does
> (with the fragment picking stage), that becomes somewhat awkward to replicate
> in procedural code.

> Other more typical commands where it's just a matter of filling out all the inputs
> tends to be a better direct fit.

Well, I certainly agree, which is part of the reason I have been trying to create a procedural interface to MoI which encapsulates the MoI API into a more procedural approach. For example, I have a 'line' function which takes two Point objects as input and returns the expected curve object as the result.

BTW, I have been taking the approach that all geometry creating functions return objects that are not part of the geometry database. If the caller wants the user to see them, then a separate function must be called to register them. This makes it simpler to create intermediate results used to generate other final geometry.

At this point I have not completely encapsulated every MoI factory type using this approach. In fact, I just wrote the 'trim' function yesterday, which is why this question just came up now.

I'm still not completely clear on using the 'trim' factory. If I just want to create a set of trim fragments from the set of objects and cutters, do I need to call 'generateFragments' after initializing the factory object (I assume so). This returns a boolean, which means the factory must be keeping the fragment list internally at that point. So which method will return me the fragment list?

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.11 In reply to 3541.5 
> Hi Dave, so I looked at the .generateFragments() method on the trim factory,
> and it should only return E_POINTER (which translates to a message of "Invalid pointer")
> if it was given a NULL pointer for it to write its return value out to.

> It wants to return a boolean true/false value for whether any fragments were generated.

> Is it possible that the way you are calling it is not providing the pointer for a return value?

> This would be the pointer named "pVarResult" in IDispatch::Invoke()
> http://msdn.microsoft.com/en-us/library/aa912367.aspx

> But if you are calling it through regular JavaScript, the whole JavaScript system is supposed
> to be providing that pointer for the return value to be written to.

Eureka! That was the problem...thank you so much for continuing to think about this!

If you noticed in the code I posted yesterday, I had written:

factory.generateFragments();

instead of something like:

var result = factory.generateFragments();

Apparently the Javascript API to the COM interface is actually sensitive to that, and was not providing anything useful for the 'pVarResult' value in the case where the result was not being consumed. Once I added code to do something with the result, the problem went away (although I am still having problems get useful results from the 'trim' factory).

That's a huge issue to be aware of! Thanks again for sticking with me on this problem...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.12 In reply to 3541.6 
> Hi Dave, I'm not sure if I answered this question:

> But I may need some clarification - which factory methods...

> ...Geometry does have a flag for whether it is "in the database" or not
> in the database list, but that's a different thing than a factory's committed state.

Thanks again for continuing to clarify this. I think a clearer picture of how the factories work is starting to emerge :-)

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.13 In reply to 3541.8 
Hi Dave,

> I hope you don't mind if I use bits and pieces of your
> explanation in the API documentation I am writing?

Sure, feel free to use it!


> The bit about the binders automatically calling 'update'
> helps clarify how the binders operate quite a bit <...>

Also I should mention that there are a few of varieties of binders, there is one that goes on HTML elements in the UI, which are set up with a bindingx="" attribute.

Then there's one that can be set up with moi.ui.bindUIToInput(), which hooks up a UI control to a factory input, and handles copying the control's value to the input and calling update() on the factory when the control's value has changed.

Also the point picker has its own binding methods where you can tell it to connect to a factory input, and when the pointpicker has a new point from a mouse move, it sends the point (or the point + cplane frame, or just the cplane frame) to the factory input and calls update().

quote:

- In order to be displayed on the screen, all geometry must be in the geometry database.
- It is possible to create, maintain and use geometry that is not displayed to the user (i.e. not in the geometry database).
- Factories can work with any geometry objects, whether or not they are in the geometry database.

Yup, that's all correct!

Or should be anyway - the last one should be correct but let me know if you run into instances with a particular command where it does not seem to work properly. One thing is that objects do not get their ID guid assigned to them until they are added into the geometry database. So it could be possible that something is sensitive to that, but it is not supposed to be.


> Now, the big question: when/how are geometry objects destroyed?

They are reference counted just like you suspected.

So you never really explicitly destroy it, but if you remove it from any list that was holding a reference to it, your script will then hold the last reference and the object will be destroyed when your script itself gets garbage collected or terminated.


> Based upon this assumption, adding geometry to the geometry
> database automatically keeps geometry alive, because it become
> part of one or more lists that the database manages.

Yup, that's correct - the geometry database list holds a reference count open on the objects in it.


> Sub-objects are kept alive by lists maintained by parent objects.

Yup.


> Geometry that is not in the database is kept alive by being part of
> one or more ObjectLists maintained outside of the database.

Yup, or you can also have an individual object all by itself referenced by script - the script engine itself will keep an open reference count on the object in that case.


> Destruction of geometry not in the database can be accomplished
> by the removal of the geometry objects from all ObjectLists being
> maintained by a script (talking from a scripting perspective).

Yup, the object itself will be destroyed when the last reference is released.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.14 In reply to 3541.9 
Hi Dave,

> I do not believe the IDL file contains anything for
> these special methods (since they are not showing
> up in the documentation I am automatically generating).

Yeah, that's right - those are not declared in the IDL and are just implemented dynamically by some factories.

But most of the time they are things that are used by the UI, like for instance when doing a rotation the UI wants to show the current angle that the factory is rotating by if it was just given points as the inputs. So the rotate factory has a custom "angle" property that the UI can access to get put that number into the UI.

Or some things might analyze the input and tell the UI whether certain controls should be enabled or disabled.

Most of the time it's stuff more like that, Trim is kind of an exception.


> Does that mean the only way to discover them is to pore over
> the xxx.js files looking for them?

Yeah, that's the only way you would be able to find them right now.

But Trim may be the only thing that has a custom method that is needed to be triggered for the thing to actually do its work. The vast majority of those things are just for querying stuff to put in the UI for feedback while you are moving the mouse around.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.15 In reply to 3541.10 
Hi Dave, re: scripting Trim

> So which method will return me the fragment list?

So the way the command works is you put an object list for objects to trim in input 0.

Then you put in an object list for cutting objects in input 1.

Then run the generateFragments() method, this will create an internal list of fragments, and those fragments are added to the geometry database and then the next step of the regular Trim command is to pick which pieces you want to discard.

Once the selection of pieces to discard (or keep depending on the mode) is finished, then those go into a "selected fragments" list which goes into input 2. You can put in an object list there that is empty which will be the same as selecting nothing in the Trim command which splits everything up.

After that I think you should be able to call .calculate() to get the split up pieces.

So the key thing is you have to have an object list in input 2, even if it does not have any objects in it. That's just a quirk of how Trim happened to be written, it fails if it could not retrieve the object list holding the selected fragments that came from the object picker in the regular trim workflow.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.16 In reply to 3541.14 
> But Trim may be the only thing that has a custom method that is needed to
> be triggered for the thing to actually do its work. The vast majority of those
> things are just for querying stuff to put in the UI for feedback while you are
> moving the mouse around.

OK, sounds like from a procedural point of view, 'trim' is the only one that might require some additional documentation.

For completeness, it might be nice to document the other stragglers as well, but since I have not got complete explanations done yet for all the methods documented in the IDL, I guess I won't worry about them until everything else has been documented.

Thanks...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.17 In reply to 3541.15 
> So the key thing is you have to have an object list in input 2, even if it does
> not have any objects in it. That's just a quirk of how Trim happened to be written,
> it fails if it could not retrieve the object list holding the selected fragments that
> came from the object picker in the regular trim workflow.

Hmm...after reading this encouraging response, I re-examined my 'trim' function and found that this was more or less what I was doing. However, it did take quite a bit more experimentation to get it to work (more or less) correctly. After much additional fiddling around and testing, I found that the following code seems to work (from a procedural perspective):

function trim ( objects, cutters, extend_lines, project_intersections ) {
var trimmer = factory( 'trim', as_list( objects ), as_list( cutters ),
object_list(), 'remove', arg( extend_lines, false ),
arg( project_intersections, true ) );
var result = trimmer.generateFragments();
var result = trimmer.calculate();
trimmer.reset();

return result;
}

The things to note about this code are:

- I bind an empty ObjectList to input 2 (fragments), but never look at it again.
- The call to 'calculate' produces the expected 'fragments' list to return.
- You MUST call 'reset' on the trim factory, otherwise the internal state of MoI gets really messed up (but doesn't crash). What I got was some fragments that could never be deleted using the UI, but moving them created new clones that could be deleted (weird). Putting the 'reset' call in fixed that behavior.

After my experience with finding that it was necessary to call 'reset' to get 'trim' to behave correctly, I began to investigate the problem I was seeing with weird 'ghost' objects when using other factories. What I found there was that the following code seems to work well for other factories (such as 'line'):

...set up factory and initialize it...
var result = f.calculate();
f.cancel();

return result;

Note that (at least in the case of 'line', which I've been doing the testing on), the call to 'cancel' seems to cure the problem with weird ghost objects showing up on the display (but a call to 'reset' did not fix the problem in this case).

In any case, this exercise seems to have made my procedural interface a little more reliable in the results it produces, so thanks again for all the explanations...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.18 In reply to 3541.17 
Hi Dave, so what kind of ghost objects are you seeing with the line, is it an extra point object?

Are you possibly calling update() at some time before you call calculate() ?

If so then you probably should leave out the call to update() because that's meant to be used for an interactive command, so things like UI objects are created during that call, in the case of Line there is a point object created to mark the beginning point.

If you do call update(), then you could call cancel() to erase the interactive stuff like the UI geometry though.

But if you call calculate() only and avoid calling update() the UI geometry should be suppressed.


But it sounds like you may be currently getting the regular "interactive mode" stuff created by a call to update() - if you just want to generate output objects directly and have them returned to your script then call calculate() _instead_ of calling update().


- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.19 In reply to 3541.18 
> Hi Dave, so what kind of ghost objects are you seeing with the line, is it an extra point object?

I never use 'update' calls in my code. Previously, when I was still seeing the 'ghost' objects, the only factory call I was using was 'calculate'. Now, in addition to 'calculate', I also call 'cancel' after the call to 'calculate', which seems to eliminate the 'ghost' objects when creating things like lines.

What I mean by a 'ghost' object is a copy of a created object (like a line) that was never explicitly added to the geometry database (i.e. because only 'calculate' was used to create it), but which nevertheless shows up in the viewport.

The situation is usually as follows:

- Run a script that creates a line (using 'calculate'), but which never gets added to the geometry database.
- The viewport looks OK at this point (i.e. the created line is not visible, since it was not added to the geometry database).
- Now, using the UI (not a script), start an interactive operation, like creating another line. Click with the mouse to set the line's start point.
- At this point, the original line (created by the script in step 1), appears in all viewports.
- Complete the drawing of the interactive line by clicking to set the end point.
- At this point, the interactively created line is visible, and the 'ghost' line from step 1 disappears, never to be seen again.

Another attribute of 'ghost' geometry is that it can never be selected by clicking (no pre-selection highlight, no selection highlight), but can be selected by box dragging (normal pre-selection highlight and final selection highlight). However, once selected, the object cannot be moved, deleted, etc., although it does eventually go away, as described in the preceding series of steps. You would see this behavior immediately after running the script, when attempting to click or drag select on the empty space in the viewport where the created line should be.

So it looks like there are some circumstances where 'calculate' does put created geometry into a state where it may be displayed for a short while (usually during the extent of the next command). As I mentioned, placing a 'cancel' call after the call to 'calculate' does seem to clear up the problem (in most cases, although 'reset' seems to do a better job when using the 'trim' factory, as I mentioned before).

At this point, with the 'reset' (for 'trim') and 'cancel' (for everything else) calls in place, I have not seen any 'ghost' geometry at all. The only anomalous behavior I am seeing at this point is that when running a fairly complex script that creates lots of intermediate geometry, using a mixture of different factory operations, MoI sometimes ends up in a state that seems like it is waiting for a command to end. That is, the viewports all look fine, and may contain correctly selected objects (created by the script), but I cannot deselect existing objects or select new ones. Pressing 'enter' or 'esc' seems to exit whatever state is in in, and things are back to normal after that. I'm still investigating this, and it may be due to some other error in my script...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.20 In reply to 3541.19 
Michael,

Let me know if you want me to send you some Javascript code that can reproduce the 'ghost' object behavior. But I think you can get the behavior I described by writing a simple script that:

- Creates a 'line' factory object and sets all its inputs.
- Run the 'calculate' method on the factory object.

At that point I think you should be able to see the 'ghost' line behavior I described...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.21 In reply to 3541.20 
Just another update...

With the recent addition of the 'reset'/'cancel' calls, I'm finding my procedural scripting API is much more robust and stable now. In fact, I'm no longer seeing the last problem I mentioned, about MoI ending up in the strange 'command-like' state after running a script. As a result, I have been able to iterate really quickly now while developing my current script (since like I mentioned on another thread, I dynamically load my scripts while MoI is running).

So this is mainly a "food for thought" post for Michael...

Using my procedural API, I am finding that writing scripts many times is more like functional programming than standard procedural programming. That is, scripts tend not to have any explicit loops, but operate on lists of objects. So in many cases, ObjectList's are the "lingua franca" of the scripting world. Of course, at the lower levels of the procedural library, there are routines that must operate on the contents of the lists. In many cases, the operation that must be performed is to merge two lists into one. This can potentially create a performance bottleneck, since (as far as I know) the only way to do this is via the 'addObject' method on an ObjectList. Since Javascript (in IE) is not all that fast, I would propose adding an 'addObjects' method to ObjectList, which would add all of the objects in its ObjectList argument to the receiving ObjectList (similar to the 'addObjects' method on the GeometryDatabase object). Presumably this would be trivial to implement, but would better leverage the performance advantage of C++ over Javascript for this common scripting operation.

Again, this is just a suggestion, so feel free to take it or leave it :-)

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.22 In reply to 3541.19 
Hi Dave,

> I never use 'update' calls in my code.

Do you call .commit() though? commit() implicitly calls update() if there has not been an update done once already.

The same thing goes for commit() as update() - avoid calling that interactive mode stuff if you intend to use calculate().

The 'ghost' effect that you're describing comes from the object having the "no hit test" flag set on it - that means that it does not go into the regular hit test map so it cannot be targeted for snaps or click selections. That's to avoid things like drawing a line and having the next point you pick getting snapped on to that same line that is in the geometry database from the previous mouse move.


- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Michael Gibson
3541.23 In reply to 3541.21 
Hi Dave, the "addObjects" method sounds like a good idea, and it should be easy to add in once I am back in development mode when v3 starts up.

It definitely helps for speed to implement as much on the C++ side as possible so that the script is more directing and gluing things together rather than doing a lot of work itself.

- Michael
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.24 In reply to 3541.22 
Michael,

> Do you call .commit() though? commit() implicitly calls update() if there has not been an update done once already.

No, the only calls I am making on a factory object are: setInput, calculate and cancel (for most operations), and setInput, generateFragments, calculate and reset (for 'trim' operations). I verified this by grepping my procedural library for 'commit' or 'update', and did not find any matches anywhere.

BTW, I spoke too soon in my last post :-(. I am still seeing the weird post-script state where I have to hit enter or esc to get back to normal. The behavior I am seeing makes it look like it is a 'trim' operation side effect...

Upon script completion, a number of points I created are (correctly) in the selected state (i.e. highlighted). If I mouse around, I can unselect/select any of these points (which are all trim fragment endpoints), but cannot select any other geometry in the scene. Once I press enter, then everything is fine again (i.e. I can select any geometry in the scene). So it looks like 'trim' has somehow left MoI in a trim related picking state. I'll play around with 'cancel' and other operations on the factory some more to see if I can 'cure' this)...

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged

Previous
Next
 From:  Dave Morrill (DMORRILL)
3541.25 In reply to 3541.23 
> Hi Dave, the "addObjects" method sounds like a good idea, and it should be easy
> to add in once I am back in development mode when v3 starts up.

OK, great!

> It definitely helps for speed to implement as much on the C++ side as possible so
> that the script is more directing and gluing things together rather than doing a lot of work itself.

100% agreement on that. In fact, I was one of the pioneers in the use of 'glue' languages when I created the GLUE (General Language for Unifying Environments) language in 1987, back when I was with IBM Research :-)

- Dave Morrill
  Reply Reply More Options
Post Options
Reply as PM Reply as PM
Print Print
Mark as unread Mark as unread
Relationship Relationship
IP Logged
 

Reply to All Reply to All

 

 
Show messages:  1-5  6-25  26-38