27
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 7

Now that we’re able to place a grid of front tiles / segments, the only thing left is the placement of the right / top blind front panels – if the front tiles don’t fit properly. As always, we’re starting with a very basic test. The right blind front (if there’s one) will be placed on the bottom board, therefore start at z == SimpleCabinetModel::kBoardThickness.

TEST_N(FrontModelRightBlindFront)
{
	SimpleCabinetModel simpleCabinet(2 * 200 + 2 * SimpleCabinetModel::kBoardThickness + 50, 
									 3 * 150 + 2 * SimpleCabinetModel::kBoardThickness + 70, 800);
	
	FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(), simpleCabinet.GetFrontWidth(), simpleCabinet.GetFrontHeight());
	
	CHECK_EQUAL(SimpleCabinetModel::kBoardThickness, frontModel.GetRightZBlindFront());
}

Well, you could see that one coming, making the test pass is rather trivial – but it gets us on the track to more sophisticated functionality.

	WorldCoord GetRightZBlindFront() const {
		return fOrigin.z;
	}

Let’s add another check to the test. The height of the right blind front should be the height of the front model.

	CHECK_EQUAL(3 * 150 + 70, frontModel.GetRightBlindFrontHeight());

Quite obvious and should be easy to implement. There’s already an instance variable fHeight in the FrontModel:

	WorldCoord frontModel.GetRightBlindFrontHeight() const {
		return fHeight;
	}

The third component needed to generate the geometry for the right blind front model is the base rectangular shape for the extrude. Another check for our test.

	CHECK_EQUAL(WorldRect(SimpleCabinetModel::kBoardThickness + frontModel.GetNumColumns() * FrontModel::kSegmentWidth, 
						  FrontModel::kSegmentDepth,
						  SimpleCabinetModel::kBoardThickness + simpleCabinet.GetFrontWidth(), 0), 
				frontModel.GetRightBlindFrontBase());

Introducing two additional constants for the tile/segment’s width and depth, making the test pass requires a few lines of code:

class FrontModel {

	WorldRect GetRightBlindFrontBase() const {
		WorldCoord xStart = fOrigin.x + GetNumColumns() * kSegmentWidth;
		WorldCoord xEnd = fOrigin.x + fWidth;
		
		WorldRect r(0, 0, 0, 0);
		if (xStart != xEnd)
			r.Set(xStart, kSegmentDepth, xEnd, 0);

		return r;
	}
};

The top blind front is tackled the same way – We need it’s z position, it’s x dimensions and it’s height. Not an awful lot to see there. Now let’s check if we’re able to generate Vectorworks geometry with this model.

	void CreateFrom(const FrontModel& frontModel) {
		
		// ...

		WorldRect topBlindFrontBase = frontModel.GetTopBlindFrontBase();
		if (topBlindFrontBase != WorldRect(0, 0, 0, 0)) {
			MCObjectHandle topBlindFront = gSDK->CreateExtrude(frontModel.GetTopZBlindFront(), frontModel.GetTopZBlindFront() + frontModel.GetTopBlindFrontHeight());
			gSDK->AddObjectToContainer(gSDK->CreateRectangle(frontModel.GetTopBlindFrontBase()), topBlindFront);
		}
		
		WorldRect rightBlindFrontBase = frontModel.GetRightBlindFrontBase();
		if (rightBlindFrontBase != WorldRect(0, 0, 0, 0)) {
			MCObjectHandle rightBlindFront = gSDK->CreateExtrude(frontModel.GetRightZBlindFront(), frontModel.GetRightZBlindFront() + frontModel.GetRightBlindFrontHeight());
			gSDK->AddObjectToContainer(gSDK->CreateRectangle(frontModel.GetRightBlindFrontBase()), rightBlindFront);
		}
		

In fact, we are:

Episode 7

Tune in next weeks when you hear Dr. Bob say: “Let’s sum it up, post the code and I’m outta here”.

Previous Episode | Next Episode


24
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 6

We left off in Episode 5 trying to avoid Dr. Bob hitting us with a rolled-up newspaper as we forgot to account for the front tiles’s z position. Let’s see if we can fix this. First, we change the test for the front model origin to expect a WorldPt3 which, obviously, does contain a z-component.

TEST_N(FrontModelOrigin)
{
	SimpleCabinetModel simpleCabinet(600, 720, 800);
	
	FrontModel frontModel(simpleCabinet.GetFrontModelOrigin());
	
	CHECK_EQUAL(WorldPt3(SimpleCabinetModel::kBoardThickness, 0, SimpleCabinetModel::kBoardThickness), frontModel.GetOrigin());
}

Making this test pass requires a series of simple changes to FrontModel.

class FrontModel {

public:

	FrontModel(const WorldPt3& origin) :
		fOrigin(origin) {
	}

	WorldPt3 GetOrigin() const {
		return fOrigin;
	}

private:

	WorldPt3 fOrigin;
}

After making these changes gcc tells us that we should probably fix the tests FrontModelPositionOfFirstSegment and FrontModelPositionOfSecondSegment in order to make this thing compilable again. As in FrontModelOrigin, this requires us to replace WorldPt with WorldPt3 and we’re basically done.

With this out of the way, let’s concentrate on generalizing our FrontModel. So far, we’re able to calculate the position of the first and second segment / tile (assuming both fit into the cabinet). Looking at the screenshot in episode 0, it sure looks like the front tiles should be arranged in rows & columns. Let’s see if we can work towards this. How about computing the number of front tile rows & columns which “fit” into a given cabinet? Let’s start with a simple test – if the cabinet is too small, no front tiles will fit at all.

TEST_N(FrontModelNoRowsNoColumnsNotWideEnough)
{
	SimpleCabinetModel simpleCabinet(150, 720, 800);
	
	FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(), 
						  simpleCabinet.GetFrontWidth(), 
						  simpleCabinet.GetFrontHeight());
	
	CHECK_EQUAL(0, frontModel.GetNumRows());
	CHECK_EQUAL(0, frontModel.GetNumColumns());
}

In order to be able to compute the number of rows & columns, we have to supply the FrontModel with the space available for placing front tiles. So, we need tests to for these functions. Let’s #if 0 the last test for a moment and write new tests for GetFrontWidth() and GetFrontHeight().

TEST_N(SimpleCabinetFrontWidthHeight)
{
	SimpleCabinetModel simpleCabinet(600, 720, 800);

	CHECK_EQUAL(600 - 2 * SimpleCabinetModel::kBoardThickness, 
				simpleCabinet.GetFrontWidth());
	CHECK_EQUAL(720 - 2 * SimpleCabinetModel::kBoardThickness, 
				simpleCabinet.GetFrontHeight());
}

Let’s make this test pass.


class SimpleCabinetModel {

public:
	
	WorldCoord GetFrontWidth() const {
		return fWidth - 2 * kBoardThickness;
	}
	
	WorldCoord GetFrontHeight() const {
		return fHeight - 2 * kBoardThickness;
	}
};

Backtracking to our test FrontModelNoRowsNoColumnsNotWideEnough, we change FrontModel‘s constructor (again).

class FrontModel {

public:
	
	FrontModel(const WorldPt3& origin, WorldCoord width, WorldCoord height) : 
		fOrigin(origin), fWidth(width), fHeight(height) {
	}
};

What was our intital goal? Ah, computing the number of rows & columns (in order to make the test pass). Well, that’s straightforward:

class FrontModel {

public:
	
	long GetNumRows() const {
		return 0;
	}
	
	long GetNumColumns() const {
		return 0;
	}
};

Indeed, it’s pretty straightforward for the test we’re trying to make pass. We just write enough code to make the test pass. And returning 0 makes the test pass. Let’s move on to the next test.

TEST_N(FrontModelThreeRowsTwoColumnsFits)
{
	SimpleCabinetModel simpleCabinet(2 * 200 + 2 * SimpleCabinetModel::kBoardThickness, 
									 3 * 150 + 2 * SimpleCabinetModel::kBoardThickness, 
									 800);
	
	FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(), 
						  simpleCabinet.GetFrontWidth(), 
						  simpleCabinet.GetFrontHeight());
	
	CHECK_EQUAL(3, frontModel.GetNumRows());
	CHECK_EQUAL(2, frontModel.GetNumColumns());
}

Now is the right time to flesh out GetNumRows() and GetNumColumns().

class FrontModel {

public:
	
	long GetNumRows() const {
		return MyMathUtils::fdiv(fHeight, SimpleCabinetModel::kSegmentHeight);
	}
	
	long GetNumColumns() const {
		return MyMathUtils::fdiv(fWidth, SimpleCabinetModel::kSegmentWidth);
	}
};	

In order to get the number of front tiles fitting into a row, we need to divide the overall width available for the front tiles and divide it (as in division of integers) by the width of a segment. Same for the number of front tiles fitting into a column.

Where are we at? We now have code for calculating the number of tiles in x direction (columns) and z direction (rows). If we would have a function computing the x, y & z position of a front tile at a given row & column we could certainly beef up SimpleCabinetCreator::CreateFrom() with a nice loop placing all the front tiles. Let’s write a test for this function computing a tiles position:

TEST_N(FrontModelPositionOfARowColumn)
{
	SimpleCabinetModel simpleCabinet(1200, 720, 800);
	
	FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(), 
						  simpleCabinet.GetFrontWidth(), 
						  simpleCabinet.GetFrontHeight());

	WorldPt3 expectedPosition = frontModel.GetOrigin() +
								WorldPt3(3 * SimpleCabinetModel::kSegmentWidth, 0, 
										 2 * SimpleCabinetModel::kSegmentHeight)
	CHECK_EQUAL(expectedPosition, 
				frontModel.GetSegmentPosition(3, 2));
}

Here’s the code to make the test pass:

class FrontModel {

public:
	
	WorldPt3 GetSegmentPosition(long column, long row) const
	{
		WorldPt3 position(GetOrigin());
		
		position += WorldPt3(column * fSegmentWidth, 
							 0,
							 row * fSegmentHeight);
		
		return position;
	}
};

And for the grand final of this episode, let’s tackle placing the front tile symbols in SimpleCabinetCreator::CreateFrom(). With the preparatory work we’ve done in this episode it should be pretty straightforward – and it is:

	
	void CreateFrom(const FrontModel& frontModel) {
		
		for (long i = 0; i  frontModel.GetNumColumns(); i++) 
			for (long j = 0; j < frontModel.GetNumRows(); j++) {
				VWObject frontSegment = gSDK->PlaceSymbolByNameN(frontModel.GetFrontSymbolName(), WorldPt(0, 0));
				frontSegment.MoveObject3D(frontModel.GetSegmentPosition(i, j)); 
				fSimpleCabinet.AddObject(frontSegment);
			}	
	}

Episode 6

Tune in next week when you hear Nurse Piggy say: “Are we done yet? I have a date with a certain frog for dinner… “.

Previous Episode | Next Episode


21
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 5

In Episode 4 we finally saw the fruits of our labor – TDD-style. What’s left? We need to come up with some code for the front tiles / segments.

Let’s get right to it. Here the first test. We assume that there’s class FrontModel with a method GetOrigin(), which returns the position of the leftmost bottom front tile.

TEST_N(FrontModelPositionOfFirstSegment)
{
	SimpleCabinetModel simpleCabinet(600, 720, 800);
	
	FrontModel frontModel;
	simpleCabinet.GetFrontModel(frontModel);
	
	CHECK_EQUAL(WorldPt(SimpleCabinetModel::kBoardThickness, 0), 
				frontModel.GetOrigin());
	
}

This is another one of these “Get’s us going” tests. Not an awful lot of thought need to make it pass, but it puts us in the right mood for the next challenge.


class SimpleCabinetModel {
	
public:
	
	// ...

	void GetFrontModel(FrontModel& model) const {
		model.SetOrigin(WorldPt(kBoardThickness, 0));
	}
};	


class FrontModel {
	
public:
	
	FrontModel() : fOrigin(WorldPt (0, 0)) {
	}
	
	WorldPt GetOrigin() {
		return fOrigin;
	}
	
	void SetOrigin(const WorldPt& origin) {
		fOrigin = origin;
	}
	
private:
	
	WorldPt fOrigin;
	
};

What’s next? Look at the “spec” in Episode 0, it looks like the front tiles are made of rows and columns – but let’s not jump to conclusions yet. Dr. Bob is just a quack, not a CS major. The next simple test which comes to our mind is checking for the position of the second front tile – which should be located to the right of the first front tile (we assume that both tiles fit into the cabinet, of course).

TEST_N(FrontModelPositionOfSecondSegment)
{
	SimpleCabinetModel simpleCabinet(600, 720, 800);
	
	FrontModel frontModel;
	simpleCabinet.GetFrontModel(frontModel);
	
	CHECK_EQUAL(frontModel.GetOrigin() + WorldPt(SimpleCabinetModel::kSegmentWidth, 0), 
				frontModel.GetPositionOfSecondSegment());
}

In order to calculate the second tiles position, we need the width of a front tile. For now, we’re accessing SimpleCabinetModel::kSegmentWidth from FrontModel::GetPositionOfSecondSegment() for this purpose.

	WorldPt GetPositionOfSecondSegment() const {
		return fOrigin + WorldPt(SimpleCabinetModel::kSegmentWidth, 0);
	}

Doesn’t feel absolutely right yet, but we’re confident that this will be smoothed out over the next tests & episodes.

After the raving success with SimpleCabinetCreator in Episode 4, let’s see if we can create the Vectorworks geometry for the first & second tile before continuing our journey fleshing out the FrontModel. We have the uneasy feeling that The reason why

Let’s start with the plug-in object’s Recalculate() function. We spice up SimpleCabinetCreator a bit, as we need access to both the SimpleCabinetModel and FrontModel. Asking SimpleCabinetModel to “fill in the blanks” in FrontModel didn’t feel right, so let’s change it and pass the origin as a parameter to FrontModel‘s constructor. Way better.

EObjectEvent CObject_EventSink::Recalculate()
{
	VWParametricObj object(fhObject);
	double width = object.GetParamReal(&quot;Width&quot;);
	double height = object.GetParamReal(&quot;Height&quot;);
	double depth = object.GetParamReal(&quot;Depth&quot;);
	
	SimpleCabinetModel simpleCabinetModel(width, height, depth);

	FrontModel frontModel(simpleCabinetModel.GetFrontModelOrigin());

	SimpleCabinetCreator creator(object);
	creator.CreateFrom(simpleCabinetModel, frontModel);
	
	return kObjectEventNoErr;
}

Where does SimpleCabinetCreator get the front symbols name from? FrontModel, of course. We hard-wire the name of the symbol for now:

class SimpleCabinetCreator {
	
public:
	
	SimpleCabinetCreator(const VWParametricObj& pio) : fSimpleCabinet(pio) {
	}
	
	void CreateFrom(const SimpleCabinetModel& cabinetModel, const FrontModel& frontModel) {
		
		CreateFrom(cabinetModel);
		CreateFrom(frontModel);
	}
	
private:
	
	void CreateFrom(const SimpleCabinetModel& cabinetModel) {
		// ...		
	}
	
	void CreateFrom(const FrontModel& frontModel) {
				
		MCObjectHandle firstFrontSegment = gSDK->PlaceSymbolByNameN(frontModel.GetFrontSymbolName(), frontModel.GetOrigin());
		fSimpleCabinet.AddObject(firstFrontSegment);
		
		MCObjectHandle secondFrontSegment = gSDK->PlaceSymbolByNameN(frontModel.GetFrontSymbolName(), frontModel.GetPositionOfSecondSegment());
		fSimpleCabinet.AddObject(secondFrontSegment);
	}
	
	VWParametricObj fSimpleCabinet;
	
};

Firing up Vectorworks 2011, we are supposed to be – once again – pleased with the results. However, a closer inspection reveals that the front tiles collide with the bottom board – ah, we forgot the tiles z position in our model.

Episode 5

Tune in next week when you heard Dr. Bob say: “Nurse Piggy, would you mind slapping this developer here with a rolled-up newspaper?”

Previous Episode | Next Episode


19
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 4

In Episode 3 – Not doing the same sketch twice, we made some good progress on a class finally capable of describing the geometry of the cabinet’s parts. In this episode, we will create Vectorworks geometry. It seems like a good idea to separate the creation of the geometry from the actual model (much in the way a View is separated from a Model in MVC). Let’s call the new SimpleCabinetCreator:

class SimpleCabinetCreator {
	
public:
	
	static void CreateFrom(const SimpleCabinetModel&amp;amp;amp;amp;amp; model) {
		
		MCObjectHandle leftSide = gSDK->CreateExtrude(0, model.GetHeight());
		gSDK->AddObjectToContainer(gSDK->CreateRectangle(model.GetLeftSideBase()), leftSide);
		
		MCObjectHandle rightSide = gSDK->CreateExtrude(0, model.GetHeight());
		gSDK->AddObjectToContainer(gSDK->CreateRectangle(model.GetRightSideBase()), rightSide);
		
		MCObjectHandle top = gSDK->CreateExtrude(model.GetTopZPosition(), model.GetTopZPosition() + model.kBoardThickness);
		gSDK->AddObjectToContainer(gSDK->CreateRectangle(model.GetTopBottomBase()), top);
		
		MCObjectHandle bottom = gSDK->CreateExtrude(0, model.kBoardThickness);
		gSDK->AddObjectToContainer(gSDK->CreateRectangle(model.GetTopBottomBase()), bottom);
		
	}
	
};

Looks good so far. The main takeaway point is that the Creator class shouldn’t do any calculation. It should be translating the model straight into Vectorworks geometry without any logic on it’s own.

While we’re at it: How about renaming SimpleCabinet to SimpleCabinetModel? This would be a nice symmetrical way of naming the classes: SimpleCabinetModel and SimpleCabinetModel. Let’s do that.

Now for the grand finale of this episode. The plug-in object’s Recalculate() method is pretty straightforward. All the basic building blocks are already in place:

EObjectEvent CObject_EventSink::Recalculate()
{
	VWParametricObj object(fhObject);
	double width = object.GetParamReal("Width");
	double height = object.GetParamReal("Height");
	double depth = object.GetParamReal("Depth");
	
	SimpleCabinetModel simpleCabinetModel(width, height, depth);
	SimpleCabinetCreator::CreateFrom(simpleCabinetModel);
	
	return kObjectEventNoErr;
}

We get the width, height & depth from the parametric object, create a model and ask the SimpleCabinetCreator to create the geometry defined in the model. Let’s fire up Vectorworks and see if everything is working as expected. And – much to our surprise – it’s working out as expected:

Episode4

Sounds like a great way to end this episode. Let’s see if we can add the front tiles in the upcoming episodes. TDD-style, of course.

Make sure to tune in next week when you hear Dr. Bob say: “A third class? I’m a quack, not a CS major.”.

Previous Episode | Next Episode


18
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 3

In closing Episode 2 – Spicing up our class, we heard Dr. Bob calling bullshit on the x coordinate testing we came up with. We should be able to test the y & z coordinates of the cabinet parts, too. But this would sure look like Dr. Bob doing the same sketch twice. It would be way better (and more efficient) to calculate the 2D rectangles forming the basis of the different cabinet parts (assuming we could get away with simple extrudes for the cabinet’s parts). Let’s do that and rewrite the tests and code accordingly.

TEST_N(SimpleCabinetGetLeftSideBase)
{
	SimpleCabinet simpleCabinet(600, 720, 800);
	
	CHECK_EQUAL(WorldRect(0, 800, 
						  SimpleCabinet::kBoardThickness, 0), 
				simpleCabinet.GetLeftSideBase());
}

Making the test pass is supposed to pretty simple, we calculate the WordRect required using the depth of SimpleCabinet. But much to our dismay, there’s no such instance variable fDepth. Let’s add it (test first, of course):

TEST_N(SimpleCabinetWidthAndHeight)
{
	SimpleCabinet simpleCabinet(600, 720, 800);
	
	CHECK_EQUAL(600, simpleCabinet.GetWidth());
	CHECK_EQUAL(720, simpleCabinet.GetHeight());
	CHECK_EQUAL(800, simpleCabinet.GetDepth());
}

Of course, making the test pass is trivial. We add the accessor and instance variable to SimpleCabinet:


	WorldCoord GetDepth() const {
		return fDepth;
	}
	
	WorldCoord fDepth;

Now back to our initial task, coding GetLeftSideBase():

	WorldRect GetLeftSideBase() const {
		return WorldRect(0, fDepth, kBoardThickness, 0);
	}

With GetLeftSideBase() under our belt, let’s tackle the cabinet’s right side:

TEST_N(SimpleCabinetGetRightSideBase)
{
	SimpleCabinet simpleCabinet(600, 720, 800);
	
	CHECK_EQUAL(WorldRect(600 - SimpleCabinet::kBoardThickness, 800, 
						  600, 0), 
				simpleCabinet.GetRightSideBase());
}

As the basic shape of the right side will be identical to the left side’s shape, we choose to implement it by re-using LeftLeftSideBase():

	WorldRect GetRightSideBase() const {
		WorldRect r = GetLeftSideBase();
		r.Offset(WorldPt(fWidth - kBoardThickness, 0));
		return r;
	}	

With the base shape of the cabinet’s left & right side plus the cabinet’s height, we are pretty optimistic about being able to create the proper Vectorworks geometry. Let’s see if we can compute the top & bottom board’s basic shape too. The basic shape for both parts is:

TEST_N(SimpleCabinetGetTopBottomBase)
{
	SimpleCabinet simpleCabinet(600, 720, 800);
	
	CHECK_EQUAL(WorldRect(SimpleCabinet::kBoardThickness, 800, 
						  600 - SimpleCabinet::kBoardThickness, 0), 
				simpleCabinet.GetTopBottomBase());
}

Again, pretty simple to make this test pass:

	WorldRect GetTopBottomBase() const {
		return WorldRect(kBoardThickness, fDepth, 
						fWidth - kBoardThickness, 0);
	}

For the top board, we will sure need the z position, too. Let’s do this before we’re wrapping it up for today:

TEST_N(SimpleCabinetGetTopZPosition)
{
	SimpleCabinet simpleCabinet(600, 720, 800);
	
	CHECK_EQUAL(720 - SimpleCabinet::kBoardThickness, 
				simpleCabinet.GetTopZPosition());
}

Coding up SimpleCabinet::GetTopZPosition() is a breeze now that we’re on a roll ­čśë

	WorldCoord GetTopZPosition() const {
		return fHeight - kBoardThickness;
	}

That’s it for today. We have been able to come up with a representation of the different cabinet part’s basic shape, which, if mixed with the models information about the cabinet’s height, should enable us to – finally – create some geometry in Vectorworks.

Tune in next week when you hear Dr. Bob say “It’s time to create some Vectorworks objects or the patient will die of boredom…”.

P.S.: Just in case you’re wondering about Dr. Bob – Check out The Veterinarian’s Hospital on Wikia or on Youtube. This should give me a minute or two to prepare Episode 4.

Previous Episode | Next Episode


17
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 2

In Episode 1 – How to start?, we’ve cleared the first hurdle – we actually wrote a passing test. It was rather trivial, but we ended up with a class representing a cabinet with the properties “Width” and “Height”. What’s next? How about spicing up the SimpleCabinet class? (At the current state, just adding some salt to the class will spice it up substantially). How about allowing us to calculate the position of the left & right side of the cabinet? The left side of the cabinet should be located at x = 0 (in the local coordinate space of the plug-in object):

TEST_N(SimpleCabinetGetLeftSideXLocation)
{
	SimpleCabinet simpleCabinet(600, 800);
	
	CHECK_EQUAL(0, simpleCabinet.GetLeftSideXLocation());
}

Passing this test is (again) rather trivial. We add the following message to the SimpleCabinet class:

	WorldCoord GetLeftSideXLocation() const {
		return 0;
	}

If the left side’s located at x = 0, then the right side’s x location should be the width of the cabinet minus the board thickness (assuming that the width property denotes the overall width of the cabinet).

TEST_N(SimpleCabinetGetRightSideXLocation)
{
	SimpleCabinet simpleCabinet(600, 800);
	
	CHECK_EQUAL(600 - SimpleCabinet::kBoardThickness, 
				simpleCabinet.GetRightSideXLocation());
}

Adding the following method to SimpleCabinet will make the test pass. We didn’t plan for this in episode 1, but we can re-use the fWidth instance variable for the purpose of deriving the right side’s x location.


	static const WorldCoord kBoardThickness;

	WorldCoord GetRightSideXLocation() const {
		return fWidth - kBoardThickness;
	}	

While we’re at it, we could also test the x location of the cabinet’s bottom, which should be at x = kBoardThickness (as the cabinet is supposed to have continuous sides):

TEST_N(SimpleCabinetGetBottomXLocation)
{
	SimpleCabinet simpleCabinet(600, 800);
	
	CHECK_EQUAL(SimpleCabinet::kBoardThickness, 
				simpleCabinet.GetBottomXLocation());
}

Making the test pass is simple:

	WorldCoord GetBottomXLocation() const {
		return kBoardThickness;
	}

Where are we at? At last, we’re starting to actually compute something. Our “model” class is able to deliver a cabinet’s overall width & height plus the x location of the cabinet’s left side, right side & bottom. We sticked to writing the tests first. Not an awful lot of sophistication here (yet), but progress.

Tune in next week when you hear Dr. Bob say: “I’m calling bullshit on this x coordinate testing…”.

Previous Episode | Next Episode


17
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 1

After Episode 0 – Introduction, let’s try to get some real coding done in this episode.

For a TDD newbie, the first questions are always along these lines: “How do I start? What’s the first test? How do I know what to test?”

Starting with a blank slate, let’s assume we have a class SimpleCabinet – we sure need something like that. Instantiating a SimpleCabinet object with a given width & height, we should be able to derive the width & height from the object. Not very elaborate or challenging, but it’s a start. Let’s see where this leads us.

#include "CppUnitLite2.h"

TEST_N(SimpleCabinetWidthAndHeight)
{
	SimpleCabinet simpleCabinet(600, 800);
	
	CHECK_EQUAL(600, simpleCabinet.GetWidth());
	CHECK_EQUAL(800, simpleCabinet.GetHeight());
}

Obviously, the code doesn’t compile. We need an implementation:


class SimpleCabinet {

public:
	
	SimpleCabinet(WorldCoord width, WorldCoord height)
		: fWidth(width), fHeight(height) {
	}

	WorldCoord GetWidth() const {
		return fWidth;
	}
	
	WorldCoord GetHeight() const {
		return fHeight;
	}
	
private:
	
	WorldCoord fWidth;
	WorldCoord fHeight;
};

Not an awful lot to see here, but at least we are able to compile & link successfully. Uh, and it runs and the test passes. Excellent. That’s a great start for a sunny sunday morning while the baby’s asleep.

You think this is way too trivial? Where’s the beef? With a small brain like mine, I prefer to take small steps at a time. Writing a small test, just a little bit of implementation and get feedback right away. As the old Japanese proverb says: “Even a thousand-mile journey begins with the first step.”. The point is not to stop with this first step. We will get to some actual Vectorworks SDK code.

Tune in next week when you’ll hear Dr. Bob say…

Previous Episode | Next Episode


16
Apr 11

Developing a Vectorworks 2011 Plug-in, TDD-style – Episode 0

During a recent internal TDD code camp at extragroup, we were working on some basic TDD style katas like StringCalculator.

Some question regarding TDD & Vectorworks came up during the day:

  • “How do I develop a Vectorworks plug-in TDD style?”
  • “Is it possible to develop a Vectorworks plug-in TDD style?”

We haven’t been able to answer this question in sufficient detail during the one-day code camp, so I set out to work through a simple project. My goal is to develop a Vectorworks plug-in object – a simple cabinet. It should be defined by its dimensions:

  • Overall Width
  • Overall Height
  • Overall Depth

The front should be made of “tiles” defined by a Vectorworks symbol (think of a rectangular grid of symbols, e.g. a simple panel or a frame). This will be the fourth plug-in object parameter. If the grid of tiles won’t fit the available space precisely, some additional blind front panels to the right and to the top of the grid should be added.┬áTo keep things simple, the cabinet should have continuous sides, all boards are supposed to be 19mm thick (or 3/4″ for you imperial guys out there) – after all, our goal is to learn how to develop a Vectorworks plug-in object TDD-style, not to create a shipping product.

Simplecabinet

In the upcoming episodes, I won’t go into details on the actual structure of a Vectorworks plug-in, for more details on how to build a Vectorworks plug-in, please refer to http://developer.vectorworks.net. There’s also a very promising tutorial by Ryan McCuaig: Getting Started with the Vectorworks SDK. The sole focus of this series is to explore how to develop a Vectorworks plug-in object TDD-style.

I’ll be using┬áCppUnitLite2 as my C++ unit testing framework. In order satisfy the linker in the testing only target, a GSSDKStubs.cpp file contains stubs for all old-style GS_ API functions contained in the Vectorworks SDK. This enables us to create a separate testing target (command line tool) in our Xcode project linking the VW Foundation Classes while still mixing Vectorworks API calls and testing code in the same source freely. Having a separate testing target is a huge time-saver because it allows me to execute & test the pieces of our code which aren’t dependent on Vectorworks without actually starting up the Vectorworks application (which may take a moment or two).

Additionally, the testing code is also executed in the plug-in’s modules main function when loaded into Vectorworks:

if (action == kVCOMRegisterInterfaces) {
	TestResultStdErr result;
	TestRegistry::Instance().Run(result);
	if (result.FailureCount())
		Debugger();
}

That way I ensure that the testing code is run no matter which environment I’m in. If the tests work in the testing target, but fail when run in Vectorworks, I’m thrown into the debugger right away and get slapped with a rolled-up newspaper.

If you would like to go straight to the final code download, make sure to go straight to the Epilogue.

Enough of the preface. In the next episode, we’ll start coding.

Episodes so far:

You may follow this series via RSS or follow me on Twitter / Facebook.


09
Oct 09

The Atlassian Dragons Exercise

The installation process for the Atlassian Starter suite – Crowd, Bamboo, Fisheye, JIRA, Greenhopper and Confluence – is quite daunting and takes about 5 hours+ (way more on my Parallels VM setup, but I did expect that).

It’s obvious that the different Atlassian products have been built by different teams, at different times and sometimes even different companies. Although, AFAIK, all products are built with basically the same base technology (J2EE), each product has some minor differences in

  • Installation
  • Configuration
  • Directory set-up
  • Starting up / Stopping products (e.g. there’s no shutdown command for Crowd, Bamboo automatically installs as a service)
  • Configuration files

If the suite has to be installed manually, consistency in the setup process trumps everything. This is even more relevant if the suite is installed by a non-IT, non-Java plain old-fashioned C++ hacker like me.
Generally, editing the configuration files was no big deal, although the sheer number of changes necessary induced cross-eyes at times.

Including Crowd into the installation process made the setup process quite involved and complicated. Although single sign-on is quite a feature, I wouldn’t consider it crucial for a 10 user set-up. I would’ve preferred to make integration with Crowd an optional exercise. Plus, removing Crowd from the standard equation would have enabled more detailed feedback on setting up the different applications’s integration features.

Kudos to the Atlassian documentation team responsible for the detailed step-by-step descriptions. It was close to perfect, just very very minor errata in terms of version numbers. A few more screenshots would have been helpful, but would have made the endeavour of documenting the suite’s installation process not only daunting, but outright impossible to maintain over time.

I was very disappointed that Crucible was neither part of the exercise nor part of the $10 offer. Atlassian, please make Crucible part of the Dragons exercise and part of the $10 / 10 users offer. I’m sure there were very good technical and/or business reasons not to include it, but if the Atlassian team can pull of a stunt like the Dragons exercise, I know they can pull off including Crucible, too. It just takes a few more beers, I suppose. German beer, of course. ­čÖé


05
Mar 09

Is Quality Dead?

This looks like the start of an extremely interesting series: Quality is Dead #1: The Hypothesis.

Another factor contributing to this crisis are insanely short release cycles (even for desktop software) and the expectation that each major update has to introduce substantial new features. Lot’s of them. There’s no more time to polish & deepen existing features.