Author Topic: GetCentroid Using AcDbRegion Class ?  (Read 7093 times)

0 Members and 1 Guest are viewing this topic.

LE

  • Guest
GetCentroid Using AcDbRegion Class ?
« on: March 30, 2006, 11:30:32 PM »
Hello ALL;

Here is my code to get the centroid from a closed AcDbPolyline object, I think all the steps are correct, the only problem is the location of the CENTROID.... is coming closed to the 0,0 origin... how can be adjusted to get the right value?

Thanks!

Code: [Select]
static int ads_getcentroid(void)
{
Acad::ErrorStatus es;
ads_point pt;
ads_name ename;

// pick a polyline if not exit
if (acedEntSel ("\nSelect polyline: ", ename, pt) != RTNORM) return RSERR;

// get the object id if not exit
AcDbObjectId objId;
if (acdbGetObjectId (objId, ename) != Acad::eOk) return RSERR;

// open our object for read
AcDbObjectPointer<AcDbCurve> pCurve(objId, AcDb::kForRead);
   
// try to opened if not exit with error
if ((es = pCurve.openStatus()) != Acad::eOk) {
acutPrintf("\npCurve.openStatus()=%s", acadErrorStatusText(es)); 
return RSERR;
}

// cast our object into the polyline
AcDbPolyline *pPoly = AcDbPolyline::cast(pCurve.object());

// if not closed exit
if (!pPoly->isClosed()) return RSERR;

AcDbVoidPtrArray array;
AcDbVoidPtrArray regions;

array.append(pPoly);
AcDbRegion::createFromCurves(array, regions);

assert(regions.length() == 1);
AcDbRegion *pRegion = AcDbRegion::cast((AcRxObject*)regions[0]);
assert(pRegion != NULL);

AcGePoint3d origin;
AcGeVector3d xAxis;
AcGeVector3d yAxis;
double perimeter;
double area;
AcGePoint2d centroid;
double momInertia[2];
double prodInertia;
double prinMoments[2];
AcGeVector2d prinAxes[2];
double radiiGyration[2];
AcGePoint2d extentsLow;
AcGePoint2d extentsHigh;

origin += xAxis*centroid[0];
origin += yAxis*centroid[1];
centroid.x = origin.x;
centroid.y = origin.y;

AcGePlane plane;
pRegion->getPlane(plane);
plane.getCoordSystem(origin, xAxis, yAxis);

if (pRegion->getAreaProp(origin,xAxis,yAxis,perimeter,area,centroid,momInertia,prodInertia,prinMoments,prinAxes,radiiGyration,extentsLow,extentsHigh) == Acad::eOk) {
acedRetPoint(asDblArray(centroid));
}
for (int i = 0; i < regions.length(); i++) {
delete (AcRxObject*)regions[i];
}
return (RSRSLT) ;
}

BTW, something I  do not like of this method/approach/class is that the first time we call the function it will place the loading message on the task bar about "Loading Modeller DLL... " and also right after getting the property, getting rid of the auxiliary objects [regions]... not elegant.

I have done the contrapart using just math and geometry calculus, but I want to use this for now.

Alexander Rivilis

  • Bull Frog
  • Posts: 212
  • Programmer from Kyiv (Ukraine)
Re: GetCentroid Using AcDbRegion Class ?
« Reply #1 on: March 31, 2006, 06:40:24 AM »
 :-D
Hi Luis!
try this code:
Code: [Select]
class SuppressOutput : public AcDbHostApplicationServices
{
public:
  SuppressOutput() {
    m_pOld = acdbHostApplicationServices();
    acdbSetHostApplicationServices(this);
    setWorkingGlobals(m_pOld->workingGlobals());
    setWorkingDatabase(m_pOld->workingDatabase());
  }

  virtual ~SuppressOutput()
  {
    acdbSetHostApplicationServices(m_pOld);
    setWorkingGlobals(m_pOld->workingGlobals());
    setWorkingDatabase( m_pOld->workingDatabase() );
  }

  virtual Acad::ErrorStatus findFile(
    char* pcFullPathOut,
    int   nBufferLength,
    const char* pcFilename,
    AcDbDatabase * pDb,
    AcDbHostApplicationServices::FindFileHint hint )
  {
    return Acad::eOk;
  }

  virtual AcadInternalServices* acadInternalServices()
  {
    return m_pOld->acadInternalServices();
  }
  virtual const ProdIdCode prodcode() { return m_pOld->prodcode();}

  virtual void displayChar(char c) const { return; }
  virtual void displayString(const char* string, int count) const { return; }

private:
  AcDbHostApplicationServices *m_pOld;
};


// ----- ads_getcentriod symbol (do not rename)
static int ads_getcentroid(void)
{
  Acad::ErrorStatus es;
  ads_point pt;
  ads_name ename;
  if (!acdbIsModelerStarted()) {
    SuppressOutput s; // Suppress all output string
    acdbModelerStart();
  }
  // pick a polyline if not exit
  if (acedEntSel ("\nSelect polyline: ", ename, pt) != RTNORM) return RSERR;

  // get the object id if not exit
  AcDbObjectId objId;
  if (acdbGetObjectId (objId, ename) != Acad::eOk) return RSERR;

  // open our object for read
  AcDbObjectPointer<AcDbCurve> pCurve(objId, AcDb::kForRead);

  // try to opened if not exit with error
  if ((es = pCurve.openStatus()) != Acad::eOk) {
    acutPrintf("\npCurve.openStatus()=%s", acadErrorStatusText(es)); 
    return RSERR;
  }

  // if not closed exit
  if (!pCurve->isClosed()) return RSERR;

  AcDbVoidPtrArray array;
  AcDbVoidPtrArray regions;

  array.append(pCurve.object());

  // Suppress error message if AutoCAD can not create region
  {
    SuppressOutput s;
    AcDbRegion::createFromCurves(array, regions);
  }

  if (regions.length() == 0) return (RSERR);
  AcDbRegion *pRegion = AcDbRegion::cast((AcRxObject*)regions[0]);
  if (!pRegion) return (RSERR);   

  AcGePoint3d origin;
  AcGeVector3d xAxis;
  AcGeVector3d yAxis;
  AcGeVector3d zAxis;
  double perimeter;
  double area;
  AcGePoint2d centroid;
  double momInertia[2];
  double prodInertia;
  double prinMoments[2];
  AcGeVector2d prinAxes[2];
  double radiiGyration[2];
  AcGePoint2d extentsLow;
  AcGePoint2d extentsHigh;

  AcGePlane plane;
  pRegion->getPlane(plane);
  AcGeMatrix3d mat;  mat.setToPlaneToWorld(plane);
  mat.getCoordSystem(origin, xAxis, yAxis,zAxis);
  if (pRegion->getAreaProp(origin,xAxis,yAxis,perimeter,area,centroid,momInertia,prodInertia,prinMoments,prinAxes,radiiGyration,extentsLow,extentsHigh) == Acad::eOk) {
    AcGePoint3d centroid3d(centroid.x,centroid.y,0.0);
    // Translate in WCS
    centroid3d.transformBy(mat);
    // Return point in WCS
    acedRetPoint(asDblArray(centroid3d));
  }
  for (int i = 0; i < regions.length(); i++) {
    delete (AcRxObject*)regions[i];
  }
  return (RSRSLT) ;
}
« Last Edit: March 31, 2006, 09:22:02 AM by Rivilis »

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #2 on: March 31, 2006, 09:38:37 AM »
Thank you Alexander;

Let me test it.

I see that I was at least very close no? for a first time he he  :-D


Thanks!

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #3 on: March 31, 2006, 10:31:00 AM »
As normal... it works.

But, I still see the first time message loading of the modeller on the task bar:

Loading Modeller Dlls......

Takes a little waiting time, and then works normally.

Now, we are placing a new entity [region] on top of our polyline, get our desired data and deleted... what is the advantage, of this?... if we can do the same with Visual Lisp.... hmm

Thanks!

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #4 on: March 31, 2006, 10:38:55 AM »
For example Alexander I used the below routine to get the centroid using Visual Lisp, and my idea was to come up with another approach instead, like the one that implements the calculation...

Code: [Select]
(vl-load-com)

(defun draw_point  (pt col / d)
  (setq d (* (getvar "viewsize") 0.015))
  (grdraw
    (trans (polar pt (* 0.25 pi) d) 0 1)
    (trans (polar pt (* 1.25 pi) d) 0 1)
    col
    (- col))
  (grdraw
    (trans (polar pt (* 0.75 pi) d) 0 1)
    (trans (polar pt (* 1.75 pi) d) 0 1)
    col
    (- col)))

(defun gb_getcentroid  (obj / array new_array centroid_pt base)
  (setq array (vlax-make-safearray vlax-vbObject '(0 . 0)))
  (vlax-safearray-put-element array 0 obj)
  (setq new_array
(vlax-invoke-method
   (vla-objectidtoobject
     (setq base (vla-get-database obj))
     (vla-get-ownerid obj))
   'AddRegion
   array))
  (vlax-release-object base)
  (setq centroid_pt
(vlax-get
   (setq region (vlax-safearray-get-element
  (vlax-variant-value new_array)
  0))
   'centroid))
  (vla-delete region)
  centroid_pt)

(if (not (vl-bb-ref 'gb_loaded))
  (progn
    (vl-bb-set 'gb_loaded t)
    (setq model (vla-get-modelspace
  (setq doc (vla-get-activedocument
      (setq acad_obj (vlax-get-acad-object))))))
    (if (not (vl-catch-all-error-p
       (setq vla_circle
      (vl-catch-all-apply
'vla-addcircle
(list model
      (vlax-3d-point (list 0.0 0.0 0.0))
      1.0)))))
      (progn
(gb_getcentroid vla_circle)
(vla-delete vla_circle)))
    (setq model nil
  doc nil
  acad_obj
   nil
  vla_circle nil)))

(defun C:SHOWCENTROID  (/ ename obj)
  (if (and (setq ename (car (entsel "\nSelect a polyline: ")))
   (wcmatch (cdadr (entget ename)) "*POLYLINE")
   (setq obj (vlax-ename->vla-object ename))
   (= (vla-get-closed obj) :vlax-true))
    (draw_point (gb_getcentroid obj) 7))
  (princ))

(princ)

Alexander Rivilis

  • Bull Frog
  • Posts: 212
  • Programmer from Kyiv (Ukraine)
Re: GetCentroid Using AcDbRegion Class ?
« Reply #5 on: March 31, 2006, 10:52:29 AM »
As normal... it works.

But, I still see the first time message loading of the modeller on the task bar:

Loading Modeller Dlls......
Insert next code in On_kInitAppMsg() method:
Code: [Select]
   if (!acdbIsModelerStarted()) {
    SuppressOutput s; // Suppress all output string
    acdbModelerStart();
  }
Now, we are placing a new entity [region] on top of our polyline, get our desired data and deleted... what is the advantage, of this?... if we can do the same with Visual Lisp.... hmm
We did not placing region to dwg - it exist inly in memory but not in dwg!!! VisualLisp can not do the same!!!

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #6 on: March 31, 2006, 11:02:58 AM »
Let me do that.... I'll be back

[I got the impression that it was deleting a new created object - now I get it... thanks!]

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #7 on: March 31, 2006, 11:22:29 AM »
I see Alexander;

Now that message is shown on loading time of the arx... [for some reason I never like that, but if that's the only way... well]

OK, I am going back into my approach of using calculus... I will implement some of the functions of yours for the polylines segments... and see if I can make that work too..


Thanks!

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #8 on: March 31, 2006, 04:21:05 PM »
Something I noticed on the corrected code, was that you took out:

Quote
// cast our object into the polyline
AcDbPolyline *pPoly = AcDbPolyline::cast(pCurve.object());

It was to avoid filtering just lightweight polylines... good.

BTW, did you were able to test the other code [the one I did using calculation] ? or do you recommend this approach better?

Thanks!


Alexander Rivilis

  • Bull Frog
  • Posts: 212
  • Programmer from Kyiv (Ukraine)
Re: GetCentroid Using AcDbRegion Class ?
« Reply #9 on: March 31, 2006, 04:31:48 PM »
BTW, did you were able to test the other code [the one I did using calculation] ? or do you recommend this approach better?
I've not tested yours calculation code,  but:
1) yours code only for LWPOLYLINE - with using AcDbRegion: LWPOLYLINE, POLYLINE, CIRCLE, ELLIPSE, SPLINE, etc.
2) yours code is only for LWPOLYLINE which lie in WCS plan - with AcDbRegion: in any UCS.
I think it is better using AcDbRegion, but if you can using own calculation - it is yours rights! :)

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #10 on: March 31, 2006, 04:33:28 PM »
10-4

 8-)

cornbread

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #11 on: April 03, 2006, 11:12:24 AM »
If you have an array of the X coordinates and an array of the Y coordinates this is roughly how you get the centroid of a non-self intersecting polygon. Remember a centroid is actually a center of mass so if your polygon is shaped like a skinny C or something like that the centroid may not be inside the polygon. Also this only works in a cartesian system if you are working on an ellipse IE working in Lat / Lon then this won't work for anything but small distances. I didn't check this so it might have an error or two but it should be basically correct.

Code: [Select]
void get_centroid(int iNumVerts, double *Xs, double *Ys, double *x, double *y){
double ai, atmp = 0, xtmp = 0, ytmp = 0;
for(int i=0;i<(iNumVerts-1);i++){
ai = Xs[i] * Ys[i+1] - Xs[i+1] * Ys[i];
atmp += ai;
xtmp += (Xs[i+1] + Xs[i]) * ai;
ytmp += (Ys[i+1] + Ys[i]) * ai;
}
*x = (xtmp / (3 * atmp));
*y = (ytmp / (3 * atmp));
return;
}

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #12 on: April 03, 2006, 04:20:53 PM »
Hi CB;

Have not tried your function, is in there included the arc segments?...

cornbread

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #13 on: April 03, 2006, 06:28:40 PM »
In geometry there is no such thing as an arc in a polygon. The above is just a straight polygon centroid calculation. To account for Acad's bizarre version of a polygon which can contain arcs you would have to add further calculation to that function.
« Last Edit: April 03, 2006, 07:58:15 PM by cornbread »

LE

  • Guest
Re: GetCentroid Using AcDbRegion Class ?
« Reply #14 on: April 03, 2006, 06:34:03 PM »
Yep they call it bulge's.... I did the calcs, and could not finished [some little portions of the code, that could not make it to work], left the geometry function on the side, after getting this new approach [for me] with the help of Rivilis.

Thanks.