Jump to content
Developer Wiki and Function Reference Links ×

More questions-Various


Recommended Posts

Greetings,

My first script (it's been a struggle!) is similar(?!) to the Place Plant tool in Landmark.

It takes the active symbol (which are just circles with 2 letters inside them) and places them at each vertex of a polyline as it is drawn.

After the polyline is complete it outlines the "planting" by duplicating the symbols, ungrouping the duplicates and Adding surfaces to give a polyline.

My current problem is that I want to delete the duplicate text that was produced during the "outlining" sequence, but in the end of my script where I deselect the LObject and delete what's remaining, one of the text blocks gets deselected and not the poly outline.

I've also tried LNewObject but that didn't work either.

Here it is.

PROCEDURE PolyLn;

{Draws a polyline, places a symbol at each vertex, outlines

the symbols and groups the result}

VAR

pX, pY,rotationAngle,offsetDX, offsetDY:REAL;

h,g,L,L2,L3,symHd,t: HANDLE;

convertAction,index,lw: INTEGER;

fillPattern: LONGINT;

symName,add:STRING;

BEGIN

{aquire active symbol & return nameSTRING}

symHd:=ActSymDef;

symName:=GetSDName(symHd);

{start poly, aquire mouse click location, place vertex

and insert symbol instance}

OpenPoly;

BeginPoly;

GetPt(pX, pY);

AddPoint(pX,pY);

Symbol(symName, pX, pY,0);

GetPt(pX, pY);

AddPoint(pX,pY);

Symbol(symName, pX, pY,0);

{repeats as long as the shift key is held down}

WHILE

(Shift) DO

BEGIN

GetPt(pX, pY);

AddPoint(pX,pY);

Symbol(symName, pX, pY,0);

END;

EndPoly;

{set poly fill and line wt,}

h:= LNewObj;

SetFPat(h,0);

SetLW(h,6);

MoveFront;

SetDSelect(h);

{duplicate symbols and select only the duplication.

for some reason the duplicate command leaves the original selected,

unlike the in-document duplicate}

Group;

Duplicate(0,0);

DSelectAll;

g:= LObject;

SetSelect(g);

HUngroup(g);

{break duplicate symbols into groups}

g:=LNewObj;

SymbolToGroup(g,2);

Ungroup;

{add surfaces and set line wt}

DoMenuTextByName('Add Surface',0);

L:=LObject;

SetLW(L,24);

SetDSelect(L);

DeleteObjs;

SetSelect(h);

MoveFront;

END;

Run (PolyLn);

Questions:

1/ Often when trying things I get a "cannot use a Function here" message, but I don't really understand the practical difference between a Function and a Procedure.

2/ In the script I've succesfully used the LNewObject, but I don't understand why it's not working at the end here.

3/ My script successfully (almost) draws what I want it to, but it's all invisible while doing it.

I'm sure I've seen a temporary-rubber-band-sort-of-thing procedure but I can't find it, or; work out a way to see the symbols as they are being placed like you can when placing symbols manually.

4/Ive used the (Shift) call in the WHILE loop so that I could place any number of symbols. What I really wanted, though, was to click for n times and then double-click to finish like a normal polyline, but I couldn't find a double-click call.

The other thought I had was to Get the last click and the previous click locations, and make the WHILE loop conditional on them not being the same.

(but I couldn't work it out, so I went the easy way!)

Thoughts?

5/ The last thing I want to do is Group the whole lot, but if I use SelectAll it picks up everything in the layer.

What can I use to select only whats been produced "inside" the script?

cheers,

N.

Link to comment

1/ Often when trying things I get a "cannot use a Function here" message, but I don't really understand the practical difference between a Function and a Procedure.

A procedure just does something, like SetSelect in SetSelect(g);

A function will ?return? a value, like LObject

in g:= LObject;

2/ In the script I've succesfully used the LNewObject, but I don't understand why it's not working at the end here.

DoMenuByText calls usually create objects outside the vectorscript expected order. They are considered sort of last resort ways of doing things.

If you could manage to identify your objects two-by-two you might use a normal vectorscript procedure to do the add surface, but it would be a hell of a trouble.

4/Ive used the (Shift) call in the WHILE loop so that I could place any number of symbols. What I really wanted, though, was to click for n times and then double-click to finish like a normal polyline, but I couldn't find a double-click call.

The other thought I had was to Get the last click and the previous click locations, and make the WHILE loop conditional on them not being the same.

(but I couldn't work it out, so I went the easy way!)

Thoughts?

Careful, I think Shift doesn't work the same from Mac to PC

5/ The last thing I want to do is Group the whole lot, but if I use SelectAll it picks up everything in the layer.

What can I use to select only whats been produced "inside" the script?

Have you tried BeginGroup; as you start and EndGroup; at the end?

Link to comment

If you duplicate objects with a script and LNewObj doesn't work for you, try LActLayer. A newly duplicated object will be on top of the stacking order on the Active layer until it is moved or another object is created/moved on top of it.

Good luck,

Raymond

Link to comment

Hello again Raymond, Alexandre;

thankyou again for your replys

quote:

If you could manage to identify your objects two-by-two you might use a normal vectorscript procedure to do the add surface, but it would be a hell of a trouble.


This was what I thought. Nasty. {:-O

quote:

Have you tried BeginGroup; as you start and EndGroup; at the end?


I'll give this a try tonight.

quote:

Have a look at the NNA?s examples page, and study the 'path object' plug-in for a different approach that might help you.


I have looked at this before, but the "missing bits" that are provided by the compiler made it a (more) confusing example for learning the basics. - more questions raised thn answered ;-)

quote:

If you duplicate objects with a script and LNewObj doesn't work for you, try LActLayer. A newly duplicated object will be on top of the stacking order on the Active layer until it is moved or another object is created/moved on top of it.


Well that's what I thought.

Here's the bit of script:

Ungroup;

{add surfaces and set line wt}

DoMenuTextByName('Add Surface',0);

L:=LObject;

SetLW(L,24);

SetDSelect(L);

DeleteObjs;

I thought that the poly produced by the AddSurface would be the last object. (LActLayer produces the same result: doesn't seem to recognise the poly as being the last object and deletes it along with the remaining selected objects.

Why would that be?

cheers,

N.

Link to comment

LObject points to the last object in the drawing (i.e., the last object to redraw if all layers are visible), and is not necessarily on the active layer.

L:=LObject;

SetLW(L,24);

SetDSelect(L);

Unless you are on the top layer of the drawing, this will usually NOT do what you want. I am not sure where your object lies in the stacking order, but I have not tried your code yet. I will look more closely at it later.

Raymond

Link to comment

More Mystery here,

The final sequence that I couldn't get to work:

DoMenuTextByName('Add Surface',0);

L:=LObject;

SetLW(L,12);

SetDSelect(L);

DeleteObjs;

SetSelect(h);

MoveFront;

END;

I modified to:

DoMenuTextByName('Add Surface',0);

L:=LObject;

SetLW(L,12);

DSelectObj((T=Polyline));

DeleteObjs; .......etc

which suceeded in leaving the bits I wanted but the

L:=LObject;

SetLW(L,12);

still wasn't being associated with the polyline produced by the Add Surface command. LNewObj and LActLayer didn't work either. In short, I couldn't work out any way to pin a line weight on that poly. Then I crashed VW.

When I restarted VW the LObject call worked! :-0

I'd swear this (things not working and then working- or vice versa) has happened before, but being hardly in control of the vehicle, I couldn't tell Iif it was my imagination or not.

I've HAVE noted that changing one line of script has all sorts of apparently unconnected "trickle down" effects on the rest of the script. Those butterflys in the Amazon flapping their wings are causing merry hell with my scripts over here. ;-)

Is there something going on here? Am I imagining things?

Does restarting change the behaviour of scripts?

Link to comment

Hi Nicholas

I think you might try calling ReDraw; after DoMenuText().

Restarting should not affect how a script runs,but don't forget that how a doc is set up (prefs,layer options and so on ) can have consequences on some scripts.A restart could change some of that. Also...is it possible that your script was working on a previously produced poly?

Indeed,adding to a script will usually have unexpected results.Figuring out why is called de-bugging.One of the best tools we have for this is Message(). Probably the first thing I might have done is put Message(L) after LObject to see if it was getting a handle to anything at all (which it probably isn't).My guess is that the message would be 0.

This might not help that much with your current prob,but it certainly will be useful to you in the future.The computer is like a super fast working employee who doesn't understand English. You THINK you have explained exactly what needs to be done,but have made some assumptions along the way. The employee follows your instructions literally and the result isn't what you expect but he works so fast you can't tell where it all went wrong. Messaging out variables is a way to find out what he's thinking at each step and this will often point to an area lacking definition.99% of the time the instructions aren't completely exact.This employee doesn't think for himself and only does what you tell him to.

There's a very good chance that ReDraw will help.Have a look at what the function ref says.

later

Charles

[ 05-16-2004, 01:53 PM: Message edited by: ccroft ]

Link to comment

Hi there,

I'm well stuck now.

I imported the script and symbols into a new file, and it doesn't work again. :-(

quote:

Originally posted by ccroft:

Hi Nicholas

I think you might try calling ReDraw; after DoMenuText().

No change with ReDraw I'm afraid.

quote:

Restarting should not affect how a script runs,but don't forget that how a doc is set up (prefs,layer options and so on ) can have consequences on some scripts.A restart could change some of that. Also...is it possible that your script was working on a previously produced poly?


Certainly possible, but the Poly produced by the Add surface definitely appeared to be bpicking up the attributes assigned to it by the LObject call. (if I changed the point size in the script the line weight changed)

quote:

One of the best tools we have for this is Message(). Probably the first thing I might have done is put Message(L) after LObject to see if it was getting a handle to anything at all (which it probably isn't).My guess is that the message would be 0.


Tried this suggestion; No, there is a handle. (but to what?)

Working through the Debugger seems to point to the problem; what is (or is not) the Last "Object" on the Layer/Document.

Clearly the script doesn't seem to think that the poly produced by the "AddSurface" IE the thing I'm trying to assign a variable to with the LObject/LNewObj/LActLayer call, IS the last Object.

I just can't work out why, or what it thinks is the last object.

quote:

You THINK you have explained exactly what needs to be done,but have made some assumptions along the way. The employee follows your instructions literally and the result isn't what you expect


Ha! :-)

The design students I teach say I teach well, but the capacity for misunderstanding is infinite. It never ceases to amaze me how difficult it is to eliminate ambiguity. ;-)

So, I'm stuck.

Can you tell me what the (various) Last Objects in my script

would be?

[ 05-18-2004, 06:34 PM: Message edited by: propstuff ]

Link to comment

What a drag! You tried:

DoMenuText();

Redraw;

L:=LNewObj;

and still no joy?I have a half dozen scripts that draw a poly and get the handle with LNewObj, though none with add surface. I did have a problem with duplicate that was solved wih ReDraw.

To further understand what may be going wrong I would simplify the script to test only this one problem. You might draw 2 circles in a new blank drawing and make a script that says (in pseudo-code):

select all

add surface

L:= LNewObj

change line color

This will tell you if,in fact,the prob is with add surface and not some other element or combo there-of. And it may tell you what LNew is getting (which could help).

If you can't get it to work then you're probably done.

If it does work then proceed step by step to make this simple script more and more resemble your goal.For eg place 2 of the symbols you will use later.Add script one line at a time and verify that everyting keeps working.

I've never encountered a "corrupt" file...but I think you should start with a new drawing to eliminate that.

Finally I think Alex might be right . What you're trying to do does sound like a path PIO. I waited far too long in my learning curve to start writing PIO's for the same reasons as you, but many of the things I was doing are handled much better in a PIO than a menu command or tool.

You may want to drop this idea for now and start with something simpler to learn basic scripting and then look at PIO's. A lot of times it's easier to script the program to draw someting than to manipulate existing objects.....too much "select this ,deselect that,get a handle to the other,no not that one!" and so on instead of simply "draw this".

When you're starting out it's easier to write scripts that mimic doing it manually,but PIO's are not that mysterious once you can write a few things that work.

Best of luck and keep pluggin

Charles

[ 05-18-2004, 11:06 PM: Message edited by: ccroft ]

Link to comment

Hi Nicholas,

You picked a very intricate task to start your programming experience. You are trying to navigate a list of handles to objects while creating, modifying or destroying those objects along the way. Although it may seem easy, it?s not. That doesn?t mean it can?t be done, it just means you need to be exceptionally careful while doing it.

There are some caveats you should know with VectorScript. Duplicated objects do not register as "New" objects. Ungrouping an object by handle invalidates your handle to the group, and creates new handles at the level you are in. The same applies to decomposing a symbol, so you must use the group or symbol handle to find the next item in the list BEFORE you destroy the goup or symbol it points to. There are many others that you?ll collect as you continue.

It took a while for me to examine your code (entropy continues to run amok), but I think it will now do what you want. Here's the modified procedure. It may not do exactly as you intended, but you should be able to tweak it from here. It?s definitely a good example of handling objects in nested lists.

Best wishes,

Raymond

code:

PROCEDURE PolyLn;

{Draws a polyline, places a symbol at each vertex, outlines the symbols and groups the result}

VAR

pX, pY, rotationAngle, offsetDX, offsetDY: REAL;

h, g, L, L2, L3, symHd, t: HANDLE;

convertAction, index, lw: INTEGER;

fillPattern: LONGINT;

symName, add: STRING;

k, j : Handle; { new one for working inside groups }

BEGIN

{aquire active symbol & return nameSTRING}

symHd := ActSymDef;

symName := GetSDName(symHd);

{ your routine won?t run without selecting a symbol first, so I added this to prompt you. }

if (SymName='') then begin

ClrMessage;

Message ('No Symbol is selected');

SysBeep;

end

else begin

{ start poly, acquire mouse click location, place vertex and insert symbol instance }

OpenPoly;

BeginPoly;

GetPt(pX, pY);

AddPoint(pX, pY);

Symbol(symName, pX, pY, 0);

GetPt(pX, pY);

AddPoint(pX, pY);

Symbol(symName, pX, pY, 0);

{repeats as long as the shift key is held down}

while Shift do

begin

GetPt(pX, pY);

AddPoint(pX, pY);

Symbol(symName, pX, pY, 0);

end;

EndPoly;

{set poly fill and line wt}

h := LNewObj; { h points to the new polygon }

SetFPat(h, 0);

SetLW(h, 6);

MoveFront; { probably not needed }

SetDSelect(h);

{duplicate symbols and select only the duplication.

for some reason the duplicate command leaves the original selected,

unlike the in-document duplicate}

Group;

Duplicate(0, 0);

g := LActLayer; { Points to duplicated group of Symbols sans Poly }

DSelectAll;

{ the following can probably be done with less code, but sometimes brute force works

as fast, and as well as finesse. }

{ g already points to the duplicated group. Go inside the group and decompose your

symbols first. }

k := FInGroup(g);

while (k<>nil) do begin

SymbolToGroup(k, 2);

k := NextObj(k);

end; { while }

{ second pass, go back inside and ungroup everything. }

k := FInGroup(g);

while (k<>nil) do begin

j := NextObj(k); { save the next object before ungrouping this one }

hUngroup(k);

k := j;

end; { while }

{ third pass, go back inside and delete all text objects }

k := FInGroup(g);

while (k<>nil) do begin

j := NextObj(k); { save the next object before deleting this one }

if (GetType(k)=10) then { type 10 = text }

DelObject (k);

k := j;

end; { while }

{ end of brute force }

SetSelect(h); { poly }

SetSelect(g); { duplicated and decomposed group, sans text }

HUngroup(g); { Disolve the group of duplicated and decomposed symbols }

{ add surfaces and set line wt }

DoMenuTextByName('Add Surface', 0);

L := LActLayer; { Points to the original group of symbols. }

g := prevObj(L); { Points to the new AddSurface object (i.e., polyline). }

SetLW(g, 24);

SetSelect(g);

MoveFront;

end; { if/else SymName }

END;

Run (PolyLn);
[/code]

Link to comment

Thanks again for your replies,

The other night I had arrived at the sequence

L3:=ActLayer;

n:=FSObject(L3);

WHILE n<>NIL DO

BEGIN

SymbolToGroup(n,2);

n:=NextObj(n); ......

to achieve (a clunkier version of) the same thing as your

k := FInGroup(g);

while (k<>nil) do begin

SymbolToGroup(k, 2);

k := NextObj(k);

The rest I was still working on, because my script, despite not working properly for most symbols, WAS actually doing what I wanted it to for one or two of my test symbols.

Outlining, deleting unwanted bits, grouping; the whole lot!

Huh?

I'll get on to looking at yours now.

cheers,

N.

Link to comment

Well Raymond,

Your script does indeed do (mostly) what I wanted to and does set the outline weight as required; but has left me even more baffled as to why mine does not. :-D

Here (with comments in italics) is the section after the initial poly and symbols are placed (Handle;h)

SetDSelect(h);

{duplicate symbols and select only the duplication.

for some reason the duplicate command leaves the original selected,

unlike the in-document duplicate}

Group;

g2:=LObject;

Duplicate(0,0);

DSelectAll;

g:= LObject;

SetSelect(g);

HUngroup(g);

{break duplicate symbols into groups}

L3:=ActLayer;

n:=FSObject(L3);

WHILE n<>NIL DO

BEGIN

SymbolToGroup(n,2);

Ungroup;

n:=NextObj(n);

END;

{add surfaces to make outline and set line wt}

DoMenuTextByName('Add Surface',0);

DSelectObj((T=Polyline));

{remove surplus objects from duplicate symbols and leave outline.

recall polyline and symbols and group together}

DeleteObjs;

at this point my script has succesfully made the poly and symbols, duplicated them, added them to make the Outline and deleted the unwanted remains (whether text or something else) and all the components I want are present (but not necessarily selected)

L:=LSActLayer;

SetSelect(L);

SetFPat(L,0);

SetLW(L,24);

SetSelect(g2);

SetSelect(h);

MoveFront;

Group;

END;

Run (PolyLn);

After the Add surface is where the problem occurs. That is; I cant get anything to point to that poly so I can set its line weight.

Here's your code from that section;

DoMenuTextByName('Add Surface', 0);

L := LActLayer; { Points to the original group of symbols. }

g := prevObj(L); { Points to the new AddSurface object (i.e., polyline). }

SetLW(g, 24);

SetSelect(g);

MoveFront;

end;

After the Add Surface call in your script (as far as I can see from my testing); *the same elements* are present as in mine: the initial polyline, a group of symbols, and the new poly outline which has just been created by AddSurface.

In the the Function Reference it says;

quote:

Function PrevObj returns the object in any list which precedes the specified object.

If "L" points to the *original* group of symbols; and PrevObj returns the object which *precedes* L, then, ..... then, ................ I don't understand the meaning of "precede" , or "the list", or both. {:-O

And,

as well as not understanding how that call works, I don't understand why the same sequence (after AddSurface) that works in yours doesn't work in mine.

Hummm....

N.

Link to comment

Well, I may be hard pressed to explain WHY, but I'll try. Bear with me as some of it may already be obvious to you.

Lists are linear, that is, they are composed of objects that are strung together like beads on a chain, each object pointing to the object immediately before it and the object immediately after it. Lists also have a beginning and an end. When you get a handle to an object you essentially have a number, or an address, that points to that object in a list. PrevObj() and NextObj() are functions that return handles to the objects that are immediately before and immediately after the object you have a handle to, respectively. When a list has no more objects in it, PrevObj() and NextObj() return NIL, or zero, as the address of the adjacent object. NIL is the special value that signifies the end, or the beginning, has been reached.

There are many lists in your document at the same time. A list can be all the objects on a layer, in a group, or in a symbol definition. There are other lists as well that don't appear as objects drawn on the screen, such as the lists of symbol definitions, record definitions, worksheets, saved sheets, vectorscript routines, layers, etc. The objects in these lists can be viewed in separate windows such as the Resource Browser, command palettes, etc.

Once you have a handle to an object in a list, any list, you can navigate that list forward or backward using NextObj() or PrevObj() respectively. For objects that appear in the drawing, the corresponding list is ordered in drawing order. For example, the first object in the list of objects on a layer is the first object drawn, and is the object on the bottom of the layer. The list continues to the last object which is the last object drawn and it appears on top of all other objects on that layer. This is most easily seen when the objects overlap. Layers are similarly structured, with the bottom layer drawn first and the top layer drawn last.

Having said all that, let me answer a question you didn't explicitly ask, "How did I know that the AddSurface object would be before the group of symbols?" Short answer, "I didn't." I guessed. Since it was not on the top of the list, which is where I would have expected it, I went hunting for it. I ran your program many times, and stopped at various points displaying the values of handles in your code and compared them to the value of the AddSurface poly.

This is what I deduced about the AddSurface command: It appears that the resultant poly will be located in the stacking order at the same level as was the lowest selected object, prior to the AddSurface command. This makes it difficult to find if you don't know this. In your case, since all objects are created by your script, they are all on the top of the list, or the top of the active layer, and therefore easy to locate, relatively speaking.

A simple script I use to display the handle value of a selected object is:

code:

procedure ShowHandle;

{ Display the value of the first selected object's Handle. }

{ The value will vary each time the file is opened. }

VAR

H :Handle;

BEGIN

H := FSActLayer;

Message(H, ' Type = ', GetType(H));

END;

Run(ShowHandle);
[/code]

In your code you write:

DeleteObjs;

L := LSActLayer; { L = NIL. }

SetSelect(L); { Not needed. If L<>Nil, L is already selected, based on previous line }

Because the previous line de-selects all objects, LSActLayer will return NIL, as nothing is currently selected.

Sometimes it is easier to get the handle you want before you perform a complex operation. You already have a handle to the original poly, h. Your duplicated poly is consumed In the AddSurface as are the remains of the decomposed symbols. All that is left is the new Poly, the original group of symbols and the original poly. It has to be one of the last three objects on the active layer. So, the new surface is either the last object, or the previous one. The original poly is the lowest object, since you never moved it since creating it. You can specify a handle to the new surface as either NextObj(h) or PrevObj(LActLayer). Either way, it is the second from the end.

One thing that needs mentioning, earlier in your script you use g := LObject. This will only work IF the active layer is also the topmost layer in the drawing. You should restrict your calls to ones that return handles to objects on the active layer, otherwise your code may not work if you change you layer stacking order.

To really see how your code runs, use the Debugger or use Message() to display the values of handles and other variables in your code. You can comment out the remaining code with (* and *). These comments delimiters work with {...} defined comments between them.

HTH,

Raymond

Link to comment

Well, I'd like to thank you Raymond,charles, et al for helping me understand all this. I took the Beta script in today to the firm that I'm currently contracting to, and they were well pleased to see that the cumbersome manual process they had been using *might* soon be "automated".

BTW, the version which worked on my Mac didn't work properly on the Dell XP; VW10.0 machines at their office. I had a quick look and implemented your suggestions, in a couple of spots, to assign handles *before* grouping/adding/etc, and also to avoid calls (egLObject) which don't explicitly operate on the active layer. After that it behaved on the XP box.

quote:

For example, the first object in the list of objects on a layer is the first object drawn, and is the object on the bottom of the layer. The list continues to the last object which is the last object drawn and it appears on top of all other objects on that layer.

This was my assumption; and the mental picture I had was that the AddSurface polyline had only just been created, so therefore it was on top of the list (stack), and; the group had been created some steps ago, so therefore it was well down on the list. Hence my Cognitive Dissonance trying to imagine how something which had just been created could be "previous" to something which was created prior to itself. {:-O

quote:

"How did I know that the AddSurface object would be before the group of symbols?" Short answer, "I didn't." I guessed. Since it was not on the top of the list, which is where I would have expected it, I went hunting for it. I ran your program many times, and stopped at various points displaying the values of handles in your code and compared them to the value of the AddSurface poly.


I'm most grateful that "boneheaded persistance", (and generosity of spirit) is well in evidence.

I also *attempted* to message the type of object I was getting at various stages of the script but mucked up the call and rather than try to fix that, resorted to commenting out the following script instead. This was how I determined that my script was producing the "same objects" at the point after the AddSurface.

quote:

To really see how your code runs, use the Debugger

I used this quite a bit also, but (unless the Handle values can be interpreted) all I could tell was if *an object* had aquired a handle and if it was different from another handle. (one early hint was that there was the same handle for objects which were supposed to be different)

quote:

You can comment out the remaining code with (* and *). These comments delimiters work with {...} defined comments between them.


That was another I worked out; It doesn't like {....{comment}..}.

I got around that with more brackets.

All I have to do now is ...............learn how to rewrite it all to make a proper Plug-in! {:-?

cheers,

N.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...