Hi CAD Friends,
Wonder if there is a LISP routine (already created by someone) to count all the blocks inside a closed polyline.
These are typically plant blocks with attributes in it. I have these sheet layout boundaries in model space, and the City now wants the count of all the plant blocks in a particular sheet, and a totals count in the end. I was using BCOUNT, but then I have to select the blocks individually along the sheet boundaries carefully. Instead of that If I can select the polylines of these sheet boundaries, then the lisp should count and provide a total count of different types of blocks contained (all the blocks with the centre point inside that selected polyline) inside the closed polyline.
If some one can modify the BCOUNT lisp adding an additional feature to count blocks inside the user selected closed polyline ( get input from user selecting the closed polyline), that would be awesome.
Regards,
VA
Solved! Go to Solution.
Solved by DannyNL. Go to Solution.
Solved by DannyNL. Go to Solution.
Solved by DannyNL. Go to Solution.
Solved by john.uhden. Go to Solution.
Solved by ActivistInvestor. Go to Solution.
Solved by ВeekeeCZ. Go to Solution.
Solved by maratovich. Go to Solution.
Hi John.Uhden,
This routine is working great. it actually counts all the blocks correctly in all the tests i did so far.
I have a few suggestions, in order to make this little user friendly.
1. After the BIB function is called, is there a possibility of showing a drop down list in the command line with all the block names in the current drawing,
incase if we want to select a particular block to count inside the selected polyline?
2. Once the routine is run ( result: when all the blocks inside the polyline are selected),
can we subsequently run the BCOUNT lisp routine ( courtesy to Lee-MAC programming) , so that we can get the counts of all the blocks in the command line itself? Now I am doing it as two different lisp runs, BIB and then BCOUNT.
3. being next step , Is there a way to extract the key attribute values in the plant blocks, A, B or a,b or aa, bb and get the counts of each type of blocks in a table showing :
First column: Key value ( eg: A,B or a,b or aa, bb depending upon how many blocks in the selection inside selected polyline)
Second column: Name of Block: (eg: Tree-Dec-Prairie Spire Green Ash)
Third Column: Total counts of each block
4. The routine should ask for an insertion point for the table, and the table should have Title ( Plant Count) and Headers - Column 1: Attribute Key ; Column 2: Block Name; and Column 3: Block Counts
5. The table items should be sorted by column 1: Attribute Key - A-Z; then a-z; and then aa-zz.
Not sure every thing listed here is attainable as suggested. This way the LISP will be a more robust one.
Thanks for all your help. This is really working good so far. Please let me know if you need any more details. I have attached the drawing for your use.
Regards,
VA
I'm glad that the routine will be helpful to you.
!. The routine allows you to type in one or more block names with wildcards, separated by commas. As freeware, I am not going to complicate the code with multiple-select dialog boxes.
2. Yes, I could add reporting of the number of insertions for each block name it finds within the boundary. I will do so.
3. Someone else can add the attribute reporting. Everything here is public domain.
4. I am not table capable since all I have is ACAD 2002. But someone else could add that.
5. Same as #4.
6. I should add ellipses as candidate boundaries. No, I am not adding polyface meshes or hatches or images or 3D solids or Civil 3D surfaces.
John F. Uhden
Here ya go...
(defun C:BlocksInsideBoundary ( / *error* cmdecho @Inside @delta @addname bnames Oname SS i B E Object P Names) ;; v1.0 (c. 08-20-03) ;; v1.1 (09-15-03) using Uhden's new @Inside function
;; v2.0 donated to AutoCAD forum (01-09-18) (gc) (vl-load-com) (or *acad* (setq *acad* (vlax-get-acad-object))) (or *doc* (setq *doc* (vla-get-ActiveDocument *acad*))) (defun *error* (error) (if (vl-position cmdecho '(0 1))(setvar "cmdecho" cmdecho)) (if B (redraw B 4)) (vla-endundomark *doc*) (cond ((not error)) ((wcmatch (strcase error) "*QUIT*,*CANCEL*")) (1 (princ (strcat "\nERROR: " error))) ) (princ) ) (vla-endundomark *doc*) (vla-startundomark *doc*) (setq cmdecho (getvar "cmdecho")) (setvar "cmdecho" 0) (command "_.expert" (getvar "expert")) ;; dummy command ;;--------------------------------------------------------------------------------------------- ;; Function to determine if a point is inside a curve boundary (defun @Inside (PIQ Object / Closest Start End Param P a1 a2 Defl @2D @Bulge) ;; "LOOK, MA'... NO RAYS!" ;; @Inside.lsp v1.0 (09-15-03) John F. Uhden, Cadlantic. ;; v2.0 Revised (09-17-03) - See notes below. ;; v3.0 Revised (09-20-03) - See notes below. ;; Function to determine whether a point is inside the boundary ;; of a closed curve. ;; It employs the theorem that the sum of the deflections of a ;; point inside the curve should equal 360°, and if outside 0° ;; (both absolute values). ;; The results with Ellipses were fairly rough, and the results ;; with a Spline were very rough, thus the fuzz factor of 2. ;; ;; Arguments: ;; PIQ - Point to test (2D or 3D point as a list in UCS) ;; Object - Curve to test (Ename or VLA-Object) ;; ;; Returns: ;; T (if the PIQ is inside the curve) ;; nil (if either the arguments are invalid, ;; or the PIQ is on or outside the curve) ;; ;; NOTES: ;; Requires one or another version of the @delta function, ;; such as included here. ;; It will not work well with self-intersecting (overlapping) ;; bulged polyline segments. ;; Curves can be CIRCLEs, ELLIPSEs, LWPOLYLINEs, POLYLINES, ;; SPLINEs, and maybe even more. ;; Thanks already to Doug Broad for finding bugs. (setq Sample 0.5) ; this is better for bulged polylines. ;; Sure, you could decrease the sampling from 0.5 to say 0.1, ;; but it will only slow down the process and still not ;; guarantee success with overlapping bulged segments. ;; DO NOT change the sampling value to anything that is ;; greater than 1 or not evenly divisible into 1, as you ;; would not be sampling vertices. ;; (09-17-03) Found that cusps brought back inside the figure ;; yield a total deflection of (* pi 2), so changed evaluation ;; to see if deflection was greater than 4, which is ;; equivalent to a fuzz factor of 2.28 from (* pi 2). (vl-load-com) (defun @delta (a1 a2) (cond ((> a1 (+ a2 pi)) (+ a2 pi pi (- a1)) ) ((> a2 (+ a1 pi)) (- a2 a1 pi pi) ) (1 (- a2 a1)) ) ) ;; Function to convert a 3D point into 2D for the purpose ;; of ignoring the Z value. ;; Added (09-20-03) (defun @2D (p)(list (car p)(cadr p))) ;;----------------------------------------------------- ;; Function to determine if an angle is with the sector ;; defined by two other angles. ;; Returns the delta angle from the first angle. ;; (defun @insect (Ang Ba Ea) (if (> Ba Ea) (cond ((>= Ang Ba)) ((<= Ang Ea)) (1 nil) ) (< Ba Ang Ea) ) ) (defun @Bulge (Param / Bulge V1 V2 Half Delta Chord Radius Center Ba Ea Ang P) (and (setq Bulge (vl-catch-all-apply 'vla-getbulge (list Object (fix Param)))) (numberp Bulge) (/= Bulge 0) (< Param End) (setq V1 (vlax-curve-getpointatparam Object (fix Param))) (setq V2 (vlax-curve-getpointatparam Object (1+ (fix Param)))) (setq V1 (trans V1 0 1)) (setq V2 (trans V2 0 1)) (setq Half (if (minusp Bulge) -0.5 0.5) Delta (* 4.0 (atan (abs Bulge))) Chord (distance V1 V2) Radius (/ Chord 2 (sin (/ Delta 2))) Center (polar V1 (+ (angle V1 V2)(* (- pi Delta) Half)) Radius) ) (if (minusp Bulge) (setq Ba (angle Center V2) Ea (angle Center V1)) (setq Ba (angle Center V1) Ea (angle Center V2)) ) (setq Ang (angle Center PIQ)) (@insect Ang Ba Ea) (setq P (polar Center Ang Radius)) ) P ) (and (cond ((not Object) (prompt " No object provided.") ) ((= (type Object) 'VLA-Object)) ((= (type Object) 'Ename) (setq Object (vlax-ename->vla-object Object)) ) (1 (prompt " Improper object type.")) ) (or (and (< 1 (vl-list-length PIQ) 4) (vl-every 'numberp PIQ) ) (prompt " Improper point value.") ) (or (not (vl-catch-all-error-p (setq Start (vl-catch-all-apply 'vlax-curve-getStartPoint (list Object) ) ) ) ) (prompt " Object is not a curve.") ) (or (equal Start (vlax-curve-getendpoint Object) 1e-10) (prompt " Curve is not closed.") ) (setq P (trans PIQ 1 0)) ; PIQ in WCS (setq Closest (vlax-curve-getclosestpointto Object P) ; In WCS ) ;; Test to see if PIQ is on object: (not (equal (@2D P)(@2D Closest) 1e-10)) ; in WCS (setq End (vlax-curve-getEndparam Object)) (if (= (vla-get-objectname Object) "AcDbSpline") (setq Sample (/ End 100)) (setq Sample 1.0) ) (setq Param Sample Defl 0.0) (setq a1 (angle PIQ (trans Start 0 1))) ; in UCS (while (<= Param End) (setq Param (min Param End)) (setq P (vlax-curve-getpointatparam Object Param) a2 (angle PIQ (trans P 0 1)) ; in UCS Defl (+ Defl (@delta a1 a2)) a1 a2 ) ; (grdraw PIQ P 3) (if (setq P (@Bulge Param)) (progn ;(grdraw PIQ (trans (vlax-curve-getpointatparam Object P1) 0 1) 1) ;(grdraw PIQ P 1) ;(grdraw PIQ (trans (vlax-curve-getpointatparam Object P2) 0 1) 3) (setq a2 (angle PIQ P) Defl (+ Defl (@delta a1 a2)) a1 a2 ) ) ) (setq Param (+ Param Sample)) ) ;(print Defl) ; Optional display of results (> (abs Defl) 4) ) ) ;;--------------------------------------------------------------------------------------------------------- ;; Function to keep count of block names found inside the boundary (defun @addname (name / item) (if (setq item (assoc name Names)) (setq Names (subst (cons name (1+ (cdr item))) item Names)) (setq Names (cons (cons name 1) Names)) ) ) ;;----------------------------- ;; Begin the action: (and (setq B (car (entsel "\nSelect boundary object: "))) (setq Object (vlax-ename->vla-object B)) (setq Oname (vlax-get Object 'ObjectName)) (cond ((not (vl-position Oname '("AcDbPolyline" "AcDb2dPolyline" "AcDbSpline" "AcDbCircle" "AcDbEllipse"))) (alert (strcat "Object selected is a(n) " Oname)) ) ((and (/= Oname "AcDbCircle")(/= Oname "AcDbEllipse")(= (vlax-get Object 'Closed) 0)) (alert "Object is not closed.") ) ((and (= Oname "AcDbEllipse")(/= (rem (vlax-get Object 'EndAngle) pi)(vlax-get Object 'StartAngle))) (alert "Ellipse is not closed.") ) ((zerop (vlax-get Object 'Area)) (alert "Object has an area of zero (0).") ) (1) ) (or (redraw B 3) 1) (setq bnames (getstring "\nEnter block names to find, or * for all <*>: ")) (or (/= bnames "")(setq bnames "*")) (setq SS (ssget "X" (list '(0 . "INSERT")(cons 2 bnames)(cons 410 (getvar "ctab"))))) (repeat (setq i (sslength SS)) (setq p (cdr (assoc 10 (entget (setq e (ssname SS (setq i (1- i)))))))) (if (@Inside P Object) (@addname (cdr (assoc 2 (entget e)))) (ssdel e SS) ) 1 ) (if (and SS (> (sslength SS) 0)) (progn (princ (strcat "\nFound " (itoa (sslength SS)) " blocks within boundary.")) (sssetfirst nil SS) (foreach name Names (princ (strcat "\n" (car name) ": " (itoa (cdr name))))) ) (alert "No blocks found inside boundary") ) ) (*error* nil) ) (defun C:BIB ()(C:BlocksInsideBoundary))
John F. Uhden
Hi John,
Thanks for taking time to do the revision. I tested it and it is working fine.
Regards,
VM
As far a a list of blocks... you can easily incorporate one of the GUI functions in DOSLib with just a few lines of code.
Actually the spline-turned-into-polyline is recognized correctly, but it is the selection of the blocks that fails due to the huge amount of points used for the crossing polygon. The code below uses the bounding box of the poyline as selection crossing and routine should now work in all cases. Although somewhat slower on those complex polyline shapes due to the increased number of blocks that are selected for checking. Also renamed the command from Test to BlkCounter.
I've noticed you've made some additional requests to be added, but I've not looked at those (yet).
(defun c:BlkCounter (/ BC_Selection BC_Entity BC_LowerLeft BC_UpperRight BC_PointList BC_Result BC_Previous BC_Count BC_EntityList BC_CheckLine BC_IntersectPoints BC_BoundaryCheck BC_BlockName BC_BlockList) (if (and (princ "\nSelect polyline: ") (setq BC_Selection (ssget ":S+." '((0 . "*POLYLINE")))) (setq BC_Object (vlax-ename->vla-object (ssname BC_Selection 0))) (vlax-Curve-isClosed BC_Object) (vlax-Curve-isPlanar BC_Object) (setq BC_PointList (_GetBoundingBox BC_Object)) (setq BC_Selection (ssget "_CP" BC_PointList '((0 . "INSERT")))) ) (progn (acet-ui-progress "Counting" (sslength BC_Selection)) (setq BC_Count 0) (foreach BC_Entity (vl-remove-if '(lambda (BC_Item) (listp (cadr BC_Item))) (ssnamex BC_Selection)) (acet-ui-progress (setq BC_Count (1+ BC_Count))) (setq BC_EntityList (entget (setq BC_Entity (cadr BC_Entity)))) (if (_PointInside (cdr (assoc 10 BC_EntityList)) BC_Object) (progn (if (not (assoc (setq BC_BlockName (cdr (assoc 2 BC_EntityList))) BC_BlockList)) (setq BC_BlockList (cons (list BC_BlockName 1) BC_BlockList)) (setq BC_BlockList (subst (list BC_BlockName (1+ (cadr (assoc BC_BlockName BC_BlockList)))) (assoc BC_BlockName BC_BlockList) BC_BlockList)) ) ) (ssdel BC_Entity BC_Selection) ) (if BC_CheckLine (entdel BC_CheckLine)) ) ;(sssetfirst nil BC_Selection) (acet-ui-progress) (princ (strcat "\n ** Total number of blocks found: " (itoa (sslength BC_Selection)) "\n")) (foreach BC_Item (vl-sort BC_BlockList '(lambda (BC_Block1 BC_Block2) (< (car BC_Block1) (car BC_Block2)))) (princ (strcat "\n" (car BC_Item) ": " (itoa (cadr BC_Item)))) ) (princ "\n") ) ) (princ) ) (defun _GetBoundingBox (GBB_Object / GBB_LowerLeft GBB_UpperRight) (if (vlax-method-applicable-p GBB_Object 'GetBoundingBox) (progn (vla-GetBoundingBox GBB_Object 'GBB_LowerLeft 'GBB_UpperRight) (setq GBB_LowerLeft (vlax-safearray->list GBB_LowerLeft)) (setq GBB_UpperRight (vlax-safearray->list GBB_UpperRight)) (list GBB_LowerLeft (list (nth 0 GBB_UpperRight) (nth 1 GBB_LowerLeft) 0.0) GBB_UpperRight (list (nth 0 GBB_LowerLeft) (nth 1 GBB_UpperRight) 0.0) ) ) ) ) (defun _PointInside (PI_Point PI_PolylineObject / PI_RayAngles PI_ClosestPoint PI_AngleList PI_RefAngle PI_AngleList PI_CheckLine PI_IntersectPoints PI_CheckList) (setq PI_RayAngles (_AngleList 12)) (setq PI_ClosestPoint (vlax-curve-GetClosestPointTo PI_PolylineObject PI_Point)) (setq PI_AngleList (mapcar '(lambda (PI_Factor) (* pi PI_Factor)) PI_RayAngles)) (if (not (equal (rem (setq PI_RefAngle (angle PI_Point PI_ClosestPoint)) (/ pi 2.0)) 0)) (setq PI_AngleList (append (mapcar '(lambda (PI_Factor) (+ (* pi PI_Factor) PI_RefAngle )) PI_RayAngles) PI_AngleList)) ) (foreach PI_Angle PI_AngleList (setq PI_CheckLine (entmakex (list '(0 . "RAY") '(100 . "AcDbEntity") '(100 . "AcDbRay") (cons 10 PI_Point) (cons 11 (mapcar '- (polar PI_Point PI_Angle 5.0) PI_Point))))) (if (not (vl-catch-all-error-p (setq PI_IntersectPoints (vl-catch-all-apply 'vlax-safearray->list (list (vlax-variant-value (vla-IntersectWith (vlax-ename->vla-object PI_CheckLine) PI_PolylineObject acExtendNone))))))) (setq PI_CheckList (cons (length (_GroupByNum PI_IntersectPoints 3)) PI_CheckList)) (setq PI_CheckList (cons nil PI_CheckList)) ) (entdel PI_CheckLine) ) (if PI_CheckList (not (vl-some 'not PI_CheckList)) ) ) (defun _AngleList (AL_Integer / AL_Step AL_Factor AL_Return) (setq AL_Step (/ 2.0 AL_Integer)) (setq AL_Factor 0.0) (repeat AL_Integer (setq AL_Return (append AL_Return (list (+ 0.0 AL_Factor)))) (setq AL_Factor (+ AL_Factor AL_Step)) ) AL_Return ) ; By Lee Mac (defun _GroupByNum (l n / r) (if l (cons (reverse (repeat n (setq r (cons (car l) r) l (cdr l)) r)) (_GroupByNum l n)) ) )
@lando7189 wrote, "As far a a list of blocks... you can easily incorporate one of the GUI functions in DOSLib with just a few lines of code."
Excellent suggestion. I was just being lazy.
John F. Uhden
Hi DannyNL,
I checked your revised routine and it is all working great. Thanks for the help and support.
I have a small request though. Is there a way to change the function call from the current BC to BCPL ? Not sure how difficult is this, since I am not very familiar with lisp programming syntax. The reason for this request ( more of a personal in nature) is because I have another lisp loaded in AutoCAD with same function call as BC.lsp ( thanks to Gary Annable for the routine to count specific blocks in a drawing).
As for the additional requests I have made in my previous posts - I have found 2 awesome routines ( CountV1-5.lsp and TCountV1-1.lsp - Credit goes to original programmer Lee-MAC Programming - Thank you Lee). The CountV1-5.lsp counts the selected blocks and return the results in a table format; and the TCountV1-1.lsp extracts the attribute data from the selected blocks and return the results in a table format.) If some one can incorporate these two lisps in their routines, then I think we have a comprehensive routine which is robust enough.
I am attaching the Autocad drawing where I have made those tables using those routines, and an intended Table with block counts and attribute values ( manually combined). I have also attached the Lee-Mac Programming Routines I mentioned above for your review and use. All credits goes to Lee-Mac Prog for those two routines used in the current proposed routine.
Hope this helps., and thank you all very much for this great help.
Regards,
VA
Changed BlkCounter to BCPL and added code to create a sorted table.
BTW, did not use the supplied .LSP files as it would take me longer the extract the parts that I need than writing new code
(defun c:BCPL (/ BCPL_Selection BCPL_Entity BCPL_LowerLeft BCPL_UpperRight BCPL_PointList BCPL_Result BCPL_Previous BCPL_Count BCPL_EntityList BCPL_CheckLine BCPL_IntersectPoints BCPL_BoundaryCheck BCPL_BlockName BCPL_BlockList BCPL_TableObject BCPL_SortOrder BCPL_BlockListSorted BCPL_RowCount BCPL_Block) (if (and (princ "\nSelect boundary: ") (setq BCPL_Selection (ssget ":S+." '((-4 . "<OR")(0 . "*POLYLINE")(0 . "CIRCLE")(0 . "ELLIPSE")(0 . "SPLINE")(-4 . "OR>")))) (setq BCPL_Object (vlax-ename->vla-object (ssname BCPL_Selection 0))) (vlax-Curve-isClosed BCPL_Object) (vlax-Curve-isPlanar BCPL_Object) (setq BCPL_PointList (_GetBoundingBox BCPL_Object)) (setq BCPL_Selection (ssget "_CP" BCPL_PointList '((0 . "INSERT")))) ) (progn (vla-StartUndoMark (vla-get-ActiveDocument (vlax-get-acad-object))) (acet-ui-progress "Counting" (sslength BCPL_Selection)) (setq BCPL_Count 0) (foreach BCPL_Entity (vl-remove-if '(lambda (BCPL_Item) (listp (cadr BCPL_Item))) (ssnamex BCPL_Selection)) (acet-ui-progress (setq BCPL_Count (1+ BCPL_Count))) (setq BCPL_EntityList (entget (setq BCPL_Entity (cadr BCPL_Entity)))) (if (_PointInside (cdr (assoc 10 BCPL_EntityList)) BCPL_Object) (progn (if (not (assoc (setq BCPL_BlockID (list (cdr (assoc 2 BCPL_EntityList)) (_ExtractAttributeTagValue (vlax-ename->vla-object BCPL_Entity) "KEY"))) BCPL_BlockList)) (setq BCPL_BlockList (cons (list BCPL_BlockID 1) BCPL_BlockList)) (setq BCPL_BlockList (subst (list BCPL_BlockID (1+ (cadr (assoc BCPL_BlockID BCPL_BlockList)))) (assoc BCPL_BlockID BCPL_BlockList) BCPL_BlockList)) ) ) (ssdel BCPL_Entity BCPL_Selection) ) (if BCPL_CheckLine (entdel BCPL_CheckLine)) ) (acet-ui-progress) (princ (strcat "\n ** Total number of blocks found: " (itoa (sslength BCPL_Selection)) "\n")) (if (and BCPL_BlockList (not (initget 1)) (not (vl-catch-all-error-p (setq BCPL_TableObject (vl-catch-all-apply '_CreateTable (list (vlax-3d-point (getpoint "\nInsertionpoint for table: ")) (+ (length BCPL_BlockList) 2) 4 11.25 60.0))))) ) (progn (setq BCPL_SortOrder (list "[A-Z]" "[a-z]" "[a-z][a-z]")) (foreach BCPL_Pattern BCPL_SortOrder (setq BCPL_BlockListSorted (append BCPL_BlockListSorted (vl-sort (vl-remove-if-not '(lambda (BCPL_Item)(wcmatch (cadr (car BCPL_Item)) BCPL_Pattern)) BCPL_BlockList) '(lambda (BCPL_Item1 BCPL_Item2) (< (cadr (car BCPL_Item1))(cadr (car BCPL_Item2))))))) ) (vla-SetColumnWidth BCPL_TableObject 2 100.0) (vla-SetColumnWidth BCPL_TableObject 3 40.0) (vla-SetAlignment BCPL_TableObject acDataRow acMiddleCenter) (vla-SetTextHeight2 BCPL_TableObject 0 0 0 6.0) (vla-SetText BCPL_TableObject 0 0 "Block Data") (vla-SetText BCPL_TableObject 1 0 "Block Preview") (vla-SetText BCPL_TableObject 1 1 "Attribute Value") (vla-SetText BCPL_TableObject 1 2 "Block Name") (vla-SetText BCPL_TableObject 1 3 "Count") (setq BCPL_RowCount 2) (foreach BCPL_Item BCPL_BlockListSorted (if (not (vl-catch-all-error-p (setq BCPL_Block (vl-catch-all-apply 'vla-item (list (vla-get-Blocks (vla-get-ActiveDocument (vlax-get-acad-object))) (nth 0 (nth 0 BCPL_Item))))))) (vl-catch-all-apply 'vla-SetBlockTableRecordId (list BCPL_TableObject BCPL_RowCount 0 (vla-get-ObjectId BCPL_Block) :vlax-true)) ) (vla-SetText BCPL_TableObject BCPL_RowCount 1 (cadr (car BCPL_Item))) (vla-SetCellAlignment BCPL_TableObject BCPL_RowCount 2 acMiddleLeft) (vla-SetText BCPL_TableObject BCPL_RowCount 2 (car (car BCPL_Item))) (vla-SetText BCPL_TableObject BCPL_RowCount 3 (itoa (cadr BCPL_Item))) (setq BCPL_RowCount (1+ BCPL_RowCount)) ) ) ) (vla-EndUndoMark (vla-get-ActiveDocument (vlax-get-acad-object))) ) ) (princ) ) (defun _CreateTable (CT_InsertionPoint CT_Rows CT_Columns CT_RowHeigth CT_ColumnWidth) (if (= 1 (getvar 'cvport)) 'paperspace 'modelspace) (vl-catch-all-apply 'vla-addTable (list (vlax-get-property (vla-get-ActiveDocument (vlax-get-acad-object)) (if (= 1 (getvar 'cvport)) 'paperspace 'modelspace)) CT_InsertionPoint CT_Rows CT_Columns CT_RowHeigth CT_ColumnWidth)) ) (defun _GetBoundingBox (GBB_Object / GBB_LowerLeft GBB_UpperRight) (if (vlax-method-applicable-p GBB_Object 'GetBoundingBox) (progn (vla-GetBoundingBox GBB_Object 'GBB_LowerLeft 'GBB_UpperRight) (setq GBB_LowerLeft (vlax-safearray->list GBB_LowerLeft)) (setq GBB_UpperRight (vlax-safearray->list GBB_UpperRight)) (list GBB_LowerLeft (list (nth 0 GBB_UpperRight) (nth 1 GBB_LowerLeft) 0.0) GBB_UpperRight (list (nth 0 GBB_LowerLeft) (nth 1 GBB_UpperRight) 0.0) ) ) ) ) (defun _ExtractAttributeTagValue (EATV_BlockObject EATV_Tag / EATV_Return) (if (and (= (type EATV_BlockObject) 'VLA-OBJECT) (= (vla-get-ObjectName EATV_BlockObject) "AcDbBlockReference") (= (vla-get-HasAttributes EATV_BlockObject) :vlax-true) (= (type EATV_Tag) 'STR) ) (progn (foreach EATV_Attribute (vlax-safearray->list (vlax-variant-value (vla-GetAttributes EATV_BlockObject))) (if (and (not EATV_Return) (= (strcase (vla-get-TagString EATV_Attribute)) (strcase EATV_Tag)) ) (setq EATV_Return (vla-get-TextString EATV_Attribute)) ) ) ) ) EATV_Return ) (defun _PointInside (PI_Point PI_PolylineObject / PI_RayAngles PI_ClosestPoint PI_AngleList PI_RefAngle PI_AngleList PI_CheckLine PI_IntersectPoints PI_CheckList) (setq PI_RayAngles (_AngleList 12)) (setq PI_ClosestPoint (vlax-curve-GetClosestPointTo PI_PolylineObject PI_Point)) (setq PI_AngleList (mapcar '(lambda (PI_Factor) (* pi PI_Factor)) PI_RayAngles)) (if (not (equal (rem (setq PI_RefAngle (angle PI_Point PI_ClosestPoint)) (/ pi 2.0)) 0)) (setq PI_AngleList (append (mapcar '(lambda (PI_Factor) (+ (* pi PI_Factor) PI_RefAngle )) PI_RayAngles) PI_AngleList)) ) (foreach PI_Angle PI_AngleList (setq PI_CheckLine (entmakex (list '(0 . "RAY") '(100 . "AcDbEntity") '(100 . "AcDbRay") (cons 10 PI_Point) (cons 11 (mapcar '- (polar PI_Point PI_Angle 5.0) PI_Point))))) (if (not (vl-catch-all-error-p (setq PI_IntersectPoints (vl-catch-all-apply 'vlax-safearray->list (list (vlax-variant-value (vla-IntersectWith (vlax-ename->vla-object PI_CheckLine) PI_PolylineObject acExtendNone))))))) (setq PI_CheckList (cons (length (_GroupByNum PI_IntersectPoints 3)) PI_CheckList)) (setq PI_CheckList (cons nil PI_CheckList)) ) (entdel PI_CheckLine) ) (if PI_CheckList (not (vl-some 'not PI_CheckList)) ) ) (defun _AngleList (AL_Integer / AL_Step AL_Factor AL_Return) (setq AL_Step (/ 2.0 AL_Integer)) (setq AL_Factor 0.0) (repeat AL_Integer (setq AL_Return (append AL_Return (list (+ 0.0 AL_Factor)))) (setq AL_Factor (+ AL_Factor AL_Step)) ) AL_Return ) ; By Lee Mac (defun _GroupByNum (l n / r) (if l (cons (reverse (repeat n (setq r (cons (car l) r) l (cdr l)) r)) (_GroupByNum l n)) ) )
Hi DannyNL,
This is awesome !!!! I am really excited. Thanks for creating the product exactly I requested. I tested the routine and it is all working great. I always request other users should also test this as my work flow and workflow of others could be different. I will wait for until tomorrow so that if some one comes with any suggestions.
In the mean time, (if it is easy, quick and possible) could you please include 4 block selection options as soon as we initiate this routine:
1. Select all blocks (A)
2. Select a polyline boundary (B)
2. Left drag window selection
4. Right drag window selection
This way we can include all block counting options in this ONE Lisp routine.
As I assume, we can wait until tomorrow to "Accept as a Solution" if other users want to check and review this lisp.
Thanks for all your hard work.
Regards,
VA
Be careful there, Danny. We've been through his before. I think it was back around 2003, which was probably before Kent's time so it was probably Tony T. jumping in. I had had the theory that if a ray intersects the boundary an odd number of times, then the origin must be inside the boundary. But Tony pointed out that the ray might intersect just once at a tangent point while its origin was outside the boundary. So that's when I came up with the idea of summing all the deflections, ergo "Look ma, no rays!"
In reviewing and testing my old code for Aquaman I found that my @bulge function assumes that if the PIQ is within a bulged segment that it would fall within the start and end angles measured from the arc's center. Not necessarily. with an extreme bulge (internal angle > 180°), the PIQ might fall not within the arc's sector, but between the chord and the center point. I don't remember why I thought I needed the @bulge function, but now I am thinking of just sticking to the deflections alone. That is with the exception of checking to see if the PIQ is actually on the boundary.
I'm busy doing something else right now, but I'll let you know.
John F. Uhden
Hi John.Uhden,
Thanks for the heads up, and feedback from your experience and expertise.
I personally did not understand much of the technical concerns what you pointed out, but I am sure this has to be looked into before finalizing on it. Any expert advise from any of the CAD gurus are really appreciated, so that we can point out clearly the limitations of these routines.
Regards,
VA
If I can make my @Anonymous function foolproof, then Danny can plug it into his more robust code and you would have no worries.
John F. Uhden
John,
You're absolutely right, that's why I'm no longer using just one ray to determine if a point is inside the boundary. This was the reason one of my earlier version was missing some blocks as the ray from the insertion point intersected with an even number of points, while being on the inside of the boundary. This was caused by exactly the possibility you described.
But the modified routine doesn't just use one ray but multiple, in this case 12 or 24 depending on angle conditions, in all directions. If the point is inside the boundary all rays should have intersections with the boundary and the number of intersections doesn't really matter.
Well, thinking about it......it actually does if all rays intersect an even times with the polyline, because in that case the point is enclosed by a complex polyline but actually outside the boundary. I'll make some new modifications to the routine for the situation below.
@john.uhden wrote:Be careful there, Danny. We've been through his before. I think it was back around 2003, which was probably before Kent's time so it was probably Tony T. jumping in. I had had the theory that if a ray intersects the boundary an odd number of times, then the origin must be inside the boundary. But Tony pointed out that the ray might intersect just once at a tangent point while its origin was outside the boundary. So that's when I came up with the idea of summing all the deflections, ergo "Look ma, no rays!"
@Anonymous reviewing and testing my old code for Aquaman I found that my @bulge function assumes that if the PIQ is within a bulged segment that it would fall within the start and end angles measured from the arc's center. Not necessarily. with an extreme bulge (internal angle > 180°), the PIQ might fall not within the arc's sector, but between the chord and the center point. I don't remember why I thought I needed the @bulge function, but now I am thinking of just sticking to the deflections alone. That is with the exception of checking to see if the PIQ is actually on the boundary.
I'm busy doing something else right now, but I'll let you know.
Is the red point inside the green polygon ?
It's not like this problem hasn't been studied to death. There's plenty of research on different algorithms. For LISP-based solutions in AutoCAD, I'll still put my money on the BOUNDARY kludge, because it uses the same underlying ACIS APIs that are the product of many years of work.
In ObjectARX, this is a one-liner.
@Anonymous, see the code below with multiple selection options.
By default it will ask to select a boundary and I've expanded the boundary selection to splines, circles & ellipses besides polyline.
During this prompt you can select 'All' or 'User' (right-click menu, click on keyword in commandline, or key in the wanted option).
The 'All' option will select all blocks in the drawing (but will only count the ones with a valid value in the KEY attribute).
The 'User' option gives you the standard AutoCAD select functionality to manually select the blocks. By default this will be select on object or window/crossing depending if you move right/left after selecting a point. As this is normal selection behavior you can use all selection options AutoCAD offers, like Fence, CrossingPolygon, etc.. So if you want to force a Window selection independent if you drag left/right, just key in 'W' at the select objects prompt. The selected blocks are also only counted if the KEY attribute has a valid value).
This modified code is also more accurate in determining if a block is inside the boundary or not. I've found the previous versions to include the blocks if they were exactly on the edge of the boundary, but with this modified code those blocks are excluded (this can be made optional). Also blocks completely enclosed by a complex polyline (see second image in my previous post) will now be excluded.
Another addition is when you need to select a location for your table, the size of the table will now be shown as a preview to make it easier to select a suitable location. However, due to this preview you cannot use the OSNAPS to select an insertion point for your table.
(defun c:BCPL (/ *error* BCPL_Block BCPL_BLOCKID BCPL_BlockList BCPL_BlockListSorted BCPL_BlockName BCPL_BoundaryCheck BCPL_CheckLine BCPL_Count BCPL_Entity BCPL_EntityList BCPL_IntersectPoints BCPL_LowerLeft BCPL_NewLocation BCPL_OBJECT BCPL_PointList BCPL_Previous BCPL_Result BCPL_RowCount BCPL_Selection BCPL_SortOrder BCPL_TableObject BCPL_UpperRight BCPL_UserSelect) (defun *error* (BCPL_Message) (if (and (= (type BCPL_TableObject) 'VLA-OBJECT) (not (vlax-object-released-p BCPL_TableObject)) ) (progn (vla-Delete BCPL_TableObject) (setq BCPL_TableObject nil) ) ) (if (not (wcmatch (strcase BCPL_Message T) "*break,*cancel*,*exit*")) (princ (strcat "\nError: " BCPL_Message)) ) (princ) ) (if (or (and (not (initget (+ 1 2 4) "All User")) (setq BCPL_UserSelect (entsel "\nSelect polyline boundary [All/User]: ")) (/= (type BCPL_UserSelect) 'STR) (= (type (car BCPL_UserSelect)) 'ENAME) (member (vla-get-ObjectName (setq BCPL_Object (vlax-ename->vla-object (car BCPL_UserSelect)))) '("AcDbPolyline" "AcDb2dPolyline" "AcDbCircle" "AcDbEllipse" "AcDbSpline")) (vlax-Curve-isClosed BCPL_Object) (vlax-Curve-isPlanar BCPL_Object) (setq BCPL_PointList (_GetBoundingBox BCPL_Object)) (setq BCPL_Selection (ssget "_CP" BCPL_PointList '((0 . "INSERT")))) ) (and (= BCPL_UserSelect "All") (setq BCPL_Selection (ssget "_X" '((0 . "INSERT")))) ) (and (= BCPL_UserSelect "User") (princ "\nSelect blocks: ") (setq BCPL_Selection (ssget '((0 . "INSERT")))) ) ) (progn (vla-StartUndoMark (vla-get-ActiveDocument (vlax-get-acad-object))) (acet-ui-progress "Counting" (sslength BCPL_Selection)) (setq BCPL_Count 0) (foreach BCPL_Entity (vl-remove-if '(lambda (BCPL_Item) (listp (cadr BCPL_Item))) (ssnamex BCPL_Selection)) (acet-ui-progress (setq BCPL_Count (1+ BCPL_Count))) (setq BCPL_EntityList (entget (setq BCPL_Entity (cadr BCPL_Entity)))) (if (or (= BCPL_UserSelect "All") (= BCPL_UserSelect "User") (_PointInside (cdr (assoc 10 BCPL_EntityList)) BCPL_Object nil) ) (progn (if (not (assoc (setq BCPL_BlockID (list (cdr (assoc 2 BCPL_EntityList)) (_ExtractAttributeTagValue (vlax-ename->vla-object BCPL_Entity) "KEY"))) BCPL_BlockList)) (setq BCPL_BlockList (cons (list BCPL_BlockID 1) BCPL_BlockList)) (setq BCPL_BlockList (subst (list BCPL_BlockID (1+ (cadr (assoc BCPL_BlockID BCPL_BlockList)))) (assoc BCPL_BlockID BCPL_BlockList) BCPL_BlockList)) ) ) (ssdel BCPL_Entity BCPL_Selection) ) (if BCPL_CheckLine (entdel BCPL_CheckLine)) ) (acet-ui-progress) (princ (strcat "\n ** Total number of blocks found: " (itoa (sslength BCPL_Selection)) "\n")) (if (and BCPL_BlockList (not (initget 1)) (not (vl-catch-all-error-p (setq BCPL_TableObject (vl-catch-all-apply '_CreateTable (list (vlax-3d-point '(0.0 0.0 0.0)) (+ (length BCPL_BlockList) 2) 4 11.25 60.0))))) ) (progn (vla-put-Visible BCPL_TableObject :vlax-false) (setq BCPL_SortOrder (list "[A-Z]" "[a-z]" "[a-z][a-z]")) (acet-ui-progress "Sorting Blocks" (length BCPL_BlockList)) (setq BCPL_Count 0) (foreach BCPL_Pattern BCPL_SortOrder (acet-ui-progress (setq BCPL_Count (1+ BCPL_Count))) (setq BCPL_BlockListSorted (append BCPL_BlockListSorted (vl-sort (vl-remove-if-not '(lambda (BCPL_Item)(wcmatch (cadr (car BCPL_Item)) BCPL_Pattern)) BCPL_BlockList) '(lambda (BCPL_Item1 BCPL_Item2) (< (cadr (car BCPL_Item1))(cadr (car BCPL_Item2))))))) ) (acet-ui-progress) (acet-ui-progress "Creating Table" (length BCPL_BlockListSorted)) (setq BCPL_Count 0) (vla-SetColumnWidth BCPL_TableObject 2 100.0) (vla-SetColumnWidth BCPL_TableObject 3 40.0) (vla-SetAlignment BCPL_TableObject acDataRow acMiddleCenter) (vla-SetTextHeight2 BCPL_TableObject 0 0 0 6.0) (vla-SetText BCPL_TableObject 0 0 "Block Data") (vla-SetText BCPL_TableObject 1 0 "Block Preview") (vla-SetText BCPL_TableObject 1 1 "Attribute Value") (vla-SetText BCPL_TableObject 1 2 "Block Name") (vla-SetText BCPL_TableObject 1 3 "Count") (setq BCPL_RowCount 2) (foreach BCPL_Item BCPL_BlockListSorted (acet-ui-progress (setq BCPL_Count (1+ BCPL_Count))) (if (not (vl-catch-all-error-p (setq BCPL_Block (vl-catch-all-apply 'vla-item (list (vla-get-Blocks (vla-get-ActiveDocument (vlax-get-acad-object))) (nth 0 (nth 0 BCPL_Item))))))) (vl-catch-all-apply 'vla-SetBlockTableRecordId (list BCPL_TableObject BCPL_RowCount 0 (vla-get-ObjectId BCPL_Block) :vlax-true)) ) (vla-SetText BCPL_TableObject BCPL_RowCount 1 (cadr (car BCPL_Item))) (vla-SetCellAlignment BCPL_TableObject BCPL_RowCount 2 acMiddleLeft) (vla-SetText BCPL_TableObject BCPL_RowCount 2 (car (car BCPL_Item))) (vla-SetText BCPL_TableObject BCPL_RowCount 3 (itoa (cadr BCPL_Item))) (setq BCPL_RowCount (1+ BCPL_RowCount)) ) (acet-ui-progress) (vla-GetBoundingBox BCPL_TableObject 'BCPL_LowerLeft 'BCPL_UpperRight) (princ "\nSpecify table position: ") (setq BCPL_NewLocation (_DrawBox (vlax-safearray->list BCPL_LowerLeft) (vlax-safearray->list BCPL_UpperRight))) (if (listp BCPL_NewLocation) (progn (vla-Move BCPL_TableObject (vlax-3d-point '(0.0 0.0 0.0)) (vlax-3d-point BCPL_NewLocation)) (vla-put-Visible BCPL_TableObject :vlax-true) ) (progn (vla-Delete BCPL_TableObject) (princ "\n ** Table creation cancelled by user!") ) ) (vlax-release-object BCPL_TableObject) (setq BCPL_TableObject nil) ) ) (vla-EndUndoMark (vla-get-ActiveDocument (vlax-get-acad-object))) ) ) (princ) ) (defun _DrawBox (DB_Point1 DB_Point2 / DB_Distance DB_Width DB_Height DB_UserPoint) (setq DB_Distance (mapcar '- DB_Point2 DB_Point1)) (setq DB_Width (nth 0 DB_Distance)) (setq DB_Height (nth 1 DB_Distance)) (while (= (car (setq DB_UserPoint (grread nil 13 0))) 5) (redraw) (setq DB_UserPoint (reverse (cdr (reverse (cadr DB_UserPoint)))) DB_UserPoint (trans DB_UserPoint 1 2) ) (grvecs (list 1 DB_UserPoint (list (+ (car DB_UserPoint) DB_Width) (cadr DB_UserPoint)) (list (+ (car DB_UserPoint) DB_Width) (cadr DB_UserPoint)) (list (+ (car DB_UserPoint) DB_Width) (- (cadr DB_UserPoint) DB_Height)) (list (+ (car DB_UserPoint) DB_Width) (- (cadr DB_UserPoint) DB_Height)) (list (car DB_UserPoint) (- (cadr DB_UserPoint) DB_Height)) (list (car DB_UserPoint) (cadr DB_UserPoint)) (list (car DB_UserPoint) (- (cadr DB_UserPoint) DB_Height)) ) ) ) (redraw) (cadr DB_UserPoint) ) (defun _CreateTable (CT_InsertionPoint CT_Rows CT_Columns CT_RowHeigth CT_ColumnWidth) (vl-catch-all-apply 'vla-addTable (list (vlax-get-property (vla-get-ActiveDocument (vlax-get-acad-object)) (if (= 1 (getvar 'cvport)) 'paperspace 'modelspace)) CT_InsertionPoint CT_Rows CT_Columns CT_RowHeigth CT_ColumnWidth)) ) (defun _GetBoundingBox (GBB_Object / GBB_LowerLeft GBB_UpperRight) (if (vlax-method-applicable-p GBB_Object 'GetBoundingBox) (progn (vla-GetBoundingBox GBB_Object 'GBB_LowerLeft 'GBB_UpperRight) (setq GBB_LowerLeft (vlax-safearray->list GBB_LowerLeft)) (setq GBB_UpperRight (vlax-safearray->list GBB_UpperRight)) (list GBB_LowerLeft (list (nth 0 GBB_UpperRight) (nth 1 GBB_LowerLeft) 0.0) GBB_UpperRight (list (nth 0 GBB_LowerLeft) (nth 1 GBB_UpperRight) 0.0) ) ) ) ) (defun _ExtractAttributeTagValue (EATV_BlockObject EATV_Tag / EATV_Return) (if (and (= (type EATV_BlockObject) 'VLA-OBJECT) (= (vla-get-ObjectName EATV_BlockObject) "AcDbBlockReference") (= (vla-get-HasAttributes EATV_BlockObject) :vlax-true) (= (type EATV_Tag) 'STR) ) (progn (foreach EATV_Attribute (vlax-safearray->list (vlax-variant-value (vla-GetAttributes EATV_BlockObject))) (if (and (not EATV_Return) (= (strcase (vla-get-TagString EATV_Attribute)) (strcase EATV_Tag)) ) (setq EATV_Return (vla-get-TextString EATV_Attribute)) ) ) ) ) (if (not EATV_Return) "" EATV_Return ) ) (defun _PointInside (PI_Point PI_PolylineObject PI_OnEdgeTrue / PI_ClosestPoint PI_Distance PI_RayAngles PI_AngleList PI_RefAngle PI_AngleList PI_CheckLine PI_IntersectPoints PI_CheckList PI_Return) (setq PI_ClosestPoint (vlax-curve-GetClosestPointTo PI_PolylineObject PI_Point)) (setq PI_Distance (distance PI_Point PI_ClosestPoint)) (if (not (equal PI_Distance 0.0)) (progn (setq PI_RayAngles (_AngleList 12)) (setq PI_AngleList (mapcar '(lambda (PI_Factor) (* pi PI_Factor)) PI_RayAngles)) (if (not (equal (rem (setq PI_RefAngle (angle PI_Point PI_ClosestPoint)) (/ pi 2.0)) 0)) (setq PI_AngleList (append (mapcar '(lambda (PI_Factor) (+ (* pi PI_Factor) PI_RefAngle )) PI_RayAngles) PI_AngleList)) ) (foreach PI_Angle PI_AngleList (setq PI_CheckLine (entmakex (list '(0 . "RAY") '(100 . "AcDbEntity") '(100 . "AcDbRay") (cons 10 PI_Point) (cons 11 (mapcar '- (polar PI_Point PI_Angle 5.0) PI_Point))))) (if (not (vl-catch-all-error-p (setq PI_IntersectPoints (vl-catch-all-apply 'vlax-safearray->list (list (vlax-variant-value (vla-IntersectWith (vlax-ename->vla-object PI_CheckLine) PI_PolylineObject acExtendNone))))))) (setq PI_CheckList (cons (length (_GroupByNum PI_IntersectPoints 3)) PI_CheckList)) (setq PI_CheckList (cons nil PI_CheckList)) ) (entdel PI_CheckLine) ) ) ) (cond ( (and (equal PI_Distance 0.0) PI_OnEdgeTrue ) T ) ( (and (not (vl-some 'not PI_CheckList)) (vl-some '(lambda (PI_Value) (= (rem PI_Value 2) 1)) PI_CheckList) ) T ) ( T nil ) ) ) (defun _AngleList (AL_Integer / AL_Step AL_Factor AL_Return) (setq AL_Step (/ 2.0 AL_Integer)) (setq AL_Factor 0.0) (repeat AL_Integer (setq AL_Return (append AL_Return (list (+ 0.0 AL_Factor)))) (setq AL_Factor (+ AL_Factor AL_Step)) ) AL_Return ) ; By Lee Mac (defun _GroupByNum (l n / r) (if l (cons (reverse (repeat n (setq r (cons (car l) r) l (cdr l)) r)) (_GroupByNum l n)) ) )
Hi DannyNL,
Thank You so much Danny. I think we have a comprehensive, robust and user friendly routine now. From what all tests I have done so far, every thing is working great. When I say every thing, I am saying about my kind of work flow. I highly suggest other users to try this routine out so that if any improvement needed can be done.
Personally I want to thank all the CAD experts out here who put their valuable inputs and contributed with their years of knowledge and experience in improving this product in each stage of its development. I would like to specially mention experts like Kent1Cooper, John.Uhden, BeeKeeCZ, CADffm, ActivistInvester,lando7189, and maratovich in this regard. I would like to also thank Cad Elite users like Lee-Mac Programming, and any other users whose previous work/ routines has contributed or used by experts who worked on this current routines. All credit goes to those users whose code/ logic/ wisdom has contributed in developing this current code.
To sum every thing up:
DannyNL's routine which has come up as a final user interactive product. The Name of the routine is BCPL
We have got other routines from the following people which is equally good and working, and could be used as routines for further improvement and consideration for different workflows. These could also be used for checking and evaluating other routines of similar nature. Those include:
John.Uhden's - BIB
ActivistInvester's - SELECTCONTAINEDINSERTS
BeeKeeCZ's - BCountIn
All four routines are working great in most of my tests/ evaluations in my work flow. Please find attached all the lisps ( 4 routines zipped) and the AutoCAD file to check these routines if other users want to use it/ evaluate/ improve upon it.
I am not sure whether I can click Accept as Solution for more than one posts. I really want to Accept these Four Routines as Solutions, but first I am going to Accept DannyNL's routine. If it allows me to do more than one, then I will accept the other three as well.
Thank you all once again for making this happen and rendering help to the CAD community.
Regards,
VA
John F. Uhden
Can't find what you're looking for? Ask the community or share your knowledge.