Jump to content

Jesse Cogswell

  • Posts

  • Joined

  • Last visited

Everything posted by Jesse Cogswell

  1. I wrote up a menu command that will do this. When you run the command, it will detect whether your active layer is a Design Layer or a Sheet Layer and will then launch a dialog box listing either Design Layers or Sheet Layers and will allow you to select the ones you want to paste to. When you click OK, it will paste the contents of your clipboard in place on the selected layers. To install the plug-in, follow the instructions below: Download the attached file Paste On Multiple Layers.zip to a convenient location In Vectorworks, click Tools - Plug-ins - Plug-in Manager Click on the Third-Party Plug-ins tab Click on the Install button Select the downloaded .zip file Click OK Restart Vectorworks Click Tools - Workspaces - Edit Current Workspace Click on the Menus tab In the box on the left, find the category JNC and expand it In the box on the right, expand the destination menu (such as Edit) Click and drag Paste On Multiple Layers from the left to the destination menu on the right Click OK Let me know if anything weird happens. I tested the command out in both Vectorworks 2019 and 2021 and it seemed to behave itself. Paste On Multiple Layers.zip
  2. Okay, I think I got it all sorted. I'm not sure about the Space stuff, I have Spotlight, so the Space tools weren't even in my workspace until this morning. I've never used them on a drawing before, so my experience begins and ends with the 20 minutes of messing around with them this morning, and I'm not quite sure what you mean about contour lines being flipped with rotated labels. I tested putting a Space with a rotated label in a symbol and making it unique, but could not reproduce the text moving around. You're on your own with those. I did add code to only work on symbols on the active layer, so you shouldn't have any trouble with the tool seeing an unintentional symbol. I also patched a nasty bug where running the command on a symbol embedded in another symbol would still reference the parent symbol, creating an infinite loop when you click OK. That should be fixed now. I tested things pretty extensively in both VW2019 and VW2021 and couldn't make anything weird happen, so with any luck, this tool is finished. Make Symbol Unique.zip
  3. It's a slow day for me today, so here goes a quick crash course in Vectorscript. I'm going to try to keep things in a logical order, but between how Vectorworks handles plug-ins, standard coding practices, and Vectorscript structure and syntax, there's an awful lot to cover. I have built what I think you are looking for, and will attach the code and also walk you through it so you understand what it is doing. I am going to write all of this down rather than doing a video. Personally, I find text instruction to be far more useful than video instruction, as it lets you move at your own pace and hopefully won't make you feel rushed. HOW VECTORWORKS DEALS WITH PLUG-INS: Any plug-ins that you install or build yourself are going to be stored in your Vectorworks User Folder in the Plug-ins subfolder. The quickest way to track this down is open up your Vectorworks Preferences. In the User Folders tab, click on the button labeled Explore (Windows) or Open in Finder (Mac). This will open up an Explorer/Finder window with your user folder. From there, open up the Plug-ins folder. When you create a new script through the Plug-in Manager, it will be stored here. Likewise, if you want to install a plug-in, you can drop the .vso (plug-in object), .vsm (menu command), or .vst (tool) into this folder. This folder is scanned when the program first boots up, so after installing a command, make sure you reboot Vectorworks if the program is already running. No need to restart if you create a new script, however. TO PLACE PLUG-IN INTO WORKSPACE: Installing the plug-in is only half the battle. You will need to make sure that it is part of your workspace before you can start using it, even if it's a plug-in you've created yourself. To do this, follow the instructions below: Open up the Workspace Editor by clicking Tools - Workspaces - Edit Current Workspace. This will open a dialog with all installed menu commands/tools shown in the box on the left, and where they appear in your workspace in the box on the right. Menu commands will be found in the tab marked Menus, while plug-in objects and tools will be found in the tab marked Tools. In the box on the left, find the category of the tool (we'll cover this in just a little bit) and expand it. In the box on the right, find where you want the script to live in your workspace and expand it. Click and drag the plug-in from the left box to the right box. Be mindful of "nesting", it can be really easy to accidentally nest a tool inside another tool, requiring you to right-click the parent tool in the tool set to get to the nested one. Click OK. This will reset your workspace and add the new tools. PLUG-IN TYPES: Before we get to plug-in creation, we should chat about what the different types are and where to use them. COMMAND: This is a menu command, designed to be placed in the main menu bar at the top of the screen. These generally execute a series of commands in a similar fashion as a macro. These commands might manipulate selected objects (such as Convert to Group, Add Surface, etc), create geometry (Create Roof), or any number of other things. TOOL: The Tool type plug-ins will appear in a tool set in your workspace and are designed to manipulate existing objects, usually requiring that you click on the object after launching the tool. Examples of this are Select Similar (magic wand tool), Trim Tool, Offset Tool, etc. I personally don't write tools very often, but it's a pretty common practice to use a Tool type plug-in to place a Plug-in Object type plug-in. This can be seen the Door and Window tool. I'm not entirely sure what the advantage of doing so is, but maybe someone else can explain it. PLUG-IN OBJECT: Usually abbreviated to PIO, this is the type that creates the parametric objects that you are looking for. This type encompasses: Point Objects: Objects with a single insertion point. They will not have resize handles, any changes will need to be made from the Object Info Palette. These would be things like Data Tags, Windows, Doors, etc. Linear Objects: Objects needing two clicks to insert. These will have two resize handles, one at each side of the "line" of the PIO. This will automatically generate "LineLength" parameter (more on this in a bit). These include things like lines, Walls, and Dimensions. Rectangle Objects: This is the type that we will be using for your plug-in. Keep in mind that these do not function in the same manner as the Rectangle Tool. Rather than clicking a top-left corner and a bottom-right corner, you will instead click once to determine a starting point, a second time to determine width, and a third time to determine height. No idea why it works that way other than that it is likely left over from the original days of Vectorworks/MiniCAD. In any case, once an object is created, you will have the full set of resize handles at your fingertips. 2D Path Objects and 3D Path Objects: These work in the same fashion as the Polygon and Polyline tool, multiple clicks to form a path. I've never built a path-type plug-in, so if you have any questions, you'll have to ask someone else. INCLUDE FILE: This is a text-based resource file that can be externally referenced. I use these commonly to store dialog box settings and data that I don't want hard-coded into the plug-in. CREATING A NEW PLUG-IN: Okay, with all that out of the way, let's get started on actually building a plug-in. Open up the Plug-in Manager by going to Tools - Plug-ins - Plug-in Manager. You'll be opening this a lot, so I recommend memorizing the shortcut keys (Ctrl++Z on Windows). This dialog box has three tabs: Custom Plug-ins: Any plug-ins you create will appear here. Also, any installed plug-ins that are not encrypted will appear here as well. These plug-ins are able to have their source code edited. Third-party Plug-ins: These are plug-ins found in your Resource Folder that are encrypted. You will be able to change definition data, but won't have access to the source code itself. This tab is also where you will find the "easy" Install button. More on that later. Built-in Plug-ins: These are the plug-ins that come with your install of Vectorworks. They will be encrypted and have their source code locked. For what we will be doing, make sure you are in the Custom Plug-ins tab. From there, click on the New button. This will launch a dialog asking us what type our plug-in wants to be. For the purposes of building your plug-in, select Rectangle Object and give it a name. Click OK. This will create an empty plug-in. Before we can get to writing the script, select the plug-in and click on the Edit Definition button. This will allow us to set up the parameters that will appear in the Object Info Palette. In the General tab, select a category for the plug-in. This is what sets the category in the Workspace Editor. Typically, I will use my initials (JNC) for any custom plug-ins that I write, or will put it in a category of "Research and Development" if it's a plug-in I'm testing things with. Open the Parameters tab. This is where we will build the parameters found in the Object Info Palette. You will notice that two of those already exist, LineLength and BoxWidth. as those are automatically generated for Rectangle Objects. This window has five different columns: #: This is order in which the parameter will appear in the OIP. This number is also the number used to correlate with the parametric record of the plug-in (more on this later). Name: This is the name that we will use in the code to pull the parameter data from. The naming of this should follow general variable naming conventions, namely that it wants to start with a letter and have no spaces in it. If you want a parameter to not appear in the OIP, you can add a double underscore (__) to the Name and it will be hidden. This is common when using parameters to store background data that the plug-in needs, but the user doesn't need to know about. Alternate Name: This is what the parameter will appear as in the Object Info Palette. This allows a plug-in to be localized without having to change the source code directly. Type: This will show what the parameter's type is. This can be the direct primitive types (explained later), which would be entered through a straight field in the OIP, a boolean type (checkbox in the OIP), or even pull-down menus and radio buttons in the OIP. Default: This is the what the parameter will populate by default when first being dropped into a drawing. For anything with numbers, this field must be populated with something. Select the first parameter, LineLength and click on the Edit button. We'll want to enter something friendlier in the Alternate Name field, I recommend "Width" if building this tool in English. I understand that this seems wrong with the parameter name being called "LineLength", but it's a quirk of Rectangle Tools, the naming is backward. Click OK. Select the second parameter, BoxWidth and click Edit. Change the Alternate Name field to "Height" and click OK. Now, we need to add in the ability set the indent of your shaded polygon. The best way to achieve this is with a Control Point. Control Points act as added reshape handles as well as fields in the OIP. With that in mind, click on the New button. Control Points will automatically fill in the Name field, so don't worry about filling in that information. Select Control Point from the Type drop-down menu. Put something friendly in the Alternate Name field, something along the lines of "Indent." Put something in the default fields, I used 1 for each. You just want to make sure that it's not 0, as the reshape handle will end up on top of one of the rectangle's reshape handles in this case. Click OK. When you're done, you should have something that looks like this: For now, we can ignore the other tabs. If I remember, I'll add in a bit at the end explaining what each of them are used for. Click OK to close out the dialog window. NOW WE NEED TO TALK ABOUT CODING: Since you are new to coding, there are some concepts that you need to understand before you get started. Generally, Vectorscript does not care about white-space. You are free to indent where ever you want and are allowed to put as many spaces around things as you want. All lines need to be terminated with a semi-colon (for the most part...but we won't get into that here). Compartmentalized things like loops and Procedure/Function definitions are set between BEGIN and END statements. Any line encompassed by curly brackets ({}) is considered a comment and is not compiled. This is handy for adding notes, or temporarily removing lines of code for debugging purposes. The biggest thing to get your head around is the concept of variables. Variables are words that represent a piece of data within the code. These can generally be named however you like as long as they don't start with a number (can't have a variable named "1str", for example), aren't one of the "reserved words" (like "PROCEDURE", "CONST", "VAR", "BEGIN", "END"), don't conflict with an existing Function or Procedure (can't name a variable "Rect" because that would step on the Rect() procedure, but "Rect1" would be okay), and don't contain spaces. There are no hard and fast rules to this, it's whatever you need to make the code legible for you or anyone else needing to read it. My best advice is to just be consistent across the code as best as possible. Some conventions to consider adopting: Camel-case: This is a pretty common way of handling naming in code. For variables, the convention is to start with a lowercase letter, and only capitalize the first letter of each new word (since you can't have spaces in a variable name). An example would be something like "drwOrigin" or "thisIsMyVariableName". Procedures and Functions would be named similarly, only with a capitalized first letter, like "ThisIsMyProcedure". Vectorscript DOES NOT care about capitalization outside of strings, so it's mostly about legibility for the user, it won't care if you type "thisismyvariablename" or "thisIsMyVariableName", but someone trying to untangle your code will. Hungarian Notation: This is a pretty common way of naming variables in which the first letter defines what type the variable is. A string might be named "sMyString" while a number might be named "nMyNumber". For the purposes of demonstration, my example code will feature Hungarian Notation, but I generally don't use it, but only because I never got in the habit. It's a really handy way of keeping track of your variable types, so I would recommend implementing it. So, variables can be of any of a number of Primitive Types. These are the fundamental building blocks of the script. INTEGER: These are simple, non-decimal numbers in a range of -32767 to 32767. These are great for counting things and any operation that wants a whole number. LONGINT: These are whole numbers that exceed the range of an INTEGER. The range is great, between -2147183647 and 2147183647. These are commonly used in Vectorscript to store 16-bit numbers (for colors) and unique identifiers. REAL: These are your decimal numbers (known in other languages as floating-point values), and primarily are used for things like dimensions. STRING: A string is a collection of characters forming things like words. These are defined in code using single quotes, as in 'This is a String.' Back in the day, these used to be restricted to 255 characters, I'm not sure if that's been expanded since. CHAR: A single ASCII character. BOOLEAN: A data value that can be set to be either TRUE or FALSE. HANDLE: This is the hardest (at least it was for me) thing to wrap your head around. A HANDLE is a reference to an object that is needed for most functions in Vectorscript. In other languages, these would be referred to as "pointers." Since Vectorscript is not object-oriented, HANDLEs are used to specify what objects to run code on. The Document List Handling section of the Function Reference is going to be the best place to learn the different ways of pulling HANDLEs. VECTOR: These are complex little structures that are used for complex geometric computations. My calculus and trigonometry aren't the best, so I usually have to spend a lot of trial and error time to get these working properly. Essentially, each VECTOR is a Structure containing three REAL values, accessed by calling <variableName>.x, <variableName>.y, and <variableName>.z. I'll try to clarify that all in a bit. POINT: This is a 2D point, consisting of an X value and a Y value, both of which are REAL values. These discreet values are accessed by typing ".x" or ".y" after the variable name. For example, let's say we have a Point named "myPoint." The call to get to the X value would be myPoint.x and the Y value would be MyPoint.y. POINT3D: Same as the POINT type above, except it has an additional Z value. RGBCOLOR: A data structure containing three 16-bit LONGINT values, one for red, one for green, and one for blue. These are accessed in a similar manner as the POINTs above, with <variableName>.red, <variableName>.green, and <variableName>.blue. Variables in Vectorscript must be declared before they may be used, and them must be declared in the VAR block of code (explained below). Syntax for declaring is listing the name, the type, and separating with a colon. So, a STRING named "myString" will be declared with the script myString : STRING. Multiple variables of the same type can be declared at once, using a comma as a separator. As in, myString, alsoMyString : STRING. Once a variable is declared, it must be defined. This can happen anywhere in the code, but is recommended to be done before the variable is needed. If a variable is not defined, it will be set to something of a default state (strings will be set to '', numbers set to 0, handles set to NIL). To define a variable, the syntax is the variable name followed by a colon, then an equal sign, then a value. For example, a string would be defined with myString := 'blah blah blah'. A number could be defined as myInteger := 5. To pull the parameters that we set up earlier into the script as a variable, the syntax is a capital P followed by the parameter name (as found in the Name column on the Parameters tab. This means that if we wanted to pull the LineLength into a REAL variable named rWidth, the code would look like rWidth := PLineLength; Contant values are similar to variables, except that they are defined at the very top of the program and cannot be changed after being defined. They also do not need to have a type specified, it's handled in the definition. Syntax is the name followed by an equal sign, followed by a value. A typical definition looks like this kMyConstant = 0; Typical naming conventions for constants are to capitalize the whole name (as in MYCONSTANT), or to have a lower-case k in front, as in the example above. These are used for values that don't want to ever change in the code, such as preference selectors, parameter indexes, computational constants, etc. Next, we'll want to talk about Functions and Procedures. These are subroutines that you call to execute lines of code. Basically, you pass in variables (referred to as Arguments) into the call, and the code executes. Think of them as modules that are built to do a single task. They both fundamentally operate the same way, with the only major difference that a Function is designed to return a value. Consider the code below, the Procedure takes in two values, adds them together, then prints the text of the sum. The Function takes the same two values, adds them together, and returns the sum. PROCEDURE PrintSum(val1, val2 : REAL); BEGIN Message(val1 + val2); END; FUNCTION SumValues(val1, val2 : REAL) : REAL; BEGIN SumValues:= val1 + val2; END; The syntax for defining a Procedure is PROCEDURE ProcedureName (<insert arguments here>); The syntax for defining a Function is FUNCTION Function Name (<insert arguments here>) : <return type>; Arguments need to have their types declared, with different types being separated by a semi-colon. For example: PROCEDURE (myString1, myString2 : STRING; myInt1, myInt2 : INTEGER); The syntax for the return value of a Function is to use the Function name and to define a value for it, as shown above. VECTORSCRIPT CODING STRUCTURE: Now that we're starting to look at code, let's look at how a standard Vectorscript program is structured. Vectorscript is very rigid in how things have to be structured, so every script will be structured the same way. We think of the code broken down into different "blocks". While not every block needs to be present in the code, if you need constants then you must have a CONST block, if you need variables then you must have a VAR block. The only blocks required for the code to run are the Plug-in Definition, the Main Driver Block, and the Run Statement. Basic structure is as follows, example code in bold: __________ | Plug-in Definition: | PROCEDURE MyPlugin; |__________ | Constant Block: | CONST | kMyConstant = 'blah'; |__________ | Type Block: I'm not going to get into this one here, it's a very handy thing but outside the scope of this tutorial. More info can be found in the Language Guide | https://developer.vectorworks.net/images/7/72/VectorScriptGuide.pdf |__________ | Variable Block: | VAR | myString1, myString2 : STRING; | myNum1, myNum2 : REAL; | __________ | Embedded Procedures and Functions Block: | PROCEDURE PrintSum (val1, val2 : REAL); | BEGIN | Message(val1 + val2); | END; |__________ | Main Driver Block: | BEGIN | myNum1 := 5.5; | myNum2 := 3.1; | PrintSum(myNum1, myNum2); | END; |__________ | Run Statement: | Run(MyPlugin); |__________ All Procedures and Functions follow this same structure. It is even possible to embed Procedures and Functions inside other Procedures and Functions, though it's really bad practice outside of some necessary instances involving dialog box creation. Any variables declared in the main Variable Block at the top of the script are what we think of as global variables. These variables can be manipulated by any embedded subroutine as well as the main driver block. Variables declared within a Procedure or Function definition are only accessible within that definition, and are known as local variables. Generally, good coding practice is to avoid manipulating global variables outside of the main driver block, instead passing variables in to subroutines as arguments and returning the results. As an example from above, the PrintSum definition could instead look like this: PROCEDURE PrintSum; BEGIN Message(myNum1 + myNum2); END; The code won't stop you from doing this, but it makes the code much less modular and is considered bad practice. The final note about structure is that Vectorscript needs to be compiled before it can run. This means that the code is transferred into assembly language so that the computer can run it. To compile your script, click on the Gear icon in the Script Editor. This will highlight any errors and give you a line number to look at in order to fix the errors. What's important to note is that the code is compiled from top to bottom. This means that if you have an embedded Procedure calling another embedded Procedure, that called Procedure must appear above the call, otherwise the compiler won't know how to interpret the call. That being said, the code itself is executed as it is called, so it would start executing the Main Driver Block first, only executing embedded subroutines if and when they are called. NOW FINALLY, CODING YOUR EXAMPLE: You want to have something of an idea of the nuts and bolts of what you need the script to do. In your example, it needs to produce a rectangle as defined by the Rectangle Object plug-in type, then it needs to create a polygon using three of the points of the rectangle and a fourth indent point. Something to keep in mind with the Rectangle Object plug-in type, is that the insertion point of the plug-in (considered its origin), is on the left-most side at the center of the object, not the upper left most point. So any computations based on the Height parameter need to keep that in mind. Usually when I think about polygons, I label each needed point in the order that the polygon would be created. With that in mind, here's your sample: So the code essentially needs to compute out points A-D, then use those to build a rectangle and polygon. First thing's first, we'll want to open the Script Editor. Do this by opening up the Plug-in Manager, selecting your new plug-in, and clicking the Edit Script button. This will open what should be a blank page. Make sure that the language is set to Vectorscript, and you should be good to go. The button in the upper left is something of a shortcut to Procedures/Functions from the Vectorscript library, a criteria builder, a parameter selector, and a text file browser. I don't use it very often, but I can see some value in it. The button to the right of that that has a gear on it is the Compile button. Press this to make sure that there are no errors in your code. All code used below that have listings in the Function Reference have links to their respective pages in that reference. I HIGHLY recommend clicking on those and getting comfortable with how the Function Reference is formatted. So, in the Main Variable Block, I'm going to declare four POINT variables (pA, pB, pC, and pD), two REAL variables (rWidth, and rHeight), and a HANDLE variable (hObj). Then, in the Main Driver Block, I'm going to: Pull in parameters and set them into variables using rWidth := PLineLength; and rHeight := PBoxWidth; Compute the four points pA: X is easy as it is just the origin (0), so pA.x := 0; Y will be equal to half of the height, so pA.y := rHeight / 2; pB: X will be equal to the full width of object, so pB.x := rWidth; Y will be the same as pA, so rather than computing it out, I can call pB.y := pA.y; pC: This one is a little trickier, it will be defined by the Control Point parameter we put in. The call will be pC.x := PControlPoint01X; and pC.y := PControlPoint01Y; pD: X will be the same as pA, so pD.x := pA.x; Y will be 0 minus half of the height, so pD.y := 0 - (rHeight / 2); You could also do pD.y := pA.y - rHeight; Now that the four points have been computed, I'm going to call the CreateArielusObject function, using the returned HANDLE to define variable hObj. The CreateArielusObject function will do the following: Pull in four points as arguments, accessed using the local variables p1, p2, p3, p4. With the points we passed in, p1 has the same data as global variable pA, p2 has the same data as pB, etc. In the Local Variable Block, we're going to declare an RGBCOLOR variable, clrPen, to set the color of the polygon component, as well as a temporary HANDLE, h. Define a group using the BeginGroupN procedure. This procedure is a little different in that it is set to return a value through its arguments, done by placing VAR in front of the argument in the definition (you'll see that if you click on the link to the Function Reference). So once the group is complete, variable h will now be a HANDLE pointing to the group. The full call looks like BeginGroupN(h); As a demonstration of where the insertion point of the object is, I'm going to place a Locus Point at 0,0 using Locus. Full call is Locus(0, 0); Next up is creating the rectangle using Rect. This procedure takes in the upper-left and bottom-right points as arguments to build a rectangle, but as four separate REAL values. While we don't have a variable for the point of the bottom right corner, we can get it from points p2 and p4 (same as pA and pD). Full call is Rect(p1.x, p1.y, p2.x, p4.y); This rectangle will have the same visual attributes as the plug-in object. So, if we want to pull the pen color to use as a fill for the polygon we're going to make next, we'll want to use the GetPenFore procedure to pull the data. This procedure takes in a handle, and returns the red, green, and blue color values as 16-bit LONGINT values. In order to get a handle to the rectangle, we'll use the LNewObj function, which returns a HANDLE to the most recent new object. We can combine the two calls into a single line of code with GetPenFore(LNewObj, clrPen.red, clrPen.green, clrPen.blue); Next, we'll want to create our polygon. When thinking about 2D objects made up of different components, it's best to build from bottom to top. Before we build our polygon, we'll want to use the ClosePoly procedure to ensure that our polygon will be closed. By default, the script will create open polygons, so the last line will be missing. Next, we'll use the Poly procedure to create our polygon. This takes in an unlimited number of pairs of REAL values (one for X, one for Y) to create a polygon object. With that in mind, our call is going to be Poly(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); Then we'll want to apply the pen color from the rectangle as the fill color of the polygon using the SetFillBack procedure. This takes in a HANDLE and three LONGINT values to set the handled object's fill color. We'll use LNewObj again to get the HANDLE to the polygon. SetFillBack(LNewObj, clrPen.red, clrPen.green, clrPen.blue); We'll use EndGroup to close out the group. After this point, local variable h will now be a HANDLE to both the rectangle and polygon objects grouped together. The very last thing we'll need to do is to set the returned value for CreateArielusObject to be the same as h, using CreateArielusObject := h; And that's the code. Typically for Functions, I will almost always have a temporary local variable with a short name (like h) to do calculations with, making the last line of code for the Function used to define the Function return as that temp variable. The full code for the plug-in should look something like this: PROCEDURE ArielusTest; {* Parametric demonstration for Arielus Developed by: Jesse Cogswell Date: 7/29/2021 VW Version: 2019 Revisions: *} CONST {Constant Block with examples, not used in the code} kExampleNum = 1; kExampleString = 'This is an example'; VAR {Main Variable Block, these are Global Variables} pA,pB,pC,pD : POINT; rWidth,rHeight : REAL; hObj : HANDLE; FUNCTION CreateArielusObject(p1, p2, p3, p4 : POINT) : HANDLE; {Embedded subroutine} {Creates parametric object using given points and returns handle to object} VAR {Local Variable Block} clrPen : RGBCOLOR; h : HANDLE; BEGIN BeginGroupN(h); {Everything between BeginGroupN and EndGroup will be placed in a group together, with handle argument being the handle to the group} {Create Locus Point at Origin to demonstrate insertion point} Locus(0, 0); {Create rectangle} Rect(p1.x, p1.y, p2.x, p4.y); {Rectangle defined by upper left corner and lower right corner} GetPenFore(LNewObj, clrPen.red, clrPen.green, clrPen.blue); {Pull pen color of rectangle} {Create polygon} ClosePoly; {This command instructs the next polygon that it will be closed} Poly(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); {Polygon defined by four points, A-D} SetFillBack(LNewObj, clrPen.red, clrPen.green, clrPen.blue); {Pen color applied as polygon fill color} EndGroup; {Set return value as handle to created group} CreateArielusObject := h; END; BEGIN {Main Driver Block} {Pull in parameters into variables} rWidth := PLineLength; rHeight := PBoxWidth; {Compute points A-D} pA.x := 0; pA.y := rHeight / 2; pB.x := rWidth; pB.y := pA.y; pC.x := PControlPoint01X; pC.y := PControlPoint01Y; pD.x := pA.x; pD.y := 0 - (rHeight / 2); {Call Function to create object} hObj := CreateArielusObject(pA, pB, pC, pD); END; Run(ArielusTest); {Run Statement} If you want to look at the code in the actual IDE (very highly recommended, as the text will color-code itself which makes things easier to read), download the attached file (Arielus Test.vso) and place it in the Plug-ins subfolder of your User Folder in the process outlined above. Restart Vectorworks, then the plug-in should appear in the Custom Plug-ins tab of the Plug-in Editor. Then you should be able to select it and press the Edit Script button to open it up in the Script Editor. That about does it from me. That's a lot of information and I hope you find it useful. Keep in mind that this barely scratches the surface of what Vectorscript can do. It really requires a lot of trial and error and frequent reading of the Function Reference. I also HIGHLY recommend going through the Vectorscript Language Guide, found here: https://developer.vectorworks.net/images/7/72/VectorScriptGuide.pdf. It's the actual instruction manual on all of this and it will have a lot of examples of code. Please let me know if you have any questions. This is a lot but I invariably missed some things. Arielus Test.vso
  4. @Pat Stanford No kidding, Python can be a huge pain to debug because of the incredible freedom it gives you and it being a run-time language. For all its rigid quirks and archaic rules, Vectorscript is at least pretty forgiving and easy to debug (as long as you don't cause a memory leak leaving out a parenthesis in a ForEachObject call...), especially after 2016 when they finally added in line numbers to the IDE. @Arielus Putting "Vectorscript" into the search field in YouTube does yield some results, but there doesn't appear to be a full tutorial. Coding in general is something that I think video tutorials are not super great at, as scripting is a written medium as opposed to a visual one. If you are looking for a more visual focused method of object creation, I would suggest trying out Marionette, of which there are several training videos from Vectorworks (like this one here). I find Marionette clunky and slow since I can type much faster than clicking and dragging variables around, and Marionette objects run slower than scripted plug-in objects (though this did start getting much better starting in VW2020). I think jumping in to Vectorscript without any coding experience might be a bit daunting, especially because of its relative lack of "beginner friendly" documentation. I would suggest looking into an online course for a more common language to learn the basics of coding such as C, C#, or Java. Python is a tricky first language because of the way it "bends" a lot of the rules of programming. It was designed as a language that could be picked up in a day with any experience in coding, but it could teach you some bad habits that would hamper you in working with other languages.
  5. That could be a bug with the new expanded criteria for choosing objects, it used to just use the first selected object on the active layer, but with adding functionality to do multiple selection, I had to change it. What is likely happening is that the script is seeing a symbol that is selected on a different layer with your Layer Options set to Show/Snap but not Show/Snap/Modify. That's one of the tricks with Vectorworks and Vectorscript, you can have an object selected somewhere you don't know about, and the script may execute on something you don intend to run it on. Do me a quick favor, click Tools-Custom Selection. Select Deselect and Execute Immediately and click OK. For the criteria, select All Objects (should be the last item in the leftmost drop-down menu) and click OK. Then try the script again. If it works as expected, I'll add in some code to narrow the criteria to only symbols on the active layer, which will limit the functionality of the code for the heathens that draft with layer options set to Show/Snap/Modify.
  6. Vectorscript, which is itself based on an old language called Pascal, can be a bit obtuse (the biggest hurdle I had coming from Java and Python was figuring out how to properly use handles), but there are a lot of code examples that can be found on the online wiki as well as the offline Function Reference. On a Windows machine, that function reference is stored in C:\Program Files\Vectorworks ####\VW Help\Script Reference\ScriptFunctionReference.html. I assume it's stored in a similar manner within the application folder on a Mac. While I'm working on scripts, I always have it open somewhere, usually on a second monitor. It's faster to navigate than the online wiki, and you don't need a connection for it to work. For Python, there are a ton of online resources to teach yourself the language, but the implementation in Vectorworks is a bit tricky. You will still need to use the commands found in the Function Reference to interact with VW, but you will gain far greater control in terms of how you handle data. I learned Python several years before it was available in Vectorworks, and by the time they implemented it, I was more comfortable with Vectorscript, so that's primarily what I work in. If you do want to go through the process of learning Vectorscript, an excellent starting point would be the Vectorscript Language Guide, which used to be part of the main VW help but has since moved off to this separate document. It's what I used to teach myself back in 2010, and it's pretty easy to follow as far as these things go.
  7. @MarcelP102 Thank you for pointing out the bugs with mirrored symbols and layer elevations. After fighting some of the visual anomalies caused by mirrored symbols (especially in earlier versions), I have been in the habit to duplicate symbols and mirror the 2D and 3D geometry rather than mirroring the symbol instances, so it didn't even occur to me when writing the script. Likewise, with 90% of my drawings being done for theatre where every Z dimension wants to relate to the stage floor, I also don't use layer elevations terribly often. Both of these issues have been patched and tested. As for having the command replace multiple symbol instances, I was able to come up with a pretty simple way to implement that. The script will now build a "Unique" version of the TOP-MOST symbol in the stacking order. It will then search out symbols with the original symbol name within the initial selection, and replace them with the new unique symbol, and will transfer all record formats. I've pretty well tested the script, and everything appears to be working correctly, but let me know if you see anything else. @Pat Stanford is very right in pointing out scope-creep. It can really easy to make requests that seem really simple, but scripting in Vectorworks can be really obtuse. There are a lot of functions that don't really exist. There's no simple "Replace" command to replace one symbol with another, all of that has to be coded from scratch (pull location, rotation, symbol option, and record data from original symbol, place new symbol at same location and rotation, move the new symbol based on User Origin and Layer Elevation, set symbol options, set record data, delete original symbol). I don't always mind helping out or adding features to scripts, as scripting is a nice side activity to work on as a break from drafting and designing (it scratches a very different itch), and I appreciate the challenge. This script in particular has been a godsend for my own work over this last week, and I would probably not have gotten fed up enough to script it out had I not seen someone asking for it on this forum. And if I can add some QOL updates to Vectorworks while they're busy not fixing the features they keep adding, all the better. That being said, I don't hate money. If you feel that some of my tools are worth paying for, PM me and I will give you my PayPal handle. Make Symbol Unique.zip
  8. Got it. I originally tried to keep things a bit simple by using FSActLayer to get the handle to the selected symbol, but that won't work on symbols inside symbols or with symbols inside viewport annotations. I've amended it using a ForEachObject call instead, which will now be able to pull the symbol correctly from inside other symbols and inside viewport annotations. Let me know if you find any other instances where it doesn't work the way it should. Make Symbol Unique.zip
  9. You can select the truss and right-click on it and select Send - Send to Back or use the shortcut (Ctrl+B on Windows, Command+B on Mac). What I commonly do is put lighting positions and fixtures on separate layers so that there is no possibility of having the position go on top of the fixtures.
  10. After further testing, CreateDuplicateObject DOES INDEED work for duplicating symbol definitions, just not in Vectorworks 2019 where I was developing the original script. I made a quick test file with the code Pat posted above, and it would not work in 2019 at all. Opened the exact same file in 2021 and it worked just fine in duplicating symbol definitions. I'm going to keep the original code as is to maintain backwards compatibility.
  11. Do you have something selected? The tool will not work if nothing is selected, and will be greyed out in the menu. If you do have something selected when trying to run the tool, then we have bigger problems. I don't think "Get Viewport Location" or "Set Viewport Location" are mine, but they likely have a similar issue.
  12. @Boh Retaining the original record defaults was the idea, but I can see how overwriting the defaults could also be useful. I've added some code so that if it detects that records are being transferred, it will poll whether you want to overwrite the defaults using the current data. It will ask you for each attached record, so if you don't want to overwrite all of the records, you don't have to. Updated plug-in attached. Make Symbol Unique.zip Make Symbol Unique.zip
  13. @drelARCH & @Boh thanks to some ideas from @Pat Stanford, I was able to overhaul the way the plug-in works and come up with a way to keep attached records and retain the linked text. It should work with RM tags, but I haven't really tested them, but any record formats and data should come through. I did a quick test with page-based symbols, and things appear to be working fine, but let me know if you still see anything weird. Updated file attached. To install it, follow the same instructions above through step 7 (assuming you haven't moved the tool around in your user folder. If you have, delete the .vsm file for it before installing), no need to edit your workspace again. Make Symbol Unique.zip
  14. Okay, so I started writing up a whole thing how none of the duplicate commands in the function reference apply to symbol definitions, as those were the things I tried first. But in the process of writing that up, I got an idea to "crawl' through the selected symbol and use CreateDuplicateObject with each object in the symbol. After some tricky coding to get those duplicated objects into the new symbol definition in the proper planes, I was able to make that work and without having to navigate around the quirks with the SymbolToGroup command and the problems it causes with high level objects. The added benefit is that text objects linked to a record format now retain that link, meaning I was able to transfer records over as well, which was a huge downside to the original tool. Big props to @Pat Stanford for making me get off my butt and work for it. Updated tool attached. Same installation as before. EDIT: Due to a request from another user, I've added the option to either retain the original symbol's records in the new symbol, or to overwrite the defaults with the symbol's current values. It will prompt for each attached record, so you don't have to overwrite all of the record formats unless you want to. Make Symbol Unique.zip
  15. @MarcelP102 Took another crack at the script, and I think I got it to work. Let me know if you see anything else that isn't working as expected. You should be able to do the same process using the Install button in the Plug-in Manager to install it as long as you haven't moved it around in your user folder (if so, delete the file from the user folder before installing). No need to worry about editing your workspace again. Make Symbol Unique.zip
  16. @MarcelP102 The way the script works is that creates a new symbol with the unique name, drops the original symbol into the symbol definition, converts the symbol to a group, then ungroups the group to leave in the geometry. For hybrid symbols, it does this twice, once for 2d geometry and once for 3d geometry. My guess is that the error is a result of the walls essentially being dropped in twice, as they are hybrid objects themselves. I'll look into this a bit more tomorrow, I might be able to fix it. Until then, avoid using the tool on symbols containing hybrid plug-in objects (other hybrid symbols should be okay).
  17. I had a little time off this afternoon, so I made this tool. It will grab all of the data from the latest selected symbol, and essentially duplicate it in the same Resource Manager folder and with the same insertion options. It will then delete the source selected symbol and insert the new one in its place, applying the class and scaling data from the original. The only thing that you will lose is any attached record data. It will prompt you to enter a new name for the symbol, and will autofill with the syntax <Original Symbol Name>'-#', with the number automatically incrementing to the next available number. It's even smart enough to parse the source symbol's name to determine if the last token is a number, then to start incrementing based on that. For example, if you run the command on a symbol named "Symbol-A-1", the script will suggest "Symbol-A-2" as the new name. It will also work on purely 2D symbols, purely 3D symbols, or hybrid symbols. To install it, follow the steps below: Download attached .zip file to a convenient location Start Vectorworks, if it isn't open already Go to Tools - Plug-ins - Plug-in Manager Click on the Third-party Plug-ins tab Click on the Install button Point Vectorworks to the .zip file you downloaded earlier Restart Vectorworks Go to Tools - Workspaces - Edit Current Workspace Click on the Menus tab In the box on the left, scroll down and expand the category named JNC In the box on the right, find a home for the command to live, such as Modify or Tools Click and drag the command Make Symbol Unique from the box on the left to a destination on the right. Click OK Likewise, you can put the command into the menu Object Context so that it is available with right-clicking an object, or assign a shortcut key to it. The script is also smart enough to detect whether the selected object is a symbol, and will tell you if the most recent selected object is not a symbol. MAJOR NOTE: the symbol must be on the currently active layer, as it uses the FSObject command to find the symbol. If you have your Layer Options set to Show/Snap/Modify and select a symbol on a different layer, the tool will yell at you for not selecting a symbol. Let me know if you have any questions or if the tool behaves unexpectedly. Make Symbol Unique.zip
  18. @MarcelP102 I had a little time off this afternoon, so I made you this tool. It will grab all of the data from the latest selected symbol, and essentially duplicate it in the same Resource Manager folder and with the same insertion options. It will then delete the source selected symbol and insert the new one in its place, applying the class and scaling data from the original. The only thing that you will lose is any attached record data. It will prompt you to enter a new name for the symbol, and will autofill with the syntax <Original Symbol Name>'-#', with the number automatically incrementing to the next available number. It's even smart enough to parse the source symbol's name to determine if the last token is a number, then to start incrementing based on that. For example, if you run the command on a symbol named "Symbol-A-1", the script will suggest "Symbol-A-2" as the new name. It will also work on purely 2D symbols, purely 3D symbols, or hybrid symbols. To install it, follow the steps below: Download attached .zip file to a convenient location Start Vectorworks, if it isn't open already Go to Tools - Plug-ins - Plug-in Manager Click on the Third-party Plug-ins tab Click on the Install button Point Vectorworks to the .zip file you downloaded earlier Restart Vectorworks Go to Tools - Workspaces - Edit Current Workspace Click on the Menus tab In the box on the left, scroll down and expand the category named JNC In the box on the right, find a home for the command to live, such as Modify or Tools Click and drag the command Make Symbol Unique from the box on the left to a destination on the right. Click OK Likewise, you can put the command into the menu Object Context so that it is available with right-clicking an object, or assign a shortcut key to it. The script is also smart enough to detect whether the selected object is a symbol, and will tell you if the most recent selected object is not a symbol. MAJOR NOTE: the symbol must be on the currently active layer, as it uses the FSObject command to find the symbol. If you have your Layer Options set to Show/Snap/Modify and select a symbol on a different layer, the tool will yell at you for not selecting a symbol. Let me know if you have any questions or if the tool behaves unexpectedly. Make Symbol Unique.zip
  19. @Jayme McColgan The script you are looking for is ApplyCustomTexPart. Texturing through Vectorscript/Python is a bit obtuse, but can be pretty powerful as you can add custom texture parts (so you can texture your 3D poly inside of the PIO separately from the rest of the object, and even give it a unique name such as "video screen"). Basically, you define the parts, then use ApplyCustomTextPart to apply the texture of that part using vs.ApplyCustomTexPart(<handle to PIO>,<handle to destination object>,<part ID #>), then select the Part in the Render tab of the OIP. I have not been able to successfully get the texture to a place where the Attribute Mapping Tool will allow you to click and drag the texture around, but the standard Scale, Offset H, Offset V, and Rotation fields of the Render tab of the OIP work as expected. I've attached a Vectorscript example that I used to teach myself texturing through script (I use Vectorscript rather than Python, but you should be able to follow along with what the code is doing). Let me know if you have any trouble installing the plug-in or if you have any questions about it. Texturing Test.vso
  20. The easiest way is to duplicate the symbol in the Resource Manager, that way the Lighting Info Record comes through intact. Duplicate the symbol, move the 2D geometry, then use the Replace Lighting Device button in the OIP to switch to the yoked out version.
  21. If you have the Instrument Type in the Lighting Info Record as the same for each symbol, they will count correctly in Lightwright. Lightwright treats the Instrument Type and Symbol Name as separate data exchange fields, so you can have multiple symbols of the same Instrument Type. I do this fairly often for different lamp types, so that Source 4s with 575w and 750w will have different symbols (750w with a gray cap and with the correct wattage listed in the Lighting Info Record), but will count correctly in Lightwright.
  22. While it's not the most elegant solution, one way that I've done this in the past is to create separate symbols based on yoke direction that has the 2D geometry of the symbol offset to appear on one side or the other, but keep the 3D geometry as is. That way you can use the 3D rotation section of the Lighting Device OIP to set the yoke and have it properly focus either through pan/tilt or by tying it to a Focus Point object.
  23. If you don't need the page breaks or watermarks to be visible while planning out your drawings, go to File - Page Setup and uncheck Show page boundary. This will turn all of those off. Otherwise, it sounds like you need set the page to be in landscape mode versus portrait. To do that, open up the Page Setup dialog as above and click on the Printer Setup button. You can then change the page size (Arch D for example) and toggle between Landscape and Portrait orientation. I would recommend making sure your page setup on your sheet layer has the correct paper size and orientation. If your page setup is correct for the final size of the printed sheet layer, you should not have any page breaks and only have one set of watermarks on the top and bottom of the page.
  24. The plug-in does not need to be event-based to hide control points. I usually have a boolean parameter to set whether the control point is "active", then just use SetCntrlPtVis(objHd,<control point #>,Pboolean) to show or hide it. Works like a charm.
  25. There are a couple of ways to decrease the chance of crashes while having complex modelling. First, Vectorworks can get pretty grumpy if the Solid Subtractions/Additions become too nested (solid addition inside solid addition inside solid addition, solid additions all the way down). If you're adding a lot of solids, try to have them all in the same solid addition rather than adding them, copying and pasting, and adding them again. When I model, I try to find ways to keep them to one addition and one subtraction (and maybe one more addition after the subtraction if required). That all being said, something you should try that negates all of the advice above is to grab your object and convert it to a Generic Solid before trying the deform tool. You'll lose all solids editing ability, so be sure to either do it to a copy of the object or at least save the solids object as a symbol so that you can go back if you need to. Before you try to light it, definitely make sure that it's stored as a symbol. Symbols, even complicated ones, are more "lightweight" in terms of memory than groups. Also, one way I've dealt with lighting a complicated model is to make the model a referenced design layer viewport in the lighting drawing. It certainly makes it a lot more clunky if you have to make adjustments to the model and work back and forth, but it's still faster than losing work to crashes.
  • Create New...