Samuel Derenboim Posted January 22, 2020 Share Posted January 22, 2020 (edited) I'm not sure if this is possible in Vectorworks yet, but there are times when I need to count a number of objects/symbols inside the geometrical boundary of another object such as a rectangle or square. My question is - Is something like this possible to do ? Count the number of fixtures inside a geometrical figure and post it into a 'tag' and / or worksheet? Edited January 22, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 22, 2020 Share Posted January 22, 2020 You need the LOC Criteria. Draw a polygon and give it a Name at the bottom of the OIP. You can then use a criteria of LOC is Name to return only the items that are contained within the area. It can be a rectangle, polygon, circle, arc. Pretty much any closed 2D object. It can be combined with other criteria to limit what you are counting even further: LOC is Name TYPE is SYMBOL etc. Ask if you need more details. Quote Link to comment
Samuel Derenboim Posted January 22, 2020 Author Share Posted January 22, 2020 Thank you for your response! LOC - is that an object function? can't seem to be able to find it in the tag tool criteria or link field criteria? Quote Link to comment
Pat Stanford Posted January 22, 2020 Share Posted January 22, 2020 LOC is a criteria and can be used in Worksheets and scripts. It does not look like LOC or COUNT can be implemented in Tags. You should write it up as a request in the Wish List Forum. You could probably write a script that would count the objects in the LOC and store that to a Record.Field and then use the tag to display that data. But you would have to remember to rerun the script when the counts changed. 😞 Quote Link to comment
Samuel Derenboim Posted January 24, 2020 Author Share Posted January 24, 2020 Thank you Pat. I'll try to work that into my workflow. I'm still a bit rusty on scripts however :). It will take me some time to learn unfortunately :( Quote Link to comment
Pat Stanford Posted January 25, 2020 Share Posted January 25, 2020 Here is a starting point. Edit this to use your record and field names. As set now it expects a Record named Counts with fields of Rect and Circle of type Number. Also remember if the Center of a regular object is inside the LOC object it is counted. Outside and it is not counted, so you need to be careful about where your LOCs fall Fairly well commented, but ask if you need help. Without the comments, error checking and Var definitions, the code is only about 12 lines. Procedure FillCountsRecord; {January 24, 2020} {©2020 Patrick Stanford pat@coviana.com} {Licensed under the GNU Lesser General Public License} {Example script showing how to attach a record containing the Count of objects meeting a set of} {criteria inside a LOC (location) object so that the data can be accessed in a Data Tag.} {It takes the first selected object on the active layer and if it has a name it attempts} {to count the object meeting the criteria defined in CriteriaString AND are positioned in } {the LOC named by the selected object} {Edit the RecName, FieldName, and CriteriaString in the script for what you need} {Do Not inlcude a LOC criteria in CriteriaString. It is autmatically added by the script.} {This sample shows how you can duplicate the code to save the count of multiple criteria} {into multiple fields with a single execution of the script.} {If you only need a single count your can delete the second block.} {The second block also shows a more complicated criteria.} {I recommend using the Criteria Builder in the Custom Selection tool using the} {Save as Vectorscript option to build and get a copyable version of the criteria to paste} {into the CriteriaString assignment. The criteria number be between single quotes.} {No warranty expressed or implied. Use at your own risk.} {There be dragons. Backup, Backup, Backup, then Backup again.} {Test on an inconspicuous area before full application.} {Do not use if you are allergic to poorly protected code.} {Very lightly tested. Test suitability for your use.} Var H1 :Handle; LocName :String; RecName :String; FieldName :String; CriteriaString :String; S1 :String; TheCount :LongInt; Procedure Execute(H2:Handle); {This is the procedure called by the ForEachObject command} Begin TheCount:=TheCount+1; {Count the object} SetRField(H1,RecName,FieldName,Concat(TheCount)); {Store the count in the record} End; Begin H1:=FSActLayer; LocName:=GetName(H1); If ((H1<>Nil) & (LocName<>'')) then Begin RecName:='Counts'; {Enter the Record Name you want to store count in} SetRecord(H1, RecName); {This block count the objects that meet the Criteria in CriteriaString. Use the criteria Builder to} {specify the objects to count in CriteriaString. Don't specify the LOC criteria as that is determined} {by the object selected when the script is run.} TheCount:=0; {This must start at zero in each block for proper count} FieldName:='Rect'; {Enter the Field Name you want to store the count in} CriteriaString:='((T=RECT))'; {Us the Criteria builder to generate the criteria string you want and store here} {Do not include the LOC criteria as that is defined by the named object} {selected when the script is run} S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')'); {Do not change this line, only change Criteria String} ForEachObject(Execute,S1); {************ End Block *************} {You can fill more than one field by adding a second block and changing the Field Name and Criteria} TheCount:=0; {This must start at zero in each block for proper count} FieldName:='Circle'; {Enter the Field Name you want to store the count in} CriteriaString:='((INSYMBOL & INVIEWPORT & (ST=CIRCLE) & (PF=1237)))'; {Us the Criteria builder } {to generate the criteria string you want and store here between single quotes} {Do not include the LOC criteria as that is defined by the named object} {selected when the script is run} S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')'); {Do not change this line, only change Criteria String} ForEachObject(Execute,S1); {************ End Block *************} End Else AlrtDialog('Something went wrong. Either no object was selected or the LOC object was not named in the OIP.'); SetDSelect(H1); {Forces Refresh of OIP} SetSelect(H1); {Resets to original selection state} End; Run(FillCountsRecord); Quote Link to comment
Samuel Derenboim Posted January 27, 2020 Author Share Posted January 27, 2020 (edited) Wow, Pat, thank you so much ! I'm going to study this, will try to get as much out of it as I can. I have some pretty ambitious goals because of the LOC functions existence. PS - I'm loving the notes you have up there 🙂 Edited January 27, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 27, 2020 Share Posted January 27, 2020 Thanks Samuel, I am nearly always up to help anyone who wants to learn to script. I specifically tried to write this one as a tutorial so you and others can see some of what is possible. The biggest trick that I failed to point out is building the entire Criteria string into S1 before using it. Most functions don't handle criteria that only partially includes a variable very well, but if you build the entire sting into a single variable they work just fine. For many of these types of functions ForEachObject is the best way to go. That way you can write a very simple Execute function to only operate on a single object at a time. You do need to be careful about getting your Global and Local variables declared and used properly. Since H2 is declared as part of Execute, it will not have a valid value after Execute returns. Since TheCount is declared globally, it is persistent through each run of Execute. And be careful of non-serif fonts. 😉. I used to use a procedure name of DoIt. Until someone read it with a SansSerif font and thought I was insulting them. 1 Quote Link to comment
Samuel Derenboim Posted January 27, 2020 Author Share Posted January 27, 2020 😂😂 Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) regarding LOC='' in worksheets - the function only allows me to select one zone by name in the criteria selection, not all of them. Is there a criteria that can include all objects that have a record, and then print out all objects within them? i've tried multiple variable condition (if) statements without success. i've included a sample file with lighting zones with object names as Zone 1, 2, & 3, but the count reports the incorrect number of fixtures in each after moving them around. count by zone test1.vwx Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 Nope, Loc is a one way criteria. You might be able to do it with columns Set the criteria to be just Lights. Enter a formula in Column C of =((LOC='Zone 1')) Enter a formula in Column D of =IF(((LOC='Zone 2')), 'Zone 2', 'Not Zone 2') Enter a formula in Column E of =Count These show you can use LOC to get the LOC an item is in, but you need to use the IF to report the name. If you are only going to have 3 zones you could reasonably use a nested If to get a single column that would display the Zone name. If you get beyond 5 or 10 then nested IFs will be a nightmare. If you Summarize first on the Instrument type in Column B. Then Summarize by Column D. You should get a count for each instrument type in Zone 2 and Not in Zone 2. Try it and ask again. Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) I can see what you mean. With lighting you can have 10-20 zones easy (if i were to use the criteria energy code for example) per story, which is why i wanted to use a nested value rather than sorting it by different columns. Same goes for other criteria i wanted to list for 'building code' objects that would be located in separate tenant spaces. Those criteria can also exceed 20 objects per LOC.... the way i wanted to get around it was to create a zone that didn't have anything in it and say LOC<>'Zone 3' to include all objects in all other zones, but alas it still reports an incorrect number. I'm guessing the script is the only other way to go? or to create a separate database table for each zone....which is also cumbersome Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 And to add to the complication, LOCs can be overlapping. And a smaller LOC inside the larger LOC may be counted as an object inside the LOC. The number of objects within the LOC is not an issue. The issue if the number of LOCs. What about a separate database for each LOC? They can be in contiguous rows so you only need a single label row above them all. It would take manual editing of the criteria row, but you could specify a cell (or range of cells) that would contain the LOC names so you could change them without having to change the actual criteria. Or a script to set the database formulas based on a list of location names? Lots of ways to skin the cat. =IF(((LOC<>'Zone 3')), 'Other Zone', 'Zone 3'). works for me in Column D. With the double sort I get 7 for 1a and 3 for 1b in other zones and 2 for 1b in Zone 3. Your criteria is for both lights and Light labels, so you have more than the 12 lights that are being counted. Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) yes, creating several database tables is possible for each zone. I thought it would be easier putting this in the criteria of the database, however, it isolates zones from the light fixtures in the space. So yes, the only other choice is to create a separate database for every zone, and thats only for one story. if i wanted to include a worksheet with all stories, and they had a different number of zones, thats when the worksheet will get a bit cumbersome to read as some stories that have less zones would return a blank record (unless database tables would be specified for every zone, and every floor). right now im working on a 5 story, 80 thousand square foot building, approximately 6-7 tenant spaces per story, and probably upwards of 5-6 lighting zones per tenant. it would be easier to match the name record between light fixtures and zones than to do it this way. or not use LOC altogether unfortunately. file attached is my second attempt. I tried to list all zones in one database row, and reference them for the zones and light fixtures in another. It separates the rows and the zones in the database row in the 1st one. count by zone test2.vwx Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) Update : Here is a nested working version. Question, why can't i call out the object name? by =N, but rather have to create a conditional statement if N=zone 1, then print zone 1, rather than printing it altogether? PS - notepad ++ really helps out with these conditionals count by zone nested-working.vwx Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 I will look more later, but the simplest explanation as to why you can't do what you want is that there is no link between the objects and the zone. The objects don't ever know that they are in the zone. And the zone does not know what objects are inside it unless you specifically run something using LOC. Perhaps what you really want is a script that will go through every named object (or named in a certain class, or named with a certain record attached) and attach a record to the objects that match the LOC for each object. It should also remove the record for all objects first. Then you have a directly (though fragile) link between the object and the zone and will have a lot more flexibility in what you want to do. You just have to remember to rerun the script when objects get moved into, out of, or between zones. Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) I wrote the nested script in a matter of minutes once i figured out that i have to create a reference name for the zone name in the conditional statement... I'm thinking of just using an excel script to create a script for me that includes the maximum amount of zones the building house in one story. Turns out, it works on multiple stories !! I'm thinking about how i can do this in a simple manner and... will this crash VW? attached file below. Also, do you know if referencing the object name (=N) with a record using the datamanager would eliminate all these complications? count by zone nested-working.vwx Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) I think i accidentally found a bug. i just noticed in the file attached above - the geometry was duplicated from one floor to the next. However, the name of the boundary was not modified, yet it still calculates the correct number of objects on the 2nd story when moving the fixtures from one boundary to another correct me if I'm wrong? the downside to doing everything by LOC function , turns out that every boundary object has to be unique in the entire building, and then create a nested conditional script that computes all of them, unless of course, yes, certain operations would be done by class or operation. The kicker is this, i often use id's that can be identical to different spaces that use the same id tag so long as it is subdivided by the =story condition. so ID=1 between one and another story will be printed separately if I invoke the =story command that separates their conditions. Here, you cannot do this. but it accidently worked when a boundary object is duplicated. don't know why. ☹️ Update: Followup question - is it possible to simply list all the LOC zones and their names? Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 LOCs are generic. It does not care what story or layer it is on. Nor does it care what story or layer the other objects are on. If the physical locations are identical from layer to layer you can just specify the criteria to only look at that layer and reuses the same LOC for multiple layers. Since any named object can be used as a LOC, no you can't report just LOC objects. You can report named objects. And classes objects, and objects with a record attached. So put all of your LOCs into a class or attache a record and you can then report on them. LOC is a virtual construct. There is no intrinsic relationship between a LOC and anything. Any named object can be used as a LOC. It probably does not make any sense to use a line or a locus as a LOC, but I don't think it would error if you tried, it would just return nothing since there is no area. On your bug, please double check. If you duplicated a named object and it did not clear the name, then that is a VERY BAD THING™. My guess is that the poly was duplicated but the name was cleared. But since the original and duplicate were in the same location in space the LOC criteria used the named original and happened to give the correct result. Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) Pat, that's exactly it. it is a duplicate object, that wasn't renamed, but the object name was not there. VW picked up that it was the same name and it didn't rename it, it left it blank instead. It however, was in the same exact location as another LOC object, hence why it picks up the information. but i'm guessing this might lead to some problems down the road? Should i submit a bug report? Note - i duplicated the layer, and it didn't copy the name. If i copy and paste the object, then it auto renames Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 If you report a bug it will be returned as WAD (Working as Designed). Named objects must have unique names in a VW file. When you duplicate a named object the name if cleared. The formula you are using is still specifying the name of the first object. Since that object still exists and has the name specified, it is being used. Again, the LOC is not a thing. A LOC is a criteria that can be used to determine is an object is inside the boundaries of another object. There is NO link between either the contained objects or the boundary object and the LOC. None, zero, zip. X + Y = Z. If I tell you here is the + sign, what are X and Y, you can't do it. If I tell you X is 2 , Y is 2 and Z is 4 and then ask you what function the + sign has you can easily tell me it is addition. Similarly, If I give you a LOC, you can check is an object is in that LOC. But if I give you an object the only way to tell what LOC it is in is to test every LOC (named object) in the file. And remember that a given object can be in multiple LOCs at the same time. If you want to do LOCs on different Layers they will have to have different names. And you will have to use different criteria in the database to account for the different names. LOC is ephemeral. 1 Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 I think i understand. Sounds like LOC function would be easier to use using a marionette of scripting function rather than a worksheet callout. Seems like keeping track of the names of the boundaries can prove to be a hurdle. I'm trying to use that script you made to study it a bit, but i get this error : Would the right parenthesis go somewhere in here? S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, '))'); Quote Link to comment
Pat Stanford Posted January 28, 2020 Share Posted January 28, 2020 I your LOC name contains spaces I must be surrounded by single quotes. The same is true of Record and Field names. If there are no spaces then quotes are not required, but are still a good idea in case you change to something with a space later. LOC='Zone 1' Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) Wow, this is beautiful. Works like a charm. In order for this script to have integrated information, it would be easier to simply copy the zone information in an attached record from the boundary onto all symbols in that zone rather than counting the objects. That way, i know if the zone matches between the objects and the zone itself, they could be aligned in the worksheet easily and specify any required specific information about that zone (like wattage, lumens, etc..) there was a script you wrote once regarding counting the number of people in a space object by using the space object area and dividing it by a separate record. maybe that can give me a hint as to how to take information from one record and copy it to all the objects (or symbols) with the LOC of the boundary. Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Samuel Derenboim Posted January 28, 2020 Author Share Posted January 28, 2020 (edited) First Attempt. Procedure FillCountsRecord; Var H1 :Handle; H2 :Handle; LocName :String; LocName2 :String; RecName :String; RecName2 :String; FieldName :String; FieldName2 :String; CriteriaString :String; CriteriaString2 :String; S1 :String; S2 :String; TheCount :LongInt; Procedure Execute(H3:Handle); {This is the procedure called by the ForEachObject command} Begin SetRField(H2,RecName,FieldName)); SetRField(H1,RecName2,FieldName)); {Store the count in the record} End; Begin H1:=FSActLayer; LocName:=GetName(H1); If ((H1<>Nil) & (H2<>Nil) & (LocName<>'') & (LocName2<>'')) then Begin RecName:='!Ltlegend'; {Enter the Record Name you want to store} SetRecord(H1, RecName); FieldName:='Zone'; RecName2:='!Lights'; {Enter the Record2 Name you want write to} SetRecord(H2, RecName); FieldName:='LZone'; CriteriaString1:='(((R IN ['!LtLegend']) & (ALL)))'; {Us the Criteria builder to generate the criteria string you want and store here} CriteriaString2:='(((R IN ['!Lights']) & (T=SYMBOL)))'; {Do not include the LOC criteria as that is defined by the named object} {selected when the script is run} S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')'); {Do not change this line, only change Criteria String} ForEachObject(Execute,S1); S2:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')'); ForEachObject(Execute,S2); End Else AlrtDialog('Something went wrong. Either no object was selected or the LOC object was not named in the OIP.'); SetDSelect(H1); {Forces Refresh of OIP} SetSelect(H1); SetDSelect(H2); {Forces Refresh of OIP} SetSelect(H2); {Resets to original selection state} End; Run(FillCountsRecord); Edited January 28, 2020 by Samuel Derenboim Quote Link to comment
Recommended Posts
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.