Creation of a Simple Enity with an Anonymus Block as representation

Creation of a Simple Enity with an Anonymus Block as representation

Anonymous
Not applicable
2,671 Views
20 Replies
Message 1 of 21

Creation of a Simple Enity with an Anonymus Block as representation

Anonymous
Not applicable

Hi, 

I'd like to create a simple Arx project containing a simple entity that has a graphical representation. Thanks to @tbrammer  I know that I should use the AcDbBlockReferenc function but I'm not able to found any example on internet that could explain me how to achieve this. I should create a command that when is invoked from AutoCAD, it shows me a custom block, e.g. a car or a weel.

 

Thx,

Marco

0 Likes
Accepted solutions (1)
2,672 Views
20 Replies
Replies (20)
Message 2 of 21

tbrammer
Advisor
Advisor

An AcDbBlockReference represents a block references (BREF) to a block, that contains entities.

These are the basic steps to create an unnamed block and a BREF to it:

  1. Create an AcDbBlockTableRecord:  AcDbBlockTableRecord *btr = new AcDbBlockTableRecord;
  2. Name it "*U" to make it unnamed: btr->setName(L"*U");
  3. Populate btr with entities that represent you car (or whatever): btr->appendAcDbEntity(newEntity)
    Don't forget to newEntity->close() after you appended it the btr.
  4. Add btr to the AcDbBlockTable of your AcDbDatabase and get its ID:
    db->getBlockTable(bt, kforWrite); bt->add(ID, btr); btr->close(); bt->close();
  5. Create an AcDbBlockReference and to your btr and add it to the modelspace:
    AcDbBlockReference *bref = new AcDbBlockReference; bref->setBlockTableRecord(ID);
    postToDb(bref);

See also these ARX samples:

  1. ArxDbgUtils::defineNewAnonymousBlock() in <ARX>\samples\database\ARXDBG
  2. void addBlockWithAttributes() in <ARX>\samples\database\complex_dg\complex.cpp

Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 3 of 21

Anonymous
Not applicable

At the beginning I've to create an Entity that has a graphical representation of my car ? 

0 Likes
Message 4 of 21

tbrammer
Advisor
Advisor

@Anonymous wrote:

At the beginning I've to create an Entity that has a graphical representation of my car ? 


You wrote that you want to represent your object (let's say a car) using a block reference (AcDbBlockReference, BREF).

A BREF points to a block (AcDbBlockTableRecord, BTR) that holds AutoCAD entities that represent your car.

If you want to create the representation of a car programmatically, you have to create 1..N entities that represent the car and put them into the BTR.

 

You could also draw the car, engine, tire... blocks yourself and just load them into AutoCAD. But this is easier with named blocks. I have attached a very simple (and ugly) drawing of a car (car.dwg) that shows how you could assemble a car with blocks and a second one with four cars (cars.dwg).

 

car.dwg
   Blocks: 
      cabin : 2 3DSOLIDs 
      engine: 1 3DSOLID 
      wheel:  2 3DSOLIDs 
   BREFs: 
      1 x cabin
      1 x engine
      4 x wheel
   
cars.dwg
   Blocks: 
      cabin : 2 3DSOLIDs 
      engine: 1 3DSOLID 
      wheel:  2 3DSOLIDs 
      car:   1 BREF to cabin, 1 BREF to engine, 4 BREFs to wheel
   BREFs: 
      4 x car

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 5 of 21

Anonymous
Not applicable

Thank you for your examples @tbrammer ,

unfortunately I have a lot of confusion in my head, my goal is to have a block for each element of the machine. then I would like a simple CREATE_CAR command that automatically draws a machine on the AutoCAD draw area, then a command CREAT_WHEEL that draw a wheel, even if these two are not connected for the moment. I can't find a basic example of an ARX project to create such a thing. I hope you can recommend something to follow to get started.

0 Likes
Message 6 of 21

tbrammer
Advisor
Advisor

@Anonymous wrote:

I would like a simple CREATE_CAR command that automatically draws a machine on the AutoCAD draw area (..)


What do you mean by "automatically draw a machine"?

  1. Insertion of a block reference to a predefined block that contains a machine?   or
  2. Shall the machine itself be drawn programmatically?
    I.e. using same parameters like number of cylinders, power etc.

So for case 1: Lets say you have predifined blocks of

  • some car bodies:  body_ferrari, body_porsche
  • some engines: engine100kw, engine150kw, engine200kw
  • some wheels: wheel205, wheel195

Do you want a function like:  buildCar("body_ferrari", "engine150kw", "wheel205"); ?

 

For case 2 you would need function that create your blocks.

CreateBodyBlock("body_ferrari", "ferrari");

CreateEngineBlock("engine150kw", 150);

CreateWheelBlock("wheel205", 205);

 

What do you think about the DWGs I attached? Is this what you want to create in principle?


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 7 of 21

Anonymous
Not applicable

Yes @tbrammer , I'd like to create something like 2nd situation. I want to have some different graphical representation for eacg block. eg. a car, a wheel, an engine and so on.... I'd like to have a command like DRAW_CAR and the in the Autocad's draw should appear a car, like in 1st photos I attached. After I'd like to have a command like DRAW_ENGINE and the engine should appear on the draw. 2nd attached photos and so on .....  . This commands should use a defined block like elements in your car.dwg. Each element should be create in a different moment and for the moment can not be related. If i call another time the DRAW_WHEEL , another wheel should be displayed

wheel.pngengine.pngcar.png

Thx,

Marco  

0 Likes
Message 8 of 21

tbrammer
Advisor
Advisor
Accepted solution

Here is some sample code. You can register the function  cmdDrawCar()  as command DRAW_CAR.

It uses a simple class BlockDrawCar that "draws" a car into an AcDbBlockTableRecord.

You can modify the code in cmdDrawCar() to either create unnamed blocks or a named block that will be reused.

Please familiarize yourself with the concept of named an unnamed blocks.

 

class BlockDrawCar
{
public:
	void DrawBlock(AcDbBlockTableRecord *btr)
	{
		Acad::ErrorStatus es;

		// Draw a rectangle polyline
		AcDbPolyline *pline = new AcDbPolyline();
		AcGePoint2d p0(0.0, 0.0), p1(m_Length, 0.0);
		AcGePoint2d p2(m_Length, m_Width), p3(0.0, m_Width);
		pline->addVertexAt(0, p0);
		pline->addVertexAt(1, p1);
		pline->addVertexAt(2, p2);
		pline->addVertexAt(3, p3);
		pline->setClosed(true);
		pline->setDatabaseDefaults();
		pline->setColorIndex(m_Color);
		es = btr->appendAcDbEntity(pline);
		if (!es)
			pline->close();
		else
			delete pline;

		// Draw a circle
		AcDbCircle *circle = new AcDbCircle();
		AcGePoint3d center(m_Width / 2, m_Width / 2, 0.0);
		double radius = m_Width * 0.4;
		circle->setCenter(center);
		circle->setRadius(radius);
		circle->setDatabaseDefaults();
		circle->setColorIndex(m_Color);
		es = btr->appendAcDbEntity(circle);
		if (!es)
			circle->close();
		else
			delete circle;
	}

public:
	int m_Color=7;
	double m_Width=200;
	double m_Length=340;
};


AcDbObjectId CreateCarBlock(AcDbDatabase *db, LPCWSTR blockname)
{
	Acad::ErrorStatus es;
	AcDbBlockTableRecord *btr = new AcDbBlockTableRecord;
	BlockDrawCar *car = new BlockDrawCar;
	car->DrawBlock(btr);
	btr->setName(blockname);

	// Add the BTR to the Blocktable of the current drawing
	AcDbObjectId BtrID;
	AcDbBlockTable *blocktable;
	es = db->getBlockTable(blocktable, AcDb::kForWrite);
	if (!es)
	{
		es = blocktable->add(BtrID, btr);
		if (!es)
			btr->close();
		else
			delete btr;
		blocktable->close();
	}

	return BtrID;
}

AcDbObjectId FindBlock(AcDbDatabase *db, LPCWSTR blockname)
{
	AcDbObjectId BlockID;
	Acad::ErrorStatus es;
	AcDbBlockTable *blocktable;
	es = db->getBlockTable(blocktable, AcDb::kForRead);
	if (!es)
	{
		es = blocktable->getAt(blockname, BlockID);
		blocktable->close();
	}
	return BlockID;
}

Acad::ErrorStatus PostToDb(AcDbDatabase *db, AcDbObjectId& objId, AcDbEntity* pEnt)
{
	Acad::ErrorStatus      es;
	AcDbBlockTable*        blocktable = 0;
	AcDbBlockTableRecord*  modelspace = 0;

	if ((es = db->getBlockTable(blocktable, AcDb::kForRead)) != Acad::eOk)
		return es;

	if ((es = blocktable->getAt(ACDB_MODEL_SPACE, modelspace, AcDb::kForWrite)) == Acad::eOk)
	{
		es = modelspace->appendAcDbEntity(objId, pEnt);
		modelspace->close();
	}
	blocktable->close();

	return es;
}

void cmdDrawCar()
{
	Acad::ErrorStatus es;
	LPCWSTR blockname = L"*U"; // Blockname. This is for an unnamed block.
	blockname = L"car"; // And this for a named block

	AcDbDatabase *db = acdbHostApplicationServices()->workingDatabase();
	AcDbObjectId BtrID;
	if (blockname[0]!=L'*') // '*' as first char means: Unnamed block. Don't search for it.
		BtrID = FindBlock(db, blockname); // Search for an existing named block 

	if (BtrID.isNull())
		BtrID = CreateCarBlock(db, blockname); // For "*U" AutoCAD will create unique names like "*U1", "*U2", ...

	if (BtrID.isNull())
	{
		acutPrintf(L"\nFailed.");
		return;
	}

	// Now create a block reference
	AcDbBlockReference *bref = new AcDbBlockReference;
	bref->setDatabaseDefaults();
	bref->setBlockTableRecord(BtrID);

	AcGeVector3d insertionPoint;
	acedGetPoint(nullptr, L"\nInsertion point: ", asDblArray(insertionPoint));
	AcGeMatrix3d trans; // the transformation matrix
	trans.setToTranslation(insertionPoint);
	bref->setBlockTransform(trans);

	// Append it to modelspace
	AcDbObjectId BrefID;
	es = PostToDb(db, BrefID, bref);
	if (!es)
		bref->close();
	else
		delete bref;
}

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 9 of 21

tbrammer
Advisor
Advisor

There is a memory leak in  CreateCarBlock(AcDbDatabase *db, LPCWSTR blockname).

Either change the code to

	BlockDrawCar car;
	car.DrawBlock(btr);

or

	BlockDrawCar *car = new BlockDrawCar;
	car->DrawBlock(btr);
	delete car;

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 10 of 21

Anonymous
Not applicable

Hi @tbrammer ,

I read your example, should be very useful, thank you very very much. But I'm not understanding how to run it. I've generated an ARX project by the Objectarx wizard and added all your code in a file named BlockDrawCar.cpp. I've added dependencies to ObjectArx inc/inx64 and lib folders. But I got dependencies erros. I'm attaching a photo of my editor.

dep-errprs.png

0 Likes
Message 11 of 21

tbrammer
Advisor
Advisor

(..) I got dependencies erros. I'm attaching a photo of my editor.

Add #include "stdafx.h" as first line in the source file.

This is the "precompiled header file" that VS is complaining about in "unexpected end of file...".


I'm not understanding how to run it. I've generated an ARX project by the Objectarx wizard and added all your code in a file named BlockDrawCar.cpp.

You have to register  void cmdDrawCar()   as command.

The ARX Wizard creates a file named acrxEntryPoint.cpp where some sample commands are registered.

 

class CArxProjectTestApp : public AcRxArxApp {
	static void XXXXMyGroupMyCommand() {
	   // Put your command code here. I.e. call cmdDrawCar()
	}
}
// This macro actually registers void XXXXMyGroupMyCommand()
// as command "MyCommand" and "MyCommandLocal":
ACED_ARXCOMMAND_ENTRY_AUTO(CArxProjectTestApp, XXXXMyGroup, MyCommand, MyCommandLocal, ACRX_CMD_MODAL, NULL)

 

If you want to register a command "DRAW_CAR" you would add:

class CArxProjectTestApp : public AcRxArxApp {
	static void XXXXMyGroupDRAW_CAR() {
	   cmdDrawCar();
	}
}

ACED_ARXCOMMAND_ENTRY_AUTO(CArxProjectTestApp, XXXXMyGroup, DRAW_CAR, DISEGNA_AUTO, ACRX_CMD_MODAL, NULL)

Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 12 of 21

Anonymous
Not applicable

@tbrammer  it works 😁

the sample is perfect, i'd like to add only another thing. How can I replace the DrawBlockFunction with a certain block that I've already defined, e.g. with the block of the car in the .dwg that you posted before ? 

 

Thx,

Marco

0 Likes
Message 13 of 21

tbrammer
Advisor
Advisor

It's already in the sample code.

The first call of cmdDrawCar()  creates the block "car".

If you call cmdDrawCar()  again, it will use the existing block "car" and simply create another BREF to it.

 

Or do you mean how you can load a block from an external DWG?

This can be done like this:

 

 

 

AcDbObjectId insertDwgAsBlock(AcDbDatabase *pTargetDb, LPCWSTR dwgFilePath, LPCWSTR blockname)
{
	AcDbObjectId BtrID; // The resulting Id of the AcDbBlockTableRecordd
	Acad::ErrorStatus es;

	//----- Read the external DWG file
	AcDbDatabase *pDwg = new AcDbDatabase( Adesk::kFalse );	
	es = pDwg->readDwgFile(dwgFilePath);

	//----- Insert it into the current Database
	if (!es)
		es = pTargetDb->insert(BtrID, blockName, pDwg, true);
	delete pDwg;	
	return BtrID;
}

 

 

 

 

With db and BtrID from the code above you can use it like this :

 

 

 

	BtrID = insertDwgAsBlock(db, L"C:\\blocks\\car.dwg", L"car");

 

 

 

 

 


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 14 of 21

Anonymous
Not applicable

Fantastic @tbrammer , it works perfectly. I wanted to do just that. I'd like to ask you one last thing: can I add to these cars a palette of personal characteristics such as color, number of seats etc ? 

 

Thx,

Marco

0 Likes
Message 15 of 21

tbrammer
Advisor
Advisor

can I add to these cars a palette of personal characteristics such as color, number of seats etc ?

Yes to both.

 

Regarding colors I would recomment to use AutoCAD's color logic for blocks and entities.

Beside the "regular" RGB or color-index colors there are the special index colors values "fromBlock"=0 and "fromLayer"=256. See docs on AcDbColor and AcCmColor for details.

 

Regarding other characteristics there are different ways to implement them.  You can add custom data to AcDbObjects by

  • Adding XData (see AcDbObject::setXData(..))
  • Adding XRecords in the extension dictionary of an AcDbObject (see class AcDbXrecord and the AcDbObject methods extensionDictionary() and createExtensionDictionary())

You might want to read the ARX docs about "Protocol Extension" or ways to use COM to implement "Static or Dynamic Properties" and interact with the OPM (Object Property Manager).


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 16 of 21

Anonymous
Not applicable

@tbrammer  in your opinion which solution is easier? Can you show me a quick sample ? 

 

Thx, Marco

0 Likes
Message 17 of 21

tbrammer
Advisor
Advisor

XData is easier to use, but limited to 16kb Data. Allowed "restypes" are in the range 1000-1071.

XRecord have a bit more overhead but less limitations. They allow restypes in the range 1-369 (except 5 and 105)

Both are based on resbuf-lists that contain the data. A resbuf list is a linked list:

struct resbuf {                                                  
        struct resbuf *rbnext; // Allows them to be "linked"
        short restype;
        union ads_u_val resval;
};

The restypes defines which component of the union resval is valid.

Sample:

LPCWSTR myAppName=L"MYAPP";

void setCarXData(AcDbBlockReference *brefCar) // brefCar must be open for write
{
	acdbRegApp(myAppName); // Make sure your XData appname is registered

	resbuf *rb = acutBuildList(
		AcDb::kDxfRegAppName, myAppName,
		AcDb::kDxfXdAsciiString, L"Car",
		AcDb::kDxfXdControlString, L("{"),
		  AcDb::kDxfXdAsciiString, L"Lamborghini", //name
		  AcDb::kDxfXdReal,	250.0,				   //max speed
		  AcDb::kDxfXdInteger16,	2,			   //number of seats
		AcDb::kDxfXdControlString, L("}"),
		0
	);
	brefCar->setXData(rb); // this will replace the XData for myAppName
	acutRelRb(rb); // release the list.
}

void getCarXData(AcDbBlockReference *brefCar) // brefCar must be open for read
{
	resbuf *rb = brefCar->XData(myAppName);  // get XData for myAppName
	// -- use rb here --
	acutRelRb(rb); // release the list.
}

 

You can look at XData of entities using ArxDbg.arx with command SNOOPENTS or SNOOPDB and hit [Xdata...]


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 18 of 21

Anonymous
Not applicable

I think that for my purposes Xdata data limit can be useful. But I'm not understanding: this properties can by edited in Autocad implemeting in the future a Dialog to show a palette like:

 

Brand: Lamborghini

Seats: 2

 

....

 

Thx,

Marco

0 Likes
Message 19 of 21

tbrammer
Advisor
Advisor

Your dialog must retrieve and interprete the Xdata and display it to the user.

When the user has changed some values and leaves the dialog with [OK], you have to create a new resbuf list with the updated values and replace the existing rebuf list with the new one.


Thomas Brammer ● Software Developer ● imos AGLinkedIn
If an answer solves your problem please [ACCEPT SOLUTION]. Otherwise explain why not.

0 Likes
Message 20 of 21

Anonymous
Not applicable

Dialogs should be implemented with objectARX ? 

0 Likes