From BlenderWiki

Jump to: navigation, search

Adding a new Space Window

Adapted Window Selection Menu
New Window Space

As a newbie in coding blender it is hard to find the right starting point. I have decided to get involved in blender sources by creating a new space window first and to dig deeper into the code by extending this space with new features. This tutorial is based on the tutorial "Adding a new Space (Window type)" hosted by blender.org. [1] and serves as starting point for additional tutorials (e.g. adding header buttons or menus).

This tutorial should help new coders to get a better understanding of the different steps in the original tutorial in a more structured way.

It contains two main parts:

  • The creation of new source files which contain the minimum sources for a new space.
  • the embedding of the newly created space into the blender UI


At the end of the tutorial you should see an additional window type in the window selection menu. If you select this window type a blank screen should appear. In the windows header a windows selection menu should be embedded.


Note: As I mentioned before I am still a newbie in coding blender and in addition I am no native speaker. If you have found some parts in the tutorial which are wrong, look ugly or sound weird for a native speaker, don't hesitate to edit these parts. Thanks.


Create Source Files

The files we create are stored in src/. If you have a look at the existing screen source files you will see some sort of convention in naming them (e.g. Sound Screen is defined in drawsound.c, editsound.c and header_sound.c). We will create a new space called "Demo Space".

Three files have to be created in this step:

  • drawdemo.c
  • header_demo.c
  • editdemo.c

Note: All paths I mention here are related to blender/source/blender in svn.


drawdemo.c

This file contains code necessary to draw the screen content. We only define a blank screen using drawdemospace() which is a callback function. It is bound to the blender UI later in this tutorial.

#include "BIF_gl.h"           /* GL, GLU and some helpers */
#include "BIF_screen.h"       /* provides access to curarea pointer */
#include "BIF_mywindow.h"     /* camera definitions, window properties, mouse, ... */
#include "BIF_resources.h"    /* themes, icons, etc. (-> TH_BACK) */
#include "DNA_screen_types.h" /* ScrArea */
 
void drawdemospace(ScrArea *sa, void *spacedata)
{
	float col[3];
	BIF_GetThemeColor3fv(TH_BACK, col);
 
	glClearColor(col[0], col[1], col[2], 0.0);
	glClear(GL_COLOR_BUFFER_BIT);
 
	/* ortho at pixel level */
	myortho2(-0.375, (float)(sa->winx)-0.375, -0.375, (float)(sa->winy)-0.375);
 
	draw_area_emboss(sa);
 
	curarea->win_swap= WIN_BACK_OK; 
}

At this point it's good to know openGL. If you don't know openGL yet I suggest to learn the basics of it first as the blender UI is completely based on openGL. A good starting point might be one of the following pages:

header_demo.c

This file contains code for embedding buttons, menus, etc. in the space header (mostly UI stuff, some openGL stuff). We only define the window type selection menu which is used by all window spaces in blender. The definition of additional menus, buttons, etc. should be part of different tutorials. demo_buttons() is called by the UI to draw the header content. The binding of this function will be shown later in this tutorial.

#include "DNA_screen_types.h"   /* ScrArea */
#include "BIF_interface.h"      /* uiNewBlock, uiBlockSetCol */
#include "BIF_screen.h"         /* curarea pointer */
#include "BIF_resources.h"      /* themes, icons, etc. (-> TH_HEADER) */
#include "BSE_headerbuttons.h"  /* GetButStringLength */
#include "DNA_space_types.h"    /* SPACE_DEMO */
#include "blendef.h"	
 
#include <stdio.h>              /* sprintf */
 
void demo_buttons(ScrArea *sa)
{
	/* draw buttons */
	uiBlock *block;
	short xco;
	char name[256];
 
	sprintf(name, "header %d", curarea->headwin);
	block= uiNewBlock(&curarea->uiblocks, name, UI_EMBOSS, UI_HELV, curarea->headwin);
 
	if(area_is_active_area(curarea)) uiBlockSetCol(block, TH_HEADER);
	else uiBlockSetCol(block, TH_HEADERDESEL);
 
	curarea->butspacetype= SPACE_DEMO;
 
	xco = 8;
 
	uiDefIconTextButC(block, ICONTEXTROW,B_NEWSPACE, ICON_VIEW3D, 
					  windowtype_pup(), xco, 0, XIC+10, YIC, 
					  &(curarea->butspacetype), 1.0, SPACEICONMAX, 0, 0, 
					  "Displays Current Window Type. "
					  "Click for menu of available types.");
 
	xco += XIC + 14;
 
	uiBlockSetEmboss(block, UI_EMBOSSN);
 
	/* always as last  */
	curarea->headbutlen= xco+2*XIC;
 
	uiDrawBlock(block);	
}


editdemo.c

This file contains code to handle UI events, helper functions and screen control logics. Our new space won't have a lot of code in here as we create an initial window without much functionality. As you can see only one function is contained here. winqreaddemospace() is a callback function of the UI and will be bound to the UI source later in this tutorial.

#include "BIF_mywindow.h"     /* BWinEvent */
#include "DNA_screen_types.h" /* ScrArea */
#include "BIF_interface.h"    /* uiDoBlocks */
 
void winqreaddemospace(ScrArea *sa, void *spacedata, BWinEvent *evt)
{
	/* event handling */
	if(evt->val) {
 
		if( uiDoBlocks(&sa->uiblocks, evt)!=UI_NOTHING ) 
			evt->event= 0;
	} 
 
	/* redraw window */
	scrarea_queue_winredraw(sa);
}


Embed a new Space

To embed a new Space in blender at least the following things have to be done:

  • create a data structure which keeps the space state
  • define a unique space type identifier
  • add new space to the window type selection menu
  • bind callbacks to UI


Creation of Space Data structure

The SpaceDemo structure is embedded in makesdna/DNA_space_types.h. As you can see the other spaces are defined here as well.

typedef struct SpaceDemo {
	SpaceLink *next, *prev;
	int spacetype;
	float blockscale;
	struct ScrArea *area;
 
	short blockhandler[8];
 
	View2D v2d;
} SpaceDemo;


Definition of the Space Identifier

The Space identifiers are stored in the same file as the SpaceDemo structure. If you scroll to the end of the file you will find the enumeration of all space identifiers. Prior to "SPACEICONMAX = SPACE_NODE", insert, "SPACE_DEMO", and change SPACEICONMAX to the the value of SPACE_DEMO. SPACEICONMAX should always be the value of the last [actual] space enumeration.

enum {
	...
	SPACE_NODE,
	SPACE_DEMO,
	SPACEICONMAX = SPACE_DEMO
/*	SPACE_LOGIC	*/
};

Adapt Window Type Selection Menu

Open the file src/headerbuttons.c and edit the function windowtype_pup().

char *windowtype_pup(void)
{
 
	...
 
	"|Scripts Window %x14"//313
	"|%l"
	"|Demo Space %x17"	
	);
}


At the end of the function you can see the newly added line which consists of the space name. The line "|%l" is used as separator between item groups.

Note: Just a reminder that the assignment "SPACEICONMAX" also has 17 as its value, per, "SPACEICONMAX = SPACE_DEMO".

DemoSpace Initialization

The data structure DemoSpace defined in the beginning needs to be initialized. Therefore we need to ad a new function to src/space.c.

extern void drawdemospace(ScrArea *sa, void *spacedata);
extern void winqreaddemospace(struct ScrArea *sa, void *spacedata, struct BWinEvent *evt);
 
static void init_demospace(ScrArea *sa)
{
	SpaceDemo *sd;
 
	sd = MEM_callocN(sizeof(SpaceDemo), "initdemospace");
	BLI_addhead(&sa->spacedata, sd);
 
	sd->spacetype= SPACE_DEMO;	
 
	sd->v2d.tot.xmin=  -10.0;
	sd->v2d.tot.ymin=  -10.0;
	sd->v2d.tot.xmax= (float)sa->winx + 10.0f;
	sd->v2d.tot.ymax= (float)sa->winy + 10.0f;
 
	sd->v2d.cur.xmin=  0.0;
	sd->v2d.cur.ymin=  0.0;
	sd->v2d.cur.xmax= (float)sa->winx;
	sd->v2d.cur.ymax= (float)sa->winy;
 
	sd->v2d.min[0]= 1.0;
	sd->v2d.min[1]= 1.0;
 
	sd->v2d.max[0]= 32000.0f;
	sd->v2d.max[1]= 32000.0f;
 
	sd->v2d.minzoom= 0.5f;
	sd->v2d.maxzoom= 1.21f;
 
	sd->v2d.scroll= 0;
	sd->v2d.keepaspect= 1;
	sd->v2d.keepzoom= 1;
	sd->v2d.keeptot= 0;	
}

Note: the two callbacks drawdemospace(), winqreaddemospace() we defined in the first steps are made public to the UI here. I am not sure why these are not stored as a prototype in a header file. But as you can see nearly all defined spaces are going this way.


The definition of init_demospace() should happen before the function newspace() is defined. Otherwise we would need a prototype for init_demospace().

newspace() triggers the initialization of the different spaces. It's obvious that we have to add our space there as well if we want to have it initialized.

void newspace(ScrArea *sa, int type)
{
	...
		else if(type==SPACE_NODE)
			init_nodespace(sa);
		else if(type==SPACE_DEMO)
			init_demospace(sa);
 
		sl= sa->spacedata.first;
		sl->area= sa;
	...
}


Serialization of DemoSpace

To enable serialization of our new DemoSpace Data structure, we need to embed the following in write_screens() of blenloader/intern/writefile.c:

static void write_screens(WriteData *wd, ListBase *scrbase)
{
	...
				else if(sl->spacetype==SPACE_NODE){
					writestruct(wd, DATA, "SpaceNode", 1, sl);
				}
				else if(sl->spacetype==SPACE_DEMO){
					writestruct(wd, DATA, "SpaceDemo", 1, sl);
				}
				sl= sl->next;
			}
 
			sa= sa->next;
		}
 
		sc= sc->id.next;
	}
}


Binding the new Space Window

This is the point where we start to bind the the blender UI and the newly created source code of our space window. In src/header_demo.c we added a function called demo_buttons() this function contains the implementation for drawing the header content. To get the drawing triggered by the UI we embed the function call in scrarea_do_headdraw() of src/editscreen.c. We need to store the prototype of demo_buttons() in include/BSE_headerbuttons.h.

void scrarea_do_headdraw(ScrArea *area)
{
	...
		switch(area->spacetype) {
		case SPACE_DEMO:	demo_buttons(area);	break;
	...
 
}


In the same file we need to modify the function areawinset() like following:

void areawinset(short win)
{
	...
		switch(curarea->spacetype) {
		case SPACE_DEMO:
		{
			SpaceDemo *sdemo= curarea->spacedata.first;
			G.v2d= &sdemo->v2d;
			break;
		}
	...
}

In areawinset() the current drawing area is set which ensures to draw in the right space window.


Who is Who

In some situations the blender UI needs to get knowledge of the space type. The function spacetype_from_area() in src/spacetypes.c provides this knowledge.

We need to adapt it like follows:

static SpaceType *spacetype_from_area(ScrArea *area)
{
	switch (area->spacetype) {
	...
	case SPACE_NODE:	return spacenode_get_type();
	case SPACE_DEMO:	return spacedemo_get_type();
	default:
		newspace(area, SPACE_VIEW3D);
		return spaceview3d_get_type();
		return NULL;
	}
}

The function spacedemo_get_type() does not exist yet. Therefore we need to add this function for our space. We can do this by copy and paste from an existing space and adapt it for our needs. The following code is added to src/space.c (the according prototype should be placed in include/BIF_spacetypes.h).

SpaceType *spacedemo_get_type(void)
{
	static SpaceType *st= NULL;
 
	if (!st) {
		st= spacetype_new("Demo");
		spacetype_set_winfuncs(st, NULL, drawdemospace, changeview2dspace, winqreaddemospace);
	}
 
	return st;
}

In spacedemo_get_type() the two callback function drawdemospace() and winqreaddemospace() are bound to the current space type.


Touched or created Files

Space Creation:

  • src/header_demo.c (new)
  • src/editdemo.c (new)
  • src/drawdemo.c (new)


Space Binding:

  • src/editscreen.c
  • src/headerbuttons.c
  • src/spacetypes.c
  • src/space.c
  • include/BIF_spacetypes.h
  • include/BSE_headerbuttons.h
  • blenloader/intern/writefile.c
  • makesdna/DNA_space_types.h