luowy 发表于 2020-9-20 17:47:28

Arx学习心得

我在学arx的一些心得,大家觉得有用就拿去用吧。没有用就当抛砖引玉了。
Arx学习心得1.2009年5月14日星期四ACHAR的初始化应该采用 ACHAR char1[] =_T( "初始化");ACHAR *char2;char2 = _T( "初始化");的格式,其中因为AutoCAD定义的ACHAR为          typedef wchar_t ACHAR;因此ACHAR就具备了wchar_t的属性了,根据这点,有以下函数可以调用,分别是:   WCSCAT------------------------ACHAR的合并   WCSCPY------------------------ACHAR的复制   WCSLEN------------------------ACHAR的长度   _tcscmp------------------------代替strcmp,比较ACHAR   StrCpy---------------------------代替strcpy,跟WCSCPY一样
2.2009年5月15日星期五对于vlsip中的getpoint来说,可以提示一些东西,比如:          (setq ptInsert (getpoint (strcat “\n输入下一点/或S进行字高设定” (atoi zigao)))来提示在获取点的等待中的字高值,但是在ObjectArx中,却不能这么做,这里牵涉到一个非常好用,但上手有点模糊的东西——格式说明符。   之前模仿过一个          acutPrintf( _T(“\n当前的字高是:%.2f”, zigao);我以为这个可以在acedGetPoint中用到,          Retcode = acedGetPoint( NULL, ( _T(“\n当前的字高是:%d”, zigao), ptInsert);但上面的代码是错误的,总是提示不能转换。相关的资料也没有说对arx08等支持UNICODE的格式说明。   我用          ACHAR prompt[] = ( _T(“\n当前的字高是:%.2f”, zigao);          Retcode = acedGetPoint( NULL, prompt, ptInsert);这样也是不行的。   幸亏有人帮忙,指出合适的操作方法,方法如下            CString str;          str.Format(_T("\n指定一点或输入<S>设置字高:%.2f"), scaleFactor);          Retcode = acedGetPoint( NULL, str, ptInsert);这样就可以了,看来arx的文字格式说明符的确用途很广。3.2009年5月25日星期一关于默认输入的问题本来在lisp中,一个变量可以在内存中存在是很简单就可以实现的,如以下的内容就很简单:(setq aaa ‘1.0)这样aaa变量就可以在这个图形中存在,直到这个图形关闭为止。可是在ObjectARX中,如何实现倒是个非常麻烦的事情,因为我不知道怎么搞。在摸索一段时间后,终于明白和实现这部分内容了。以下是步骤:在CDocData类的头文件中加入一个变量,这里我以ads_real为例。class CDocData {public:   ads_real myReal;   CDocData () ;   CDocData (const CDocData &data) ;   ~CDocData () ;} ;         然后在CDocData类的实现文件中添加默认值:CDocData::CDocData(const CDocData &data) {   ads_real myReal = 1.5;}                  这样就可以用这个全局变量了,下面是测试的函数:   static voidralftt_rrr(void)   {         ads_real oldReal;         ads_real newReal;         ads_real inputReal;         int retcode;          oldReal = DocVars.docData().myReal; //这里获取了在CDocData中的MyReal值。                  if ( oldReal < 0.00000000001 ) //这里实际就是确认原始数是否为0         {            oldReal = 1.5;         }          CString prompt;         prompt.Format(_T( "\n当前的real是:%.2f,\n输入新的real:"), oldReal );         retcode = acedGetReal( prompt,&newReal );         if ( retcode == RTNORM )         {            DocVars.docData().myReal = newReal;//这里将参数返回到全局变量中。         }            }
4.2009年5月26日星期二关于使用acdbOpenObject今天想使用acdbOpenObject来打开一个AcDbObjectId,但是怎么编译都不成功,原来是在说明指针的时候不对。AcDbBlockReference blkRef;acdbOpenObject( blkRef, blkId, AcDb::kforWrite );blkRef->erase( true );上面的代码怎么看都是对的,但就是不能被编译,最后查明问题,就是在说明指针的时候没有使用间接引用,将第一句改为   AcDbBlockReference *blkRef;这样就可以了,指针虽然是C++里面最好的东西,但掌握的时候却非常麻烦,学习的时候已经异常花时间去掌握,可是在使用的时候总是忽略,这是亟待解决的问题。5.2009年6月15日星期一如何统计曲线长度的问题关于曲线长度的计算,原以为有相关函数可以用来直接获得,但查阅了ARX的参考手册,却并没有发现这“应该有”的函数。如果是线段倒是简单,先获得起点坐标,再获得终点的坐标,然后利用distance函数来计算线段的长度。可是对于圆弧、多线段等几何图形来讲,却不能用此方法,因为这些图形的长度跟平面中的路径有关。以下是解决方法:圆弧、多线段、线段等都属于acdbcurve类,这个类里面几个函数:pCurve->getEndParam( endParam );pCurve->getDistAtParam( endParam, length );上面第一句是获得曲线终点的参数endParam,根据参考的描述,就是: The implementation of thisfunction in derived classes should return with endParam set to the parameter ofthe endpoint of the curve.即是:在衍生类中使用这个函数,可以返回这曲线的终点参数。其实点在本段曲线的参数实际上就是从本段起点到参考点的长度与曲线总长的比。如果是复杂曲线,就以像2.50形式存在,其中2表示该段在曲线中是第三段曲线,0.50表示该段起点到参考点的长度与该段曲线长度的比为0.50。(线段的参数就是从起点到该参考点的长度)第二句是获得曲线的长度,endParam是终点参数,length是曲线长度。file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.pnggetDistAtParam是获得曲线起点至参考点间的曲线长度,也就是说,当refParam与endParam为同一点时,将获得曲线的全部长度。获得指定一点的曲线上点的参数可使用函数getParamAtPoint来获取。6.2010年7月9日星期五使用sendStringtoExecute(pDoc, _T(“\003”) )可以清除当前已经选中的实体。7. 2010年10月10日星期日使用钩子使用钩子关键是灵活使用ARX提供的2个函数acedRegisterFilterWinMsg及acedRemoveFilterWinMsg以下是基本代码:Hstatic BOOL registerHook();                //这里是注册钩子    static BOOL removeHook();               //这里是移除钩子    static BOOL callback( MSG *msg );         //这里是回调函数
CPPBOOL rLockLayerHook::registerHook(){    return acedRegisterFilterWinMsg(rLockLayerHook::callback);} BOOL rLockLayerHook::removeHook(){    return acedRemoveFilterWinMsg(rLockLayerHook::callback);} BOOL rLockLayerHook::callback(MSG *msg ){    if ( msg->message != WM_KEYDOWN)//WM_KEYDOWN为需要监视消息    {       return FALSE;                   //这里FALSE,表示钩子不会再发送这个消息到cad那里去    }     return TRUE;                     //TRUE,钩子失效,发送监视消息到cad那里去}8. 2012年4月20日星期五file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image003.png
这个其实就是个comboBox,mfc ex的基类就是CacUiColorComboBox或者是CacUiTrueColorComboBox,区别在于后者在选其他颜色的时候,会出现一个真彩色的选项。弄好基类后,就要在comboBox上设置属性,其中style是droplist,owner draw是fixed,has string是true,之后就OK了9. 2012年4月27日星期五    使用 objectARX 函数 acedGetRGB(int color) 可以将cad的颜色索引(ACI)转换成对应的RGB颜色值。    也可以通过使用AcCmEntityColor来进行转换。10.2012年9月27日星期四在各vs版本中,为保持项目工程的一致性,可以将各版本的源文件放置与同一个文件夹里面,这样可以在vs编译的时候迅速优化各个工程,但是必须手动在各个vs版本中添加各个新建的项目,及新的资源h和脚本文件11.2012年11月23日星期五从网站上抄的,移除xdataThefollowing ObjectARX / Lisp code removes XData that is attached to an entityregardless of the APPNAME. Use it with caution and only if you need to do this,since removing XData from an entity without considering the appname may cause plug-insthat rely on them to misbehave.Hereis the ObjectARX code :staticvoid AdskTestCommand(void){   ads_name eNam;   ads_point pt;    intret;    ret =acedEntSel(ACRX_T("\nPick an entity :"), eNam, pt);    if(RTNORM != ret)       return;
   AcDbObjectId ObjId;   acdbGetObjectId(ObjId, eNam);
   AcDbEntity *pEnt = NULL;   Acad::ErrorStatus es          =acdbOpenAcDbEntity(pEnt, ObjId, AcDb::kForWrite);
   resbuf *xdata = pEnt->xData(NULL);    if(xdata)    {      xdata->rbnext= NULL;       pEnt->setXData(xdata);       acutRelRb(xdata);    }   pEnt->close();}Hereis the Lisp code:(defun c:DelXdata()
(setq l (car (entsel "Pick object:")))
(if l (progn
   (redraw l 3)
   (setq le (entget l '("*")) )
   (setq xdata (assoc '-3 le))
   (setq le
         (subst (cons (car xdata) (list (list (car (car (cdr xdata))))))
xdata le))
   (entmod le)
   (redraw l 4)
   le
    )
)
12.2012年11月23日星期五如何炸开文字Q:
How to explode SHX based AcDbText entities to itsconstituent lines?
A:
You can “explode” or tessellate the text entity using theAcGiTextEngine::tessellate() method. But the method returns raw pointinformation which can be used to create individual lines. The method does nothonor the text style table properties and the text height, width are assumed tobe of one unit.
To get the exact representation of an AcDbText entity, weneed to transform this raw point information depending upon the attributes ofthe text and its text style. In the attached sample, the functionfGeneratematrix() creates a transformation matrix which will place the linesdrawn using the points exactly at the text location. This transformation matrixwill take care of oblique angle, upside down, backward text properties.
NOTE: the true type fonts are not supported.

13.2012年12月19日星期三获取命令行的文字串// get the command line CWnd containerCWnd* pDock =(CWnd*)acedGetAcadTextCmdLine();   // get the child windowCWnd* pChild =pDock->GetWindow(GW_CHILD);   // loop while we have childrenwhile (pChild!=NULL){       CString str;       // get thewindow text pChild->GetWindowText(str);       // if we don'thave any text if(str.GetLength() <= 0)   pChild = pChild->GetNextWindow();    else {   // display the text   MessageBox(NULL, str);   break; }} 14.2012年12月27日星期四创建一个非矩形的视口AcDbViewport::setNonRecClipEntityId()can be used to associate aviewport with an entity and enabling non-rectangular clipping is done throughAcDbViewport::setNonRectClipOn()For a clipping entity to be valid, itmust be contained in the same paperspace as the viewport entity and should beone of the entities as explained with ObjectARX help file. Please see thedocumentation for AcDbViewport::setNonRecClipEntityId() method for moredetails.The sample code snippet below creates anon-rectangular viewport of type circle and assign a specified view to thesame:void fCreateNonRectangularViewPort(){ //____create thecircleads_point centerPoint; if(RTNORM !=acedGetPoint(NULL, L"\nEnterthe Viewport center point:",centerPoint)) return;ads_real radius; if(RTNORM !=acedGetDist(centerPoint, L"\nEnterthe radius of the Viewport:",&radius)) return;ACHAR viewName;    if (RTNORM != ads_getstring(0, L"\nEntername of view to use: ", viewName))        return; AcDbCircle* pCircle = new AcDbCircle; //____assigningthe center pointpCircle->setCenter(AcGePoint3d(centerPoint,centerPoint,0));pCircle->setRadius(radius);AcDbBlockTable *pTable;AcDbBlockTableRecord *pRecord; if (Acad::eOk !=acdbHostApplicationServices()-> workingDatabase()->getBlockTable(pTable, AcDb::kForRead)) { acutPrintf(L"\nCannot get block table."); delete pCircle; return;} if (Acad::eOk !=pTable->getAt(ACDB_PAPER_SPACE, pRecord,AcDb::kForWrite)) { acutPrintf(L"\nCannot access paper space."); pTable->close(); delete pCircle; return;}pTable->close(); AcDbObjectId circleId; if (Acad::eOk !=pRecord->appendAcDbEntity(circleId, pCircle)) { acutPrintf(L"\nCannot append circle to paper space."); pRecord->close(); delete pCircle; return;}pRecord->close();pCircle->close(); //____create theviewport entityAcDbViewport *pViewport = newAcDbViewport; //___assign thecenter point of circle //(since thevalue is known directly assigning)pViewport->setCenterPoint(AcGePoint3d(centerPoint, centerPoint,0));    //___Append newviewport to the paper space if (Acad::eOk !=acdbHostApplicationServices()-> workingDatabase()->getBlockTable(pTable, AcDb::kForRead)) { acutPrintf(L"\nCannot get block table."); deletepViewport; return;} if (Acad::eOk !=pTable->getAt(ACDB_PAPER_SPACE, pRecord,AcDb::kForWrite)) { acutPrintf(L"\nCannot access paper space."); pTable->close(); deletepViewport; return;}pTable->close(); AcDbObjectId viewportId; if (Acad::eOk !=pRecord->appendAcDbEntity(viewportId, pViewport)) { acutPrintf(L"\nCannot append viewport to paper space."); pRecord->close(); deletepViewport; return;} pRecord->close();pViewport->setNonRectClipEntityId(circleId);pViewport->setNonRectClipOn(); //___set theviewAcDbViewTable *pViewTable;AcDbViewTableRecord *pView; if (Acad::eOk !=acdbHostApplicationServices()-> workingDatabase()->getViewTable(pViewTable, AcDb::kForRead)) { acutPrintf(L"\nCannot get view table."); pViewport->close(); return;} if (Acad::eOk !=pViewTable->getAt(viewName, pView,AcDb::kForRead)) { acutPrintf(L"\nCannot access view '%s'.", viewName); pViewport->close(); pViewTable->close(); return;}pViewTable->close(); if(acedSetCurrentView(pView, pViewport)!=Acad::eOk) acutPrintf(L"\nFailed to set view"); pViewport->setOn();pView->close(); pViewport->close(); //update thescreen such the viewport is updatedacedCommand(RTSTR,L"_REGEN",RTNONE,0); } 15.2012年12月27日星期四创建有端点导航的OPM
The following snippets of code can be used to implement apolyline-like vertex edit in OPM (Object Property Manager) usingIOPMPropertyExpander interface for a custom entity.
Lets assume the custom object (called AsDkRings) has twonew variables to reflect an array of vertices:
AcGePoint3dm_polyline;intm_numbervertices;For simplicity, lets say the custom entity has a maximum offive vertices.
The custom class also has two corresponding accessfunctions. (Note the vertex number parameter):
Acad::ErrorStatusAsDkRings::polyline(AcGePoint3d& vertex,
intvertexNumber)Acad::ErrorStatusAsDkRings::setPolyline(AcGePoint3d vertex, intvertexNumber)In AsDkRings:: subWoldDraw(), we draw a polyline connectingthe five vertices.
The key to getting a spin control in OPM is to return agrouping number. This number will determine the number of elements to grouptogether.
//IOPMPropertyExpanderSTDMETHODIMPCRings::GetElementGrouping( /* */ DISPIDdispID, /* */ short *groupingNumber){.......................... } else if (dispID ==5){*groupingNumber= 4;return S_OK;} return E_NOTIMPL;}A grouping number of 4 means there is one entry (called"Vertex") plus 3 entries (Vertex X, Y and Z) to group together intoone property. What this would do is put a spin control into the first item(vertex in this case), so that this could be used to traverse an array of 3remaining grouped items (which is Vertex X, Y and Z).
Next, we should specify the number of items that the propertyis going to display. Here this would be the number of vertices= 5. Thiswould mean that the spin contol can go up to a maximum of 5 steps, for fivevertices.
//IOPMPropertyExpanderSTDMETHODIMPCRings::GetGroupCount( /* */ DISPIDdispID, /* */ long*nGroupCnt){...............................} else if (dispID ==5){*nGroupCnt =m_numbervertices; // Number of verticesreturn S_OK;} return E_NOTIMPL;}Next, we set the string to display in the OPM.
//IOPMPropertyExpanderSTDMETHODIMPCRings::GetElementStrings( /* */ DISPIDdispID, /* */OPMLPOLESTR __RPC_FAR *pCaStringsOut, /* */ OPMDWORD__RPC_FAR *pCaCookiesOut){................................ ................................} else if (dispID ==5){// For theVertices pCaStringsOut->cElems = 4; pCaStringsOut->pElems = (LPOLESTR*)CoTaskMemAlloc(sizeof(LPOLESTR)*4); pCaStringsOut->pElems = SysAllocString(L"Vertex"); pCaStringsOut->pElems = SysAllocString(L"Vertex X");pCaStringsOut->pElems= SysAllocString(L"Vertex Y"); pCaStringsOut->pElems = SysAllocString(L"Vertex Z"); pCaCookiesOut->cElems = 4; pCaCookiesOut->pElems = (DWORD*)CoTaskMemAlloc(sizeof(DWORD)*4);
pCaCookiesOut->pElems = 10; pCaCookiesOut->pElems = 1; pCaCookiesOut->pElems = 2; pCaCookiesOut->pElems = 3;return S_OK;} return E_NOTIMPL;}The cookies count is the unique identifier for eachproperty item that will be used to get and set values. The table below willgive you an idea of cookie value for each vertex value. Please remember thatvertex entry has a spin control that goes like 1, 2, 3, 4.........and so on.
Property StringCookieValue      
                Vertex = 1    Vertex = 2   Vertex = 3Vertex = 4....and so on      
Vertex             0            4         8          12      ....and so on      
Vertex X         1            5         9          13      ....and so on      
Vertex Y         2            6          10         14       ....and so on      
Vertex Z         3            7          11          15      ....and so onSo just by using the cookie values, you must get the vertexnumber and find out if it is an x, y, or z coordinate. The following code doesthat.
// Get the coordinateindex (x=0, y= 1, z=2) from dwCookiefor (int i = 1; i< 4; i++) {vertex = (double(dwCookie)- i) / 4; if( vertex ==(double(dwCookie) - i) / 4) {index = i -1;break;}}
indexwill return 0 for vertex X, 1 for vertex Y and 2 for vertex Z for thecorrosponding cookie values.So this is allthat is needed to implement a poly-line vertex edit. 16.2012年12月27日星期四为实体创建一个自定义的捕捉点
You can define a custom osnap like this:
//-------------------------------------------------------------------// Osnap classes
class MkrInfo : publicAcDbCustomOsnapInfo{public:   ACRX_DECLARE_MEMBERS(MkrInfo);
    Acad::ErrorStatusgetOsnapInfo(    AcDbEntity*          pickedObject,    Adesk::GsMarker       gsSelectionMark,    constAcGePoint3d&    pickPoint,    constAcGePoint3d&    lastPoint,    constAcGeMatrix3d&   viewXform,    AcArray<AcGePoint3d>& snapPoints,    AcDbIntArray &   geomIdsForPts,    AcArray<AcGeCurve3d*>& snapCurves,    AcDbIntArray &   geomIdsForLines);};


ACRX_CONS_DEFINE_MEMBERS(MkrInfo,AcDbCustomOsnapInfo, 1);

Acad::ErrorStatusMkrInfo::getOsnapInfo(AcDbEntity*          pickedObject,Adesk::GsMarker      gsSelectionMark, constAcGePoint3d&    pickPoint, constAcGePoint3d&    lastPoint, constAcGeMatrix3d&   viewXform,AcArray<AcGePoint3d>&snapPoints,AcDbIntArray&   geomIdsForPts,AcArray<AcGeCurve3d*>&snapCurves,AcDbIntArray&   geomIdsForLines){   AsDkCurve *p = AsDkCurve::cast(pickedObject);    if (NULL !=p)    {       AcGeLineSeg3d *pls1 = new AcGeLineSeg3d;AcGePoint3dstartP, endP; p->startPoint(startP); p->m_endPoint(endP);       pls1->set(startP, (endP - startP));       snapCurves.append(pls1);
       AcGeLineSeg3d *pls2 = new AcGeLineSeg3d;       pls2->set(endP, (endP - startP));       snapCurves.append(pls2);    }    return Acad::eOk;}

//static MkrInfo _mkrInfo;

class MkrMode : publicAcDbCustomOsnapMode{public:    const TCHAR*localModeString() const;    const TCHAR*globalModeString() const;    const AcRxClass*entityOsnapClass() const;   AcGiGlyph* glyph() const;    constTCHAR*            tooltipString() const;};
const TCHAR*MkrMode::localModeString()const{    return _T("XTNd");}
const TCHAR*MkrMode::globalModeString()const{    return _T("_XTNd");}
const AcRxClass*MkrMode::entityOsnapClass()const{    return MkrInfo::desc();}
class MyGlyph : public AcGiGlyph{public:   Acad::ErrorStatus setLocation(const AcGePoint3d& dcsPoint);    voidsubViewportDraw(AcGiViewportDraw* vportDrawContext);private:   AcGePoint3d m_point;};
Acad::ErrorStatusMyGlyph::setLocation(constAcGePoint3d& dcsPoint){   m_point = dcsPoint;    return Acad::eOk;}

voidMyGlyph::subViewportDraw(AcGiViewportDraw*p){    // notneeded for ext-like snaps}
AcGiGlyph*MkrMode::glyph() const{    static MyGlyphmg;    return &mg;}
const TCHAR*MkrMode::tooltipString()const{    return _T("CustomExtend");}
MkrMode _mkrMode;And you can register a custom osnap to associate with withan entity as follows:
//Register protocolextension object (customOsnapInfo)MkrInfo::rxInit();acrxBuildClassHierarchy();AcDbEntity::desc()->addX(MkrInfo::desc(),&_mkrInfo);acdbCustomOsnapManager()->addCustomOsnapMode(&_mkrMode);And unregister as follows:
//Remove protocolextension objectacdbCustomOsnapManager()->removeCustomOsnapMode(&_mkrMode);AcDbEntity::desc()->delX(MkrInfo::desc());deleteAcRxClass(MkrInfo::desc()); 17.2013年1月16日星期三Jig多个实体
Jig more than one entity with AcEdJig class in ObjectARXThe AcEdJig class only supports the update of one entityat a time. However, there is a useful trick you can use to jig more than oneentity in ObjectARX.
To do this, you need to define a custom entity class anduse it as a *temporary* jig entity which will be used with to maintaina list of real entities that need to be jigged. You will use this custom entitywith your custom AcEdJig class to update your list of Jigged entities.Following this, you simply need to override the *temporary* jig entity'ssubWorldDraw() and inside it,you can iterate through your recordedentities and call worldDraw() on each. This will give the impression ofmultiple entity update.
Please find herea sample application (available for download and migrated to use ARX 2013libraries) which has been written in such a way that it will Jig any number ofentities. You can also provide your own input and update functions for entitythat is required to be jigged. Here is the relevant source:
Header for Jig custom entity and and custom jigdeclaration:
#ifndefASDKMULTIJIG__H#defineASDKMULTIJIG__H
#endif //ASDKMULTIJIG__H
////////////////////////////////////////////////////////classasdkEntityList : public AcDbEntity{public:
//constructorasdkEntityList (); //destructor~asdkEntityList ();
// flag toindicate that we have // assignedsome entities to the entity list boolm_calledSetEntityList;
// array ofAcDbEntitiesAcDbVoidPtrArraym_entities;

typedefAdesk::Boolean (*updateFunction) (int mode,asdkEntityList&entityList); // array ofupdate FunctionsAcArray<updateFunction>m_updateFuncs;
// entityinput dataAcArray<AcGePoint3d>m_pnts;
// setswhat entities are to be jigged Acad::ErrorStatussetEntityList(constAcDbVoidPtrArray &entities);
// loopthrough m_entities and display the data virtualAdesk::Boolean subWorldDraw(AcGiWorldDraw *wd); };
/////////////////////////////////////////////////////////////classasdkMultiJig : public AcEdJig{public: //constructorasdkMultiJig (); //destructor~asdkMultiJig ();
// mode ofinput, starts at 0 then //increments until all entities have been // input,indexes m_entityList int m_mode;
// classcontaining the list of entities to jigasdkEntityListm_entityList;ads_real m_distance;AcGePoint3dm_originalPnt;
typedefAcEdJig::DragStatus (*inputFunction)(asdkMultiJig &jig); // array ofinput FunctionsAcArray<inputFunction>m_inputFuncs;
// get thejig data virtualAcEdJig::DragStatus sampler(); // returnthe jig entity virtual AcDbEntity*entity() const; // updatethe sampler data to the jig entity virtualAdesk::Boolean update(); // add theobject to the spaceAcDbObjectIdappend(AcDbDatabase *dwg=NULL, const ACHAR*requiredSpace = ACDB_MODEL_SPACE);
private:};
Implementation of the custom jig and jig custom entity:
#include "StdAfx.h"#include "StdArx.h"
///////////////////////////////////////////////////////Acad::ErrorStatuspostToDwg (AcDbEntity *pEnt, AcDbDatabase*pDb=NULL, const ACHAR*requiredSpace=ACDB_MODEL_SPACE);
///////////////////////////////////////////////////////// constructorasdkMultiJig::asdkMultiJig(){m_mode = 0;m_distance = 0.0;}///////////////////////////////////////////////////////// destructorasdkMultiJig::~asdkMultiJig(){}///////////////////////////////////////////////////////// get the jig dataAcEdJig::DragStatusasdkMultiJig::sampler(){ // set thecursor to invisiblesetSpecialCursorType(AcEdJig::kInvisible);
// call theupdate function return ((*this->m_inputFuncs)(*this));}///////////////////////////////////////////////////////// return the jigentityAcDbEntity*asdkMultiJig::entity() const{ // check tomake sure we called setEntityList if(!m_entityList.m_calledSetEntityList)AfxMessageBox(L"Error - You must call setEntityList \beforecalling asdkMultiJig::drag()");
return (const_cast<asdkEntityList*>(&m_entityList));}
///////////////////////////////////////////////////////// update the samplerdata to the jig entityAdesk::BooleanasdkMultiJig::update(){ // call theupdate function return ((*m_entityList.m_updateFuncs)(m_mode, this->m_entityList));}///////////////////////////////////////////////////////// add the object tothe spaceAcDbObjectIdasdkMultiJig::append(AcDbDatabase *dwg, const ACHAR*requiredSpace){ // get thetotal number of entities registered int length =m_entityList.m_entities.length (); // loopround the registered // entitiesand add the to the space for (int i=0;i<length; ++i){postToDwg((AcDbEntity *)m_entityList.m_entities,   dwg,requiredSpace);}
// get theid of the first entity drawnAcDbObjectId id = ((AcDbEntity*)m_entityList.m_entities)->id ();
// closethe entities added for (int i=0;i<length; ++i)((AcDbEntity*)m_entityList.m_entities)->close (); // returnthe object id return (id);}///////////////////////////////////////////////////////// constructorasdkEntityList::asdkEntityList(){m_calledSetEntityList= false;}///////////////////////////////////////////////////////// destructorasdkEntityList::~asdkEntityList(){}///////////////////////////////////////////////////////// set what entitiesare to be jiggedAcad::ErrorStatusasdkEntityList::setEntityList ( constAcDbVoidPtrArray &entities){ // recordthat we have called this function correctlym_calledSetEntityList= true; // setupour entity listm_entities = entities; // ifnothing in there then lets say! if(m_entities.length() == 0)returnAcad::eInvalidInput; // setupour point storagem_pnts.setLogicalLength(m_entities.length()); // all ok return Acad::eOk;}///////////////////////////////////////////////////////Adesk::BooleanasdkEntityList::subWorldDraw (AcGiWorldDraw * wd){ int length =m_entities.length (); // loop thenumber of entities in the entity list for (int i=0;i<length; ++i){// lets getthe entity outAcDbEntity *ent= (AcDbEntity *)m_entities;// if okif (ent !=NULL){   // thendraw itwd->geometry ().draw (ent);}} return(Adesk::kTrue);}///////////////////////////////////////////////////////// adds an entity tothe required space... // default is currentdwg and ACDB_MODEL_SPACE.Acad::ErrorStatuspostToDwg (AcDbEntity *pEnt, AcDbDatabase *pDb, const ACHAR*requiredSpace){ // if thedefault database is to be used if (pDb ==NULL)pDb =acdbHostApplicationServices()->workingDatabase ();AcDbBlockTable*blockTable = NULL; // get apointer to the block table Acad::ErrorStatus es =pDb->getBlockTable (blockTable, AcDb::kForRead); // if itfailed then abort if (es !=Acad::eOk)return (es);AcDbBlockTableRecord*blockTableRecord = NULL; // now geta pointer to the model space entity recordses =blockTable->getAt (requiredSpace, blockTableRecord, AcDb::kForWrite); // canclose the block table // itselfas we don't need it anymoreblockTable->close(); // if itfailed then abort if (es !=Acad::eOk)return (es); //otherwise put the entity into the model spacees =blockTableRecord->appendAcDbEntity (pEnt); // nowclose it upblockTableRecord->close(); return (es);} 18.2013年1月31日星期四用ObjectARX取得当cad 截屏Capturinga Screen Shot using ObjectARXBy Fenton Webb
Issue


[*]How to capture the screen shot of   a Viewport to save it to an image file?
[*]Is it possible to temporarily   display an image over the Viewport or alter the Viewport display?
Solution
There are two AcGs functions in ObjectARX SDK that can beused to get the screen shot of the specified Viewport in AutoCAD and also toset an image to be displayed in a specified Viewport.
The functions are:


[*]acgsGetScreenShot()
[*]acgsDisplayImage()
The function acgsGetScreenShot() will get you the pointerto an AcGsScreenShot object for the specified viewport number. When in TILEMODE1, just specify the Viewport that you are interested in or just specify “0” toget the entire ModelSpace (and same is the case with PaperSpace). Similarlyjust specify the Viewport number you are interested in if the TILEMODE is 0(i.e. in PaperSpace). Also if you specify viewport number of 1 in TILEMODE 0then you will get only the entities that exist in the PaperSpace.
You can get the image data using the method getScanline()of the AcGsScreenShot class. Using this you can fill in an array of truecolorlong words which essentially will be the ARBG data. This data can be used tocreate an image file. You can use the ATIL SDK to create a bitmap or JPG easilyfrom the image data.
The function acgsDisplayImage() on the other hand takes anarray of truecolor long words and displays the image at the specified locationon a specified Viewport. The image will stay till a redraw is called.
This function can be used to:


[*]Show a temporary external image   quickly.
[*]Blank the entire view port   temporarily.
[*]Change the colors of the Viewport.   Say inverting the colors of the Viewport.
Note:The acgsGetScreenShot() does not get you the shaded orrendered view.
Here’s some code which shows how to use the functions, touse the Atil libs you need to link with the headers and libs in the Atil folderof the ObjectARX SDK:
// by Fenton Webb,DevTech, 1/30/2013// screen shoots theview details as a BMPbool RecordViewDetails(double &fieldWidth, double &fieldHeight, AcGePoint3d &position, AcGePoint3d &target, AcGeVector3d &upVector, const TCHAR *imagePath){int iVP = getCVPort();
// Computethe viewport dimensions.int nLeft, nBottom, nRight, nTop;int iImageWidth, iImageHeight;acgsGetViewportInfo (iVP, nLeft, nBottom, nRight, nTop);
iImageWidth= nRight - nLeft + 1;iImageHeight = nTop - nBottom + 1;Atil::Size size(iImageWidth, iImageHeight);int nBytesPerRow = Atil::DataModel::bytesPerRow(iImageWidth,                                Atil::DataModelAttributes::k32);unsigned long nBufferSize = iImageHeight * nBytesPerRow;
// Createan ATIL image for accepting the rendered image.std::auto_ptr<char> autoBuff = std::auto_ptr<char>(new char);char *pSnapshotData = autoBuff.get();Atil::Image * pImage = NULL;// see ifthere is a GS view createdAcGsView *pView = acgsGetGsView(iVP, false); // if not if (NULL == pView){    // then wemust be in 2D wireframe mode, so use acgsGetScreenShot    std::auto_ptr<AcGsScreenShot> autoScreenShot(acgsGetScreenShot(iVP));    AcGsScreenShot* screenShot = autoScreenShot.get(); // auto_ptrstill owns the pointer.    if (screenShot)    {   int w = 0, h = 0, d = 0;   screenShot->getSize(w, h, d);
   char* pBufTemp = pSnapshotData;   for (int row = 0; row < h; row++)   {       memcpy(pBufTemp, screenShot->getScanline(0, row), nBytesPerRow);
       //convert from RGBA to BGRA       char* pColor = pBufTemp;       for (int i = 0; i < w; i++)   //Slow but it works       {         char temp = *pColor;         *pColor = *(pColor + 2);         *(pColor + 2) = temp;         pColor += 4;       }       pBufTemp += nBytesPerRow;   }   pImage = constructAtilImg(reinterpret_cast<char*>(pSnapshotData),                    nBufferSize, nBytesPerRow, w, h, 32, 0);   std::auto_ptr<Atil::Image> autodeleter = std::auto_ptr<Atil::Image>(pImage); // auto_ptrnow owns the image
   if (!writeImageFile(pImage, kBMP, imagePath))   {       acutPrintf(_T("\nFailedto write image file %s"), imagePath);       return false;   }   else       acutPrintf(_T("\nSuccessfullywritten %s"), imagePath);
    }
    return true;}else{    return snapGSView(pView, iImageWidth, iImageHeight, fieldHeight, fieldWidth, position, target, upVector, imagePath);}}
bool snapGSView(AcGsView *pView, int width, int height, double &fieldWidth, double &fieldHeight, AcGePoint3d &position, AcGePoint3d &target, AcGeVector3d &upVector, const TCHAR *imagePath){Atil::Size size(width, height);int nBytesPerRow = Atil::DataModel::bytesPerRow(width, Atil::DataModelAttributes::k32);unsigned long nBufferSize = height * nBytesPerRow;
// Createan ATIL image for accepting the rendered image.std::auto_ptr<char> apCharBuffer = std::auto_ptr<char>(new char);char *pSnapshotData = apCharBuffer.get();// auto_ptr still owns the buffer.
// inshaded mode (from GS)Atil::Image * pImage = NULL;pImage = constructAtilImg(pSnapshotData, nBufferSize, nBytesPerRow, width, height, 32, 0);std::auto_ptr<Atil::Image> autodeleter = std::auto_ptr<Atil::Image>(pImage); // auto_ptrnow owns the imagepView->getSnapShot(pImage, AcGsDCPoint(0, 0));
// add atemp image to invert the image. do we have a better way to turn an imagearound?Atil::Image imgTempForInverted(pImage->read(pImage->size(), Atil::Offset(0, 0), Atil::kBottomUpLeftRight));*pImage = imgTempForInverted;
if (!writeImageFile(pImage, kBMP, imagePath)){    acutPrintf(_T("\nFailedto write image file %s"), imagePath);    return false;}else    acutPrintf(_T("\nSuccessfullywritten %s"), imagePath);
// recordthe view datafieldHeight = pView->fieldHeight();fieldWidth = pView->fieldWidth();position = pView->position();target = pView->target();upVector = pView->upVector();
return true;}
bool writeImageFile (Atil::Image *pImageSource, eFormatType formatType, wchar_t const *pFileName){_ASSERT(NULL != pImageSource);if(NULL == pImageSource)    return false;
_ASSERT(pImageSource->isValid());if(!pImageSource->isValid())    return false;
if(PathFileExists(pFileName))    DeleteFile(pFileName);
/*if(PathFileExists(pFileName)){ if(IsFileReadOnly(pFileName)) { RemoveReadonlyAttribute(pFileName); DeleteFile(pFileName);}}*/
if(PathFileExists(pFileName))    return false;
Atil::RowProviderInterface* pPipe = pImageSource->read(pImageSource->size(),     Atil::Offset(0,0));_ASSERTE(NULL != pPipe);if(!pPipe)    return false;
Atil::FileWriteDescriptor*pFWD = NULL;Atil::ImageFormatCodec    *pCodec = NULL;
if (formatType == kJPG)    pCodec = new JfifFormatCodec();else if (formatType == kPNG)    pCodec = new PngFormatCodec();else if (formatType == kTIF)    pCodec = new TiffFormatCodec();else if (formatType == kBMP)    pCodec = new BmpFormatCodec();
_ASSERTE(NULL != pCodec);if(NULL == pCodec)    return false;
if(!Atil::FileWriteDescriptor::isCompatibleFormatCodec(pCodec,    &(pPipe->dataModel()), pPipe->size())) {   delete pCodec;   return false;}
pFWD = new Atil::FileWriteDescriptor(pCodec);_ASSERTE(NULL != pFWD);
#ifdef UNICODE#ifndef _ADESK_MAC_Atil::FileSpecifier fs(Atil::StringBuffer((lstrlen(pFileName) + 1) * sizeof(TCHAR),     (const Atil::Byte *) pFileName, Atil::StringBuffer::kUTF_16),     Atil::FileSpecifier::kFilePath);#elseAtil::FileSpecifier fs(Atil::StringBuffer((lstrlen(pFileName) + 1) * sizeof(TCHAR),     (const Atil::Byte *) pFileName, Atil::StringBuffer::kUTF_32),     Atil::FileSpecifier::kFilePath);
#endif#elseAtil::FileSpecifier fs(Atil::StringBuffer(lstrlen(pFileName) + 1,     (const Atil::Byte *) pFileName, Atil::StringBuffer::kASCII),     Atil::FileSpecifier::kFilePath);#endif
if (!pFWD->setFileSpecifier(fs))    return false;
pFWD->createImageFrame(pPipe->dataModel(), pPipe->size());
if (formatType == kPNG) {    Atil::FormatCodecPropertyInterface* pProp = pFWD->getProperty(Atil::FormatCodecPropertyInterface::kCompression);    if (pProp != NULL) {   PngCompression* pPngComp = (PngCompression*)(pProp);   if ( pPngComp != NULL ) {       //Why not compress all we can?        pPngComp->selectCompression(PngCompressionType::kHigh);       pFWD->setProperty(pPngComp);   }   delete pProp;    pProp = NULL;    }}else if (formatType == kTIF) {    Atil::FormatCodecPropertyInterface* pProp = pFWD->getProperty(Atil::FormatCodecPropertyInterface::kCompression);    if (pProp != NULL) {   TiffCompression* pComp = (TiffCompression*)(pProp);   if ( pComp != NULL ) {       //G4 is only valid for 1 bit images.        if ( pComp->selectCompression(TiffCompressionType::kCCITT_FAX4) == false ) {         //So if that fails, resort to LZW now that it is patent free         if ( pComp->selectCompression(TiffCompressionType::kLZW) == false ) {         //If that fails (and is shouldn't, be) then set none.         pComp->selectCompression(TiffCompressionType::kNone);         }       }       pFWD->setProperty(pComp);   }   delete pProp;    pProp = NULL;    }}// get the current vpint getCVPort(){struct resbuf rb;ads_getvar(_T("CVPORT"), &rb);return rb.resval.rint;}
Atil::DataModel* colorSpace (char *&pRGBData, int colorDepth, int paletteSize){_ASSERT(NULL != pRGBData);
// Setup acolor space, with palette if neededAtil::DataModel *pDm = NULL; if (colorDepth == 8) {    Atil::RgbColor space;
    Atil::RgbPaletteModel *pPM = new Atil::RgbPaletteModel();    _ASSERT(NULL != pPM);    if(!pPM)   return NULL;
    pDm = pPM;    char *palette = pRGBData;    pRGBData += paletteSize;    for (int i = 0; i < paletteSize; i += 4)   space = Atil::RgbColor(palette,palette,palette, 255);    pPM->setEntries(0, 256, (Atil::RgbColor *)&space);} else     pDm = new Atil::RgbModel(32);

_ASSERT(NULL != pDm);return pDm;}
Atil::Image *constructAtilImg(char *pRGBData, unsigned long bufferSize, unsigned long rowBytes, unsigned long xSize, unsigned long ySize, int colorDepth, int paletteSize){if ((8 != colorDepth)&& (32 != colorDepth)) {    return NULL;}
if (paletteSize) {    if ((paletteSize < 0) ||(paletteSize > 255))     {   return NULL;    }}
if ((xSize <= 0)|| (ySize <= 0)) {    return NULL;}
Atil::Image *pImg = NULL;Atil::Size size(xSize, ySize);
//construct the Atil::Image objectif (pRGBData) {
    // Checkthe buffer for size and definition    if (bufferSize) {   if (!rowBytes) {       return NULL;   }
   //did they allocate enough?   if (rowBytes * ySize > bufferSize) {       return NULL;   }    }    else {   return NULL;   }      
    Atil::DataModel *pM = colorSpace(pRGBData, colorDepth, paletteSize);    _ASSERT(NULL != pM);    if(NULL == pM)   return NULL;
    try {   //BEWARE: pRGBData may be moved in colorSpace   pImg = new Atil::Image(pRGBData, bufferSize,       rowBytes, size, pM);    } catch (Atil::ATILException* pExpCon) {   //image construction failure   delete pExpCon;   delete pM;   pImg = NULL;   _ASSERT(FALSE);   return NULL;    }
    delete pM;}else {    Atil::RgbModel    rgbM(32);    Atil::RgbGrayModel gM;
    Atil::ImagePixel initialColor(colorDepth == 32 ?    Atil::DataModelAttributes::kRgba :     Atil::DataModelAttributes::kGray);    initialColor.setToZero();
    try {   pImg = new Atil::Image(size,       colorDepth == 32 ?&rgbM : &gM,       initialColor);    } catch (Atil::ATILException* pExpCon) {   //image construction failure   delete pExpCon;   pImg = NULL;   _ASSERT(FALSE);   return NULL;    }}
_ASSERT(NULL != pImg);return pImg;}
bool getTempImgFile(TCHAR * fileName){// Here wecreate a temp bmp file as a transitional fileTCHAR tempDic;::memset(tempDic,0,MAX_PATH);DWORD nRetSize = ::GetTempPath(MAX_PATH,tempDic);if (nRetSize > MAX_PATH || nRetSize == 0){    const TCHAR * tempStr = _T("C:\\temp");    if (wcscpy_s(tempDic,tempStr) != 0)    {   return false;    }
    if (::PathFileExists(tempStr) == FALSE &&   ::CreateDirectory(tempStr,NULL) == FALSE)    {   return false;    }}
// createthe temp file whose prefix is "img"if (::GetTempFileName(tempDic,_T("tmp"),0,fileName) == 0){    return false;}
// now splitthe filepath into its individual componentsTCHAR drive, dir, fname, ext; _tsplitpath (fileName, drive, dir, fname, ext);_stprintf(fileName, _T("%s%s%s.bmp"), drive, dir, fname);
return true;} 19.2013年6月8日星期六在文件搜索路径添加指定的目录void CRInit::addSupportPath(const CString&szNewPath ){   const ACHAR* libPath= acdbHostApplicationServices()->getEnv( L"ACAD" );    CString sysPath;    sysPath.Format( libPath);    if ( sysPath.Find( szNewPath ) != -1 )   {         acutPrintf(L"已经存在指定路径!\n");         return;   }   sysPath+= L";";   sysPath+= szNewPath;    int retcode;   retcode= acedSetEnv( L"ACAD", sysPath);    return;}20.2013年9月3日星期二AcAdDoubleClickEdit 在 arx2010 中 变到哪里了?
Solution
The AcDbDoubleClickEdit functionality that was exposed in AcDblClkEditPE.arx isnow rolled up in AcApp.arx and acad.lib.
For AutoCAD 2010 to make double click extension protocol to work.…
1. Remove any loadModule() calls to AcDblClkEditPE.arx
2. Remove any calls to AcDbDoubleClickEdit::rxinit() as this is now doneautomatically for us.
3. Include the AcDblClkEdit.h
4. Add ACRX_DEFINE_MEMBERS(AcDbDoubleClickEdit); in one of your .cpp modules For AutoCAD2007~2009file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg 21.2014年1月23日星期四以下代码获取lsp中的变量值:static void RALFHCDev_pp(void)    {      // Add yourcode for command RALFHCDev._pp here      int i = 0;      CString str;      int retcode = RTNORM;      struct resbuf *result = NULL;      retcode= acedGetSym( _T("kkk"), &result);      if ( retcode != RTNORM)      {            acutRelRb(result);            str.Format( _T("Error [%d]"),retcode);            AfxMessageBox(str );            return;      }      i = result->resval.rint;      acutRelRb(result);      str.Format( _T("kkk所ù属?的?值μ为a[%d]"),i);      AfxMessageBox( str);    } 22.2016年1月21日 10:05:21以下代码禁止选择AcDbLine//AcEdSSGetFilterEx.h#pragma onceclass AcEdSSGetFilterEx:public AcEdSSGetFilter
{
virtual void ssgetAddFilter(
int ssgetFlags,
AcEdSelectionSetService &service,
const AcDbObjectIdArray& selectionSet,
const AcDbObjectIdArray& subSelectionSet);
virtual void
endSSGet(
Acad::PromptStatus returnStatus,
int ssgetFlags,
AcEdSelectionSetService &service,
const AcDbObjectIdArray&selectionSet);
virtual void
endEntsel(
Acad::PromptStatus      returnStatus,
const AcDbObjectId&   pickedEntity,
const AcGePoint3d&      pickedPoint,
AcEdSelectionSetService& service);
//是否可以选中
virtual bool CanBeSelect(AcDbObjectId id);
//是否可以选中
virtual bool CanBeSelect(AcDbEntity* pEnt);
};//AcEdSSGetFilterEx.cpp#include"StdAfx.h"
#include "AcEdSSGetFilterEx.h"
void
AcEdSSGetFilterEx::ssgetAddFilter(
int ssgetFlags,
AcEdSelectionSetService &service,
const AcDbObjectIdArray& selectionSet,
const AcDbObjectIdArray& subSelectionSet)
{
acutPrintf(_T("\r\nAcEdSSGetFilterEx::ssgetAddFilter\r\n"));
for(int i=subSelectionSet.logicalLength()-1;i>=0;i--)
{
if(CanBeSelect(subSelectionSet.at(i)))
{
continue;
}
service.remove(i);
}
}
void AcEdSSGetFilterEx::endSSGet(
Acad::PromptStatus returnStatus,
int ssgetFlags,
AcEdSelectionSetService &service,
const AcDbObjectIdArray& selectionSet)
{
acutPrintf(_T("\r\nAcEdSSGetFilterEx::endSSGet\r\n"));
for(int i=selectionSet.logicalLength()-1;i>=0;i--)
{
if(CanBeSelect(selectionSet.at(i)))
{
continue;
}
service.remove(i);
}
}
void AcEdSSGetFilterEx::endEntsel(
Acad::PromptStatus       returnStatus,
const AcDbObjectId&      pickedEntity,
const AcGePoint3d&       pickedPoint,
AcEdSelectionSetService& service)
{
acutPrintf(_T("\r\nAcEdSSGetFilterEx::endEntsel\r\n"));
if(CanBeSelect(pickedEntity))
{
return;
}
service.remove(0);
}
//是否可以选中
bool AcEdSSGetFilterEx::CanBeSelect(AcDbObjectId id)
{
AcDbEntity* pEnt=NULL;
Acad::ErrorStatus es=acdbOpenAcDbEntity(pEnt,id,AcDb::kForRead);
if(es!=Acad::eOk)
{
return false;
}
bool rc=CanBeSelect(pEnt);
pEnt->close();
return rc;
}
//是否可以选中
bool AcEdSSGetFilterEx::CanBeSelect(AcDbEntity* pEnt)
{
return true;
}//MySSGetFilter.h#pragma once
#include "AcEdSSGetFilterEx.h"
class MySSGetFilter: public AcEdSSGetFilterEx
{
public:
//是否可以选中
virtual bool CanBeSelect(AcDbEntity* pEnt);
};//MySSGetFilter.cpp#include"StdAfx.h"
#include "MySSGetFilter.h"
//是否可以选中
bool MySSGetFilter::CanBeSelect(AcDbEntity* pEnt)
{
if(pEnt->isKindOf(AcDbLine::desc()))
{
return false;
}
else
{
return true;
}
}//acrxEntryPoint.cpp#include"StdAfx.h"
#include "resource.h"
#include "MySSGetFilter.h"
MySSGetFilter m_filter;
//-----------------------------------------------------------------------------
#define szRDS _RXST("")//-----------------------------------------------------------------------------
//----- ObjectARX EntryPoint
class CSSGetFilterTestApp : public AcRxArxApp {public:
CSSGetFilterTestApp () : AcRxArxApp () {}virtualAcRx::AppRetCode On_kInitAppMsg (void *pkt) {
// TODO: Load dependencies here// You *must* callOn_kInitAppMsg here
AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;addSSgetFilterInputContextReactor(curDoc(),&m_filter);// TODO: Add yourinitialization code herereturn (retCode) ;
}virtualAcRx::AppRetCode On_kUnloadAppMsg (void *pkt) {
// TODO: Add your code here// You *must* callOn_kUnloadAppMsg here
AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;removeSSgetFilterInputContextReactor(curDoc(),&m_filter);// TODO: Unloaddependencies herereturn (retCode) ;
}virtual voidRegisterServerComponents () {
}} ;//-----------------------------------------------------------------------------
IMPLEMENT_ARX_ENTRYPOINT(CSSGetFilterTestApp)
23.

cable2004 发表于 2020-9-20 19:29:40

999999 发表于 2020-9-22 09:15:03

顶起,分享不易,谢谢楼主分享,支持一下,让大家都能看到

edata 发表于 2020-9-22 17:12:04

十多年前的笔记,分享不易!

xvbzcmn 发表于 2020-11-16 09:24:19

多谢楼主分享

tieque 发表于 2020-11-27 13:33:47


多谢楼主分享

p-3-ianlcc 发表于 2021-10-29 16:36:09

虽然我不懂,但是…好厉害!
值得收藏的文章!
页: [1]
查看完整版本: Arx学习心得