Scolring - Forum

Entraides et échanges autour de la technologie Scol - Informations and exchanges on the Scol technology

Vous pouvez changer la langue de l'interface une fois inscrit - You can change the language once registered

You are not logged in.

#51 Re: Programing » Persistence of objects or(?) freeing memory » 26-Nov-2015 19:27:47

iri

I don't remerber precisely.


(hd list) :: removeItemInList tl list item;

This line removes nothing. Instead of that, it adds (so, it keeps) the current item.
In fact, once the function finishes, the returned list is the truncated list.
Thus, it is after the line below that a GC could free the deleted item.

set lCylinder = removeItemInList lCylinder sCylinder;

I'm clear ?

#52 Re: Programing » strange (or stupid) typing error » 26-Nov-2015 19:17:50

iri

Yes but the error messages can be obvious ...
The debugger is a hard job but it would be good to have it finally!
Currently, i have some problems with Windows and CMake ....

#53 Re: Programing » Persistence of objects or(?) freeing memory » 26-Nov-2015 19:08:04

iri
arkeon wrote:

gc take time so you should use it really only when you need it.
sometimes it's worst than better so do not abuse smile

I agree. Danger.

Else, it is not hidden smile
http://redmine.scolring.org/projects/sc … emory.html

#54 Re: Programing » strange (or stupid) typing error » 26-Nov-2015 17:32:34

iri

I should really improve these errors. This is not explicit.

#55 Re: Programing » Persistence of objects or(?) freeing memory » 26-Nov-2015 16:08:05

iri

I've not really understand what you want here. Your code is not clear (perhaps old code is remained ...).

Your objective :
- Add some cylinders.
- Each cylinder lives a while. So, it dies after a moment.
- Sometimes, a cylinder can be dead before the end of its live.

Is it right ?

Very quickly, here is my idea :

struct   Cylinder =[
	iId : I,	// cylinder identifier
	Fradius			: F,
	Fheight			: F,
	funreturnheightasintergerasstring: fun[Cylinder] S,
	funreturnarea	:	fun [Cylinder] F,
	funreturnvolume:	fun[Cylinder] F,
	tClock : Timer		// the cylinder life time
	] mkCylinder;;
	
typeof lCylinder = [Cylinder r1];;   // all living cylinders
var periodTimer = 1000;;	// 1000 ms, the life time
var id = 0;;

/* --------- */

// Miscelleanous
// Note : A function already exists for that in the standard library
fun removeItemInList (list, item)=
	if list == nil then
		nil
	else
		if item == hd list then
			tl list
		else
			(hd list) :: removeItemInList tl list item;;
			
/* --------- */

// initialize a Cylinder structure
fun initCylinder ()=
	mkCylinder [++id nil nil nil nil nil nil];;
	
/* --------- */

// setters
fun setCylinderRadius (sCylinder, fRadius)=
	set sCylinder.Fradius = fRadius;
	sCylinder;;
	
fun setCylinderHeight (sCylinder, fHeight)=
	set sCylinder.Fheight = fHeight;
	sCylinder;;
	
/* --------- */

fun deleteCylinder (sCylinder)=
	if sCylinder == nil then
	(
		_fooS "The cylinder was nil !";
		1
	)
	else
	(
		_deltimer sCylinder.tClock;
		set lCylinder = removeItemInList lCylinder sCylinder;
		_fooS sprintf "The cylinder '%d' is destroying. It will remain %d cylinder(s)." [sCylinder.iId sizelist lCylinder];
		set sCylinder = nil;
		0
	);;

/* --------- */

fun cbCylinderTimer (timer, sCylinder)=
	_fooS sprintf "The cylinder %d dies ..." [sCylinder.iId];
	deleteCylinder sCylinder;;

fun makeCylinderTimer (sCylinder)=
	set sCylinder.tClock = _rfltimer _starttimer _channel periodTimer @cbCylinderTimer sCylinder;
	sCylinder;;

/* --------- */
	
fun addCylinder (fRadius, fHeight)=
	let initCylinder -> sCylinder in
	(
	setCylinderRadius sCylinder fRadius;
	setCylinderHeight sCylinder fHeight;
	makeCylinderTimer sCylinder;
	set lCylinder = sCylinder :: lCylinder;
	_fooS sprintf "The new cylinder '%d' is added. The total is currently of %d" [sCylinder.iId sizelist lCylinder];
	sCylinder
	);;
	
/* --------- */

fun main ()=
	_showconsole;
	
	_fooS " Application starts now !";
	
	let mktab 5 nil -> array in	// for my convenience only !
	(
	deleteCylinder array.0;
	set array. 0 = addCylinder 1.0 12.0;
	set array. 1 = addCylinder 4.0 32.0;
	set array. 2 = addCylinder 7.0 16.0;
	deleteCylinder array. 0;
	set array. 3 = addCylinder 1.0 12.0;
	deleteCylinder array. 2;
	);
	0;;

The console prints :

 Application starts now !
The cylinder was nil !
The new cylinder '1' is added. The total is currently of 1
The new cylinder '2' is added. The total is currently of 2
The new cylinder '3' is added. The total is currently of 3
The cylinder '1' is destroying. It will remain 2 cylinder(s).
The new cylinder '4' is added. The total is currently of 3
The cylinder '3' is destroying. It will remain 2 cylinder(s).
The cylinder 2 dies ...
The cylinder '2' is destroying. It will remain 1 cylinder(s).
The cylinder 4 dies ...
The cylinder '4' is destroying. It will remain 0 cylinder(s).

#56 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 19:57:48

iri
hebdemnobad wrote:

I don't think in this case sorting is an issue because if I have a list of tuples such as [[I objecttype]r1] there are plenty of ways to find an object that has been emptied of values.

You may search the first Cylinder object to nil.

typeof lCylinder = [[I Cylinder] r1];;
...

fun searchNilCylinder2 (list)=
        if list == nil then
                nil
        else
                let hd list -> [index cyl] in
                if cyl == nil then
                        index // or hd list or what you want / need !
                else
                        searchNilCylinder2 tl list;;

fun searchNilCylinder ()=
        searchNilCylinder2 lCylinder;;

Note : you may include the index in the Cylinder structure ?

hebdemnobad wrote:

Another section for the O'Reilly book.

Yes big_smile

#57 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 19:09:43

iri

Yes.

Or you could define a limit of the size list.

By example :
- Can I a new Cylinder create ?
-> if the Cylinder list size is < max then ok, you can create it.
-> if the Cylinder list size is >= max then ko, you can not create it.

- I want delete a Cylinder :
-> If the Cylinder is in the Cylinder list, ok, it is deleted and  it is removed from the list.
-> If the Cylinder is not in the Cylinder list, ko, there is a bug somewhere (or the Cylinder has not been created previously) !

Thus, with this method, you manage one list only. However, this list is not sorted. The sorting is important or not ?

#58 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 17:35:22

iri

If you want be safer, check the variable before to set a field.

By example :

fun createGlobalStructure (iValue, szValue)=
	set myS = mkMyStructure [iValue szValue];
	0;;

fun setGlobalStructure (iValue, szValue)=
        if myS == nil then        // this is safer !
                createGlobalStructure iValue szValue
        else
        (
	        set myS.myInteger =  iValue;
	        set myS.myString =  szValue;
	        0
        );;

#59 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 17:31:24

iri
hebdemnobad wrote:

in other words, are the memory addresses allocated by "local" now free to be assigned to other variables, such as other MyStructures?

Its memory address is not freeed ! It is empty and you must (re)create the content by the constructor.

#60 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 17:29:10

iri

I made a mistake in my previous example. I create again a new structure content in calling the mkMyStructure constructor. So, the result is false !

In the new code below, i set (not create) the fileds of the global and the local variable after set the variables to nil (test 3 and 4).

struct MyStructure = [
	myInteger : I,
	myString : S
	] mkMyStructure;;
	
typeof myS = MyStructure;;

fun createGlobalStructure (iValue, szValue)=
	set myS = mkMyStructure [iValue szValue];
	0;;
	
fun createLocalStructure (iValue, szValue)=
	mkMyStructure [iValue szValue];;
	
fun setGlobalStructure (iValue, szValue)=
	set myS.myInteger =  iValue;
	set myS.myString =  szValue;
	0;;
	
fun setLocalStructure (s, iValue, szValue)=
	set s.myInteger =  iValue;
	set s.myString =  szValue;
	0;;
	
fun testGlobalStructure ()=
	_fooS "Global ... !";
	_fooId myS.myInteger;
	_fooS myS.myString;
	0;;
	
fun testLocalStructure (localStructure)=
	_fooS "Local ... !";
	_fooId localStructure.myInteger;
	_fooS localStructure.myString;
	0;;
	
fun main ()=
	_showconsole;
	
	_fooS "Test one :";
	testGlobalStructure;
	testLocalStructure nil;
	
	_fooS "Test two :";
	createGlobalStructure 5 "The life is beautiful !";
	testGlobalStructure;
	testLocalStructure createLocalStructure 15 "Shinning";
	
	_fooS "Test three :";
	set myS = nil;
	testGlobalStructure;
	let createLocalStructure 15 "Shinning" -> local in
	(
	set local = nil;
	testLocalStructure local;
	setLocalStructure local 25 "Wolf";
	testLocalStructure local;
	);
	
	_fooS "Test four :";
	setGlobalStructure 19 "One Flew Over the Cuckoo's Nest";
	testGlobalStructure;
	
	0;;

The console prints :

Test one :
Global ... !
NIL
NIL
Local ... !
NIL
NIL
Test two :
Global ... !
5
The life is beautiful !
Local ... !
15
Shinning
Test three :
Global ... !
NIL
NIL
Local ... !
NIL
NIL
Local ... !
NIL
NIL
Test four :
Global ... !
NIL
NIL

So, after set myS = nil; or set local = nil; the variables are known but they are inoperable. To use them, you must call mkMyStructure again.
Is the variable memory freeed ? Not totally, its address is kept in the Scol heap as you declare a variable with typeof.
typeof myS = MyStructure;; myS is currently "empty" (nil). Like in C, an address is reserved. You are able to set the fields later.

A local variable is freeed when the next instruction ends.

#61 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 16:07:23

iri
hebdemnobad wrote:

what I'm thinking of is how scol would handle a game or simulation where you have, say, thousands of animals swimming around the viewer (most would not be rendered by the so3 engine, but scol would keep track of them since once they come close to the viewer, they will become visible), where each animal holds variables like strings (names), hit-points (integers) and transforms ([scale][location][rotation][speed][transform coefficient])[FFF][FFF][FFFF][F][F] tuples, where they are eating each other and mating, therefore destroying and creating new animals, increasing and decreasing hitpoints.

or the simulation of a planet containing thousands of scene nodes (buildings or perhaps life forms) which must be dynamically created and destroyed.

ooh ... several way to proceed ....

I think there is a client part and a server part. You can seperate datas in the client or in the server. The server stores the datas but it doesn't play. The client doesn't need all datas to play. You can also use lists to add / remove / modify the datas. in the server, you can work in memory or from data files. And more, i don't very cogitate.

#62 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 16:02:30

iri
hebdemnobad wrote:

so what exactly does the code:

set dcylinder = nil;

do in terms of freeing memory.


Look this code :

struct MyStructure = [
	myInteger : I,
	myString : S
	] mkMyStructure;;
	
typeof myS = MyStructure;;

fun createGlobalStructure (iValue, szValue)=
	set myS = mkMyStructure [iValue szValue];
	0;;
	
fun createLocalStructure (iValue, szValue)=
	mkMyStructure [iValue szValue];;
	
fun testGlobalStructure ()=
	_fooS "Global ... !";
	_fooId myS.myInteger;
	_fooS myS.myString;
	0;;
	
fun testLocalStructure (localStructure)=
	_fooS "Local ... !";
	_fooId localStructure.myInteger;
	_fooS localStructure.myString;
	0;;
	
fun main ()=
	_showconsole;
	
	_fooS "Test one :";
	testGlobalStructure;
	testLocalStructure nil;
	
	_fooS "Test two :";
	createGlobalStructure 5 "The life is beautiful !";
	testGlobalStructure;
	testLocalStructure createLocalStructure 15 "Shinning";
	
	_fooS "Test three :";
	set myS = nil;
	testGlobalStructure;
	let createLocalStructure 15 "Shinning" -> local in
	(
	set local = nil;
	testLocalStructure local
	);
	
	_fooS "Test four :";
	createGlobalStructure 19 "One Flew Over the Cuckoo's Nest";
	testGlobalStructure;
	
	0;;

It displays in the log/console :

Test one :
Global ... !
NIL
NIL
Local ... !
NIL
NIL
Test two :
Global ... !
5
The life is beautiful !
Local ... !
15
Shinning
Test three :
Global ... !
NIL
NIL
Local ... !
NIL
NIL
Test four :
Global ... !
19
One Flew Over the Cuckoo's Nest

In the test 1, no struct variable is created, so all fields are nil.
In the test 2, global and local variables are created, all fields are set.
In the test 3, global and local variables are been created, then they set to nil. All fields are nil too.
In the test 4, global variable is set again. Their fields are not nil, the global variable is never freeed while the VM is not exited (or the owner channel exists).

#63 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 15:05:47

iri

You can not do that explicitely !
Scol manages itself the memory.

In some APIs, functions destroys some objects explicitely. These objects call externaly (= third party library, = not directly managed by Scol) a memory function. But even in this case, they should be automatically destroyed when the VM exits, their owner channel are closed or if they are a local object.

If you would do these inoperable objects, you can set them to nil.

set dcylinder = nil;

Something like unset doesn't exist in Scol.

Another way, if this is a critical problem, is to create a new channel from the current channel, to load in it a package with all structure (struct) code and to close it when you no longer need. With your code, you may write that :

In a cylinder package (cylinder.pkg) :
I just  change your main function name to "mainCylinder" and i remove the _showconsole function.

struct   Cylinder =[
	Fradius			: F,
	Fheight			: F,
	funreturnheightasintergerasstring: fun[Cylinder] S,
	funreturnarea	:	fun [Cylinder] F,
	funreturnvolume:	fun[Cylinder] F
	
	]mkCylinder;;
	
	
fun returnheight(dcylinder)=
	let ftoi dcylinder.Fheight -> height in
itoa height;;
	


fun return_area_function(dyclinder)=
	let dyclinder.Fradius *. PIf-> result in
	 result;;

fun return_volune_function(dyclinder)=
	let return_area_function dyclinder-> area in
	area *. dyclinder.Fheight;;
	 
fun initcylinder(radius, height, returnheightfunction, returnareafunction, returnvolumefunction)=
	_fooS "initializing cylinder";
	mkCylinder [radius height returnheightfunction returnareafunction returnvolumefunction];;
		
fun copy_this_cylinder(dcylinder)=
     initcylinder dcylinder.Fradius dcylinder.Fheight dcylinder.funreturnheightasintergerasstring dcylinder.funreturnarea dcylinder.funreturnvolume;;

fun mainCylinder ()=

	let initcylinder 102.0 19.087 @returnheight @return_area_function @return_volune_function -> dcylinder in
	(
	_fooS strcatn  "the height of the cylinder as a float converted to an interger converted to a string is " :: (returnheight dcylinder) :: nil;
	_fooS strcatn "the area taken up by the cylinder is ":: (ftoa (return_area_function dcylinder)):: " square meters."::nil; 
	_fooS strcatn "the volume taken up by the cylinder is " ::(ftoa (return_volune_function dcylinder)):: " cubic meters"::nil; 
	let copy_this_cylinder dcylinder-> new_cylinder in
		(
		   _fooS "creating new cylinder and changing radius from 102.0 to 10.5 with copy_this_cylinder (dcylinder) function";
			set new_cylinder.Fradius= 10.5;
			_fooS strcat "the radius of the copied cylinder is" ftoa new_cylinder.Fradius;
		);
	);
	0;;

In a main package :

proto mainCylinder = fun [] I;;

// Return the new channel created
fun callCylinder ()=
	let _openchannel nil "_load \"tests/cylinder.pkg\"" _envchannel _channel -> newChnannel in
 	(
	 mainCylinder;
	newChnannel
	);;
	
	
fun closeCylinder (channel)=
	_killchannel channel;
	0;;

fun main()=
	_showconsole; 
	
	closeCylinder callCylinder;
	0;;

mainCylinder must be prototyped : this function is still unknown when the main package is loaded by the VM.
Note : if you want call this function only, you can call it directly when the new channel is created. In this case, your callCylinder function should only be :

fun callCylinder ()=
	_openchannel nil "_load \"tests/cylinder.pkg\"\nmainCylinder" _envchannel _channel;;

In the main function (or anywhere you want), you call the function which creates the new channel (here : callCylinder) and, later (when you want), you close this channel (here : closeCylinder).

#65 Re: Programing » Persistence of objects or(?) freeing memory » 24-Nov-2015 11:13:02

iri

Hello

Right, mkCylinder is the constructor to allocate in memory and create in the current Scol environment a Cylinder object.

The object can be freeed when two events :
- when the VM exits (or the owner channel is closed);
- when the object is no longer needed. In this case, the Scol garbage collection (GC) should find, destroy and deallocate the such objects.

In your example, dcylinder should be freeed after the end of the next instruction. Indeed, dcylinder is created in a local variable.
Same thing for new_cylinder.

The locale variables are not reachable when the next instruction ends. This is sure. But the objects can be still in memory until the GC destroys it.

#66 Re: Programing » strbuild and strextr and how they work together » 23-Nov-2015 23:11:48

iri

Directly print a list of strings lists is for debugging. A printer function should be written each time to format the output precisely.

#67 Re: Programing » strbuild and strextr and how they work together » 23-Nov-2015 21:50:04

iri

strbuild / strextr are commonly used to set arguments in the functions. By example, you can define a list of string parameters like for each name, a gender, a city, a phone, ...
They are also used to parse a long string.

To read or write it, you have several ways. Here are the most common :

fun fooFromName (list, name)=
	let switchstr list name -> lResult in
	let if !strcmp "m" nth_list lResult 0 then ["he" "him"] else ["she" "her"] -> [s t] in
	(
	_fooS sprintf "%s is %s years old, %s lives in %s. Set %s to call %s." [name hd tl lResult s hd tl tl lResult hd tl tl tl lResult t];
	// or, the line below displays the same thing :
	// _fooS sprintf "%s is %s years old, %s lives in %s. Set %s to call %s." [name nth_list lResult 1 s nth_list lResult 2 nth_list lResult 3 t];
	);;

// Return the same list
fun fooAllList (list)=
	if list == nil then
		nil
	else
		(_fooSList hd list) :: fooAllList tl list;;
		
fun fooLineOfList (list, number)=
	_fooSList nth_list list number;;

fun main ()=
	_showconsole;
	
	let ("John" :: "m" :: "25" :: "New York" :: "0123456789" :: nil) ::
	     ("Harry" :: "m" :: "44" :: "Boston" :: "2365478910" :: nil) ::
	     ("Sally" :: "f" :: "32" :: "Seattle" :: "541239870" :: nil ) ::
	     nil
	-> list in
		
	(
	_fooS "By name ...";
	fooFromName list "John";
	fooFromName list "Sally";
	
	_fooS "\nAll ...";
	fooAllList list;
	
	_fooS "\nItem 1 :";
	fooLineOfList list 1;
	
	0
	);;

In the log/console, you are :

By name ...
John is 25 years old, he lives in New York. Set 0123456789 to call him.
Sally is 32 years old, she lives in Seattle. Set 541239870 to call her.

All ...
John:m:25:New York:0123456789:NIL
Harry:m:44:Boston:2365478910:NIL
Sally:f:32:Seattle:541239870:NIL

Item 1 :
Harry:m:44:Boston:2365478910:NIL

With strcatn, this is not easy to parse the returned string.

Note : a text line by line can be get/set from lineextr / linebuild.
To extract a sublist, listextr may be a good choice. And more about list, see http://redmine.scolring.org/projects/sc … brary.html.

#69 Re: Programing » making a hello world scol app for android » 13-Nov-2015 21:52:28

iri

For that, it is already done. You can try.

#70 Re: Programing » implementing pseudo classes and pseudo inheritance » 13-Nov-2015 21:50:07

iri

Hello,

struct defines a new type. It contains one or several fields. Each field can be a different type (included from another struct) but it is not changeable.
Thus, struct creates a new type. Just this.
struct can be used to encapsulate some objects in a single "opaque" object.

struct Animal =[

ihasbackbone:		I, //0 no backbone, 1 backbone
idiet:				I, //0 for vegetarian, 1 for carnivore, 2 for omnivore
ibiome:				I, //0 water, 1 land, 2 air
imulticellular:	I,//0 unicellular, 1 colonial like dcoral, 2 multicellular
igender:				I,//0 hermaphrodite, 1 female, 2 male
ilegs:				I, //0 for none, interger for amount of legs
sname:				S //every animal has a name!
	] mkAnimal;;

Each field is a function. By example, ihasbackbone has the type : fun [Animal] I. You are an "opque" object in entry, an integer in output which is an elementary object.
mkAnimal is the constructor. You must construct your type before use it.

typedef is like C-union.
Usually in Scol, these unions allow to several types for one variable. You can see this like an interface. It can be too seen like a switch.

typedef InterfaceAnimal_Interface = 	
Animal_interface Animal
| Invertebrate_interface Invertebrate
| Mollusc_interface Mollusc
| Other_animal_interface;;

Each element is a constructor of the InterfaceAnimal_Interface. It is also a function, its type is : fun [Invertebrate] InterfaceAnimal_Interface.
Other_animal_interface can be considered like a "default constructor".
Maybe this can help you to see some usages :
struct => from an "opaque object" to an "elementary object"
typedef => from an "elementary object" to an "opaque object".
However, in these two cases, the "opaque objects" are really different from each other. It is NOT a "reverse" !

typedef is really useful in a pseudo-object programming. If the syntaxe is relatively onerous, it is lighter in memory !.
Otherwise, several typeof replace a typedef, sometimes a struct.

Scol is polymorphic. By example :

fun compareObject (object1, object2)=
  object1 == object2;;

It accepts any type in entry (except S).

More in our wiki :
http://redmine.scolring.org/projects/tu … es_in_Scol

Your code is correct.

#71 Re: Programing » a simple object with a simple method » 5-Nov-2015 19:51:34

iri

That's right.

However, you can continue further and make something as JS :

You may rewrite your main function like this (your old code is commented and i use the Scol formatted strings function sprintf) :

fun main()=
	_showconsole; 
	let initcylinder 102.0 19.087 @returnheight @return_area_function @return_volune_function -> dcylinder in
	(

	_fooS strcat  "the height of the cylinder as a float converted to an interger converted to a string is " exec dcylinder.funreturnheightasintergerasstring with [dcylinder];
	//_fooS strcatn  "the height of the cylinder as a float converted to an interger converted to a string is " :: (returnheight dcylinder) :: nil;

	_fooS sprintf "the area taken up by the cylinder is %f square meters." [exec dcylinder.funreturnarea with [dcylinder]]; 
	//_fooS strcatn "the area taken up by the cylinder is ":: (ftoa (return_area_function dcylinder)):: " square meters."::nil; 

	_fooS sprintf "the volume taken up by the cylinder is %f cubic meters" [ftoa exec dcylinder.funreturnvolume with [dcylinder]]; 
	//_fooS strcatn "the volume taken up by the cylinder is " ::(ftoa (return_volune_function dcylinder)):: " cubic meters"::nil; 

	);
	0;;

Here, i use exec to call the method set in the initialization.

exec function_to_call with [arguments];

The return of exec is the return of the called function.

You are able to change the function (the method) during the execution. For that, change the method in your structure.

// fun [Cylinder fun [Cylinder] F] I
fun changeVolumeMethod (dyclinder, functionToSet)=
  set dyclinder.funreturnvolume = functionToSet;
  0;;

In the next exec call, the new method will be executed. If you set nil, nothing is done.

To be continued ... smile

#74 Re: Openspace3D » Introduction To OpenSpace3D Book? » 7-Oct-2015 20:48:40

iri

Don't worry, no offense !

Official tutorials :

- http://www.openspace3d.com/lang/en/support/ (middle page)
- https://www.youtube.com/user/Openspace3D

And all included examples in your local openspace3d directory

#75 Re: Openspace3D » Introduction To OpenSpace3D Book? » 7-Oct-2015 18:12:43

iri
Morbott wrote:

I was attracted to OpenSpace3D because of its PlugITs system, but I'll be honest, I haven't found it easy to pick up.

hmmm ... A bit like Blender, perhaps some find it difficult to use because its interface is different from many other 3d softwares.

Morbott wrote:

A lot of times, I find that the noob part of these books get glossed over. When one is a pro, it's sometimes hard to dumb things down again and communicate to someone who has never used the program.

True. However, I think this is not the case here.
Users should you give their comments.

Morbott wrote:

Anyway... if anyone has just a screen grab or anything that shows the table of contents, that would be great.

http://www.openspace3d.com/ebook-conten … de-lebook/ (the table of contents)
http://www.openspace3d.com/buy-openspac … enspace3d/

Board footer

Powered by FluxBB