diff --git a/Extras/readblend/LICENSE.txt b/Extras/readblend/LICENSE.txt new file mode 100644 index 000000000..07b85e91c --- /dev/null +++ b/Extras/readblend/LICENSE.txt @@ -0,0 +1,24 @@ + +Copyright (C) 2003-2006 Adam D. Moss (the "Author"). All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the Author of the +Software shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written authorization +from the Author. diff --git a/Extras/readblend/Makefile b/Extras/readblend/Makefile new file mode 100644 index 000000000..bf2358e51 --- /dev/null +++ b/Extras/readblend/Makefile @@ -0,0 +1,36 @@ + +CC = gcc +COPTS = -O2 -Wall + +#CC = gcc31 +#COPTS = -O2 -Wall -ggdb -lm + +#COPTS = -O3 -g -fno-inline-functions -ffast-math -pg -Wall +#COPTS = -ggdb -Wall + +CFLAGS = $(COPTS) $(INCS) -lm + +EXEC = testblend + +OBJ_SRCS = testblend.c readblend.c +OBJ_OBJS = $(OBJ_SRCS:.c=.o) + +%.o: %.c + ${CC} ${CFLAGS} -c $< -o $@ + +all: $(EXEC) + +clean: + rm -f $(EXEC) *.o gmon.out + +#dist: +# tar -hzvcf flynn-`cat VERSION`.tar.gz `cat MANIFEST` +# ls -la flynn-`cat VERSION`.tar.gz + +#testdist: +# tar -zvcf flynn-testdir.tar.gz test +# ls -la flynn-testdir.tar.gz + +$(EXEC): $(OBJ_OBJS) + $(CC) $(CFLAGS) $^ -o $@ + diff --git a/Extras/readblend/README.blendstruct b/Extras/readblend/README.blendstruct new file mode 100644 index 000000000..597bb787f --- /dev/null +++ b/Extras/readblend/README.blendstruct @@ -0,0 +1,26 @@ +ReadBlend, a data extraction API for Blender's .blend files + +quick notes on the logical .blend file format as presented +by readblend: + + +BLENDFILE + | + |--BLENDFILE_VERSION + | + |--NUM_BLOCKS ... number of blocks in the file + |--BLOCK[NUM_BLOCKS] ... array of blocks + | + |--BLOCK_TAG ... general 'DATA', otherwise specialized type + | + |--OBJECT_TYPE ... the block is an array of objects of this type + |--OBJECT_COUNT ... this is the number of objects in the block + |--OBJECT[OBJECT_COUNT] ... array of objects + | + |--OBJECT + an OBJECT... + = ATOMIC type (uchar, float, etc) + or + = STRUCTURE (array of assorted OBJECTs, nested) + or + = POINTER (a reference to another BLOCK) diff --git a/Extras/readblend/ReadBlend.sln b/Extras/readblend/ReadBlend.sln new file mode 100644 index 000000000..fe04c7d28 --- /dev/null +++ b/Extras/readblend/ReadBlend.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReadBlend", "ReadBlend.vcproj", "{74CA6BF4-60C4-4FE6-BAB5-31F31FA2A0B3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {74CA6BF4-60C4-4FE6-BAB5-31F31FA2A0B3}.Debug|Win32.ActiveCfg = Debug|Win32 + {74CA6BF4-60C4-4FE6-BAB5-31F31FA2A0B3}.Debug|Win32.Build.0 = Debug|Win32 + {74CA6BF4-60C4-4FE6-BAB5-31F31FA2A0B3}.Release|Win32.ActiveCfg = Release|Win32 + {74CA6BF4-60C4-4FE6-BAB5-31F31FA2A0B3}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Extras/readblend/ReadBlend.vcproj b/Extras/readblend/ReadBlend.vcproj new file mode 100644 index 000000000..fbb49b582 --- /dev/null +++ b/Extras/readblend/ReadBlend.vcproj @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Extras/readblend/abs-file.h b/Extras/readblend/abs-file.h new file mode 100644 index 000000000..1f0e89f7b --- /dev/null +++ b/Extras/readblend/abs-file.h @@ -0,0 +1,226 @@ +/* + * stdio/physfs abstraction layer 2004-02-06 + * + * Adam D. Moss + * + * These wrapper macros and functions are designed to allow a program + * to perform file I/O with identical semantics and syntax regardless + * of whether PhysicsFS is being used or not. + */ +#ifndef _ABS_FILE_H +#define _ABS_FILE_H + + typedef unsigned char uint8_t; + typedef unsigned long int uint64_t; + typedef unsigned int uint32_t; + typedef int int32_t; + typedef unsigned short uint16_t; + typedef short int16_t; + + +/* +PLEASE NOTE: This license applies to abs-file.h ONLY; The version of +PhysicsFS itself which you are using may have been released under a +license with additional restrictions. + +Copyright (C) 2002-2004 Adam D. Moss (the "Author"). All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the Author of the +Software shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written authorization +from the Author. +*/ + +#include +#include + +/* + * API: + * + * Macro/function use like stdio equivalent... + * -------------- ---------------------------- + * MY_FILETYPE FILE + * MY_OPEN_FOR_READ fopen(..., "rb") + * MY_READ fread(...) + * MY_GETC fgetc(...) + * MY_GETS fgets(...) + * MY_OPEN_FOR_WRITE fopen(..., "wb") + * MY_WRITE fwrite(...) + * MY_PUTC fputc(...) + * MY_PUTS fputs(...) + * MY_CLOSE fclose(...) + * MY_ATEOF feof(...) + * MY_TELL ftell(...) + * MY_SEEK fseek(..., SEEK_SET) + * MY_REWIND rewind(...) + * MY_SETBUFFER (not a standard for stdio, does nothing there) + * MY_FILELENGTH (not a standard for stdio, but implemented anyway) + */ + +/* + * Important DEFINEs: + * + * It is important to define these consistantly across the various + * compilation modules of your program if you wish to exchange file + * handles between them. + * + * USE_PHYSFS: Define USE_PHYSFS if PhysicsFS is being used; note that if + * you do intend to use PhysicsFS then you will still need to initialize + * PhysicsFS yourself and set up its search-paths. + * + * Optional DEFINEs: + * + * PHYSFS_DEFAULT_BUFFER_SIZE : If set then abs-file.h sets the + * PhysicsFS buffer size to this value whenever you open a file. You + * may over-ride this on a per-filehandle basis by using the + * MY_SETBUFFER() macro (which simply does nothing when not using + * PhysicsFS). If you have not defined this value explicitly then + * abs-file.h will default to the same default buffer size as used by + * stdio if it can be determined, or 8192 bytes otherwise. + */ + +#ifndef PHYSFS_DEFAULT_BUFFER_SIZE +#ifdef BUFSIZ +#define PHYSFS_DEFAULT_BUFFER_SIZE BUFSIZ +#else +#define PHYSFS_DEFAULT_BUFFER_SIZE 8192 +#endif +#endif + +#ifdef USE_PHYSFS + +#include +#define MY_FILETYPE PHYSFS_file +#define MY_SETBUFFER(fp,size) PHYSFS_setBuffer(fp,size) +#define MY_READ(p,s,n,fp) PHYSFS_read(fp,p,s,n) +#define MY_WRITE(p,s,n,fp) PHYSFS_write(fp,p,s,n) +#if PHYSFS_DEFAULT_BUFFER_SIZE +static MY_FILETYPE* MY_OPEN_FOR_READ(const char *const filename) +{ + MY_FILETYPE *const file = PHYSFS_openRead(filename); + if (file) { + MY_SETBUFFER(file, PHYSFS_DEFAULT_BUFFER_SIZE); + } + return file; +} +static MY_FILETYPE* MY_OPEN_FOR_WRITE(const char *const filename) +{ + MY_FILETYPE *const file = PHYSFS_openWrite(filename); + if (file) { + MY_SETBUFFER(file, PHYSFS_DEFAULT_BUFFER_SIZE); + } + return file; +} +#else +#define MY_OPEN_FOR_READ(fn) PHYSFS_openRead(fn) +#define MY_OPEN_FOR_WRITE(fn) PHYSFS_openWrite(fn) +#endif +static int MY_GETC(MY_FILETYPE *const fp) { + unsigned char c; + /*if (PHYSFS_eof(fp)) { + return EOF; + } + MY_READ(&c, 1, 1, fp);*/ + if (MY_READ(&c, 1, 1, fp) != 1) { + return EOF; + } + return c; +} +static char * MY_GETS(char * const str, const int size, + MY_FILETYPE *const fp) { + int i = 0; + int c; + do { + if (i == size-1) { + break; + } + c = MY_GETC(fp); + if (c == EOF) { + break; + } + str[i++] = c; + } while (c != '\0' && + c != -1 && + c != '\n'); + str[i] = '\0'; + if (i == 0) { + return NULL; + } + return str; +} +static int MY_PUTC(int c, MY_FILETYPE *const fp) { + unsigned char cc = (unsigned char)c; + if (MY_WRITE(&cc, 1, 1, fp) != 1) { + return EOF; + } + return (int)cc; +} +static int MY_PUTS(const char *s, MY_FILETYPE *const fp) { + int i = 0; + while (s[i] != '\0') { + if (MY_PUTC(s[i], fp) == EOF) { + return EOF; + } + ++i; + } + if (MY_PUTC('\n', fp) == EOF) { + return EOF; + } + return 0; +} +#define MY_CLOSE(fp) (0==PHYSFS_close(fp)) +#define MY_ATEOF(fp) PHYSFS_eof(fp) +#define MY_TELL(fp) PHYSFS_tell(fp) +#define MY_SEEK(fp,o) (0==PHYSFS_seek(fp,o)) +#define MY_REWIND(fp) MY_SEEK(fp,0) +#define MY_FILELENGTH(fp) PHYSFS_fileLength(fp) + +#else /* !USE_PHYSFS */ + +#define MY_FILETYPE FILE +#define MY_READ(p,s,n,fp) fread(p,s,n,fp) +#define MY_WRITE(p,s,n,fp) fwrite(p,s,n,fp) +#define MY_OPEN_FOR_READ(n) fopen(n, "rb") +#define MY_OPEN_FOR_WRITE(n) fopen(n, "wb") +#define MY_GETC(fp) fgetc(fp) +#define MY_GETS(str,size,fp) fgets(str,size,fp) +#define MY_PUTC(c,fp) fputc(c,fp) +#define MY_PUTS(str,fp) fput(str,fp) +#define MY_CLOSE(fp) fclose(fp) +#define MY_ATEOF(fp) feof(fp) +#define MY_TELL(fp) ftell(fp) +#define MY_SEEK(fp,o) fseek(fp,o, SEEK_SET) +#define MY_REWIND(fp) rewind(fp) +#define MY_SETBUFFER(fp,size) +/* a TODO from ryan: "seeking to the end followed by an ftell() is probably +more portable, but you can also use fileno() to get a handle from a +(FILE *) to use with fstat()...on supported platforms, that is, most +platforms, this is probably faster." */ +static long MY_FILELENGTH(FILE *fp) { + long currentpos = ftell(fp); /* save current cursor position */ + long newpos; + fseek(fp, 0, SEEK_END); /* seek to end */ + newpos = ftell(fp); /* find position of end -- this is the length */ + fseek(fp, currentpos, SEEK_SET); /* restore previous cursor position */ + return newpos; +} +#endif /* USE_PHYSFS */ + +#endif /* _ABS_FILE_H */ diff --git a/Extras/readblend/readblend.c b/Extras/readblend/readblend.c new file mode 100644 index 000000000..9a229d7cb --- /dev/null +++ b/Extras/readblend/readblend.c @@ -0,0 +1,2397 @@ + +#include +//#include +#include +#include + +#include "abs-file.h" + + +#define B_DEBUG + + +#ifdef B_DEBUG +# ifndef dprintf +# include +# define dprintf fprintf +# endif +#else +# define dprintf +#endif + + +/* the types extracted from the Blender file */ +typedef struct _BlendType BlendType; +struct _BlendType { + char* name; + int size; + + int is_struct; + + /* if is_struct... this defines the types of each of the structure fields */ + int fieldtypes_count; + int* fieldtypes; /* type indices */ + int fieldnames_count; + int* fieldnames; /* name indices */ +}; + + +typedef struct _BlendField BlendField; +struct _BlendField { + char* field_bytes; + int field_bytes_count; + + /* the offset into field_bytes at which each field of a + structure begins. this is so we can keep the data aligned + correctly for various architectures. a non-structure type + is equivalent to a structure with a single field. + */ + int* field_offsets; + int field_offsets_count; +}; + + +typedef struct _BlendBlock BlendBlock; +struct _BlendBlock { + char tag[5]; + uint32_t blender_pointer; + /*void* fixed_pointer;*/ + + int type_index; + /* a block is simply an array of its type as defined by type_index. + array_entries is an array of pointers to each entry in the block's + array. + */ + BlendField *array_entries; + int array_entries_count; +}; + + +/* the opaque BlendFile structure */ +typedef struct { + BlendType* types; + int types_count; + + char* *names; + int names_count; + + int* strc_indices; + int strc_indices_count; + + BlendBlock *blocks; + int blocks_count; + + int name_undef; /* index of the name we add specially for top blocks */ + +} IBlendFile; + + +#define BlendFile IBlendFile +#include "readblend.h" + + +/* endianness conversion macros */ +#define BGETLEUINT16(p) ((uint16_t) ( \ + ( ((uint16_t)*( 1 + (uint8_t*)(p) ))<<8 ) | \ + ( ((uint16_t)*( 0 + (uint8_t*)(p) ))<<0 ) \ + ) ) +#define BGETLEINT16(p) ((int16_t) BGETLEUINT16(p)) +#define BGETLEUINT32(p) ((uint32_t) ( \ + ( ((uint32_t)*( 3 + (uint8_t*)(p) ))<<24 ) | \ + ( ((uint32_t)*( 2 + (uint8_t*)(p) ))<<16 ) | \ + ( ((uint32_t)*( 1 + (uint8_t*)(p) ))<<8 ) | \ + ( ((uint32_t)*( 0 + (uint8_t*)(p) ))<<0 ) \ + ) ) +#define BGETLEINT32(p) ((int32_t) BGETLEUINT32(p)) +#define BGETLEUINT64(p) ((uint64_t) ( \ + ( ((uint64_t)*( 7 + (uint8_t*)(p) ))<<56 ) | \ + ( ((uint64_t)*( 6 + (uint8_t*)(p) ))<<48 ) | \ + ( ((uint64_t)*( 5 + (uint8_t*)(p) ))<<40 ) | \ + ( ((uint64_t)*( 4 + (uint8_t*)(p) ))<<32 ) | \ + ( ((uint64_t)*( 3 + (uint8_t*)(p) ))<<24 ) | \ + ( ((uint64_t)*( 2 + (uint8_t*)(p) ))<<16 ) | \ + ( ((uint64_t)*( 1 + (uint8_t*)(p) ))<<8 ) | \ + ( ((uint64_t)*( 0 + (uint8_t*)(p) ))<<0 ) \ + ) ) +#define BGETLEINT64(p) ((int64_t) BGETLEUINT64(p)) +static float BGETLEFLOAT32(char *p) { + union { /* use type-punning, or aliasing optimization will kick our arse */ + uint32_t u32; + float f32; + } punner; + punner.u32 = BGETLEUINT32(p); + return punner.f32; +} +static double BGETLEDOUBLE64(char *p) { + union { /* use type-punning, or aliasing optimization will kick our arse */ + uint64_t u64; + float f64; + } punner; + punner.u64 = BGETLEUINT64(p); + return punner.f64; +} + + +static BlendFile* +bf_new(void) +{ + BlendFile *rtn = malloc(sizeof(BlendFile)); + if (!rtn) { + dprintf(stderr, "out of mem making bf handle\n"); + return NULL; + } + rtn->types = NULL; + rtn->types_count = 0; + rtn->names = NULL; + rtn->names_count = 0; + rtn->blocks = NULL; + rtn->blocks_count = 0; + rtn->strc_indices = NULL; + rtn->strc_indices_count = 0; + rtn->name_undef = -1; + return rtn; +} + + +static long +seek_past_string(MY_FILETYPE* file, const char *str) { + const int match_max = strlen(str); + int match_now = 0; + + do { + const char c = MY_GETC(file); + if (c == str[match_now]) { + ++match_now; + } else { + match_now = 0; + } + } while(match_now < match_max && + !MY_ATEOF(file)); + + if (MY_ATEOF(file)) { + return -1; + } + + return MY_TELL(file) - match_max; +} + +#define EXPANDO_MULTIPLE(ARRAY,ADDITIONPTR,NUMBER) \ +do { \ + (ARRAY) = realloc((ARRAY), sizeof((ARRAY)[0]) * (NUMBER +(ARRAY##_count))); \ + memcpy(&(ARRAY)[ARRAY##_count], ADDITIONPTR, sizeof((ARRAY)[0]) * NUMBER); \ + (ARRAY##_count) += NUMBER; \ +} while(0) + +#define EXPANDO(ARRAY,ADDITION) \ +do { \ + (ARRAY) = realloc((ARRAY), sizeof((ARRAY)[0]) * (1 + (ARRAY##_count))); \ + (ARRAY)[ARRAY##_count] = (ADDITION); \ + ++(ARRAY##_count); \ +} while(0) + + +static unsigned short +read_ushort(MY_FILETYPE* file) { + unsigned char c[2]; + if (MY_READ(c, 2, 1, file) == 1) { + return c[0] | (c[1]<<8); + } else { + return 0xFFFF; + } +} + + +static long +read_long(MY_FILETYPE* file) { + unsigned char c[4]; + if (MY_READ(c, 4, 1, file) == 1) { + return ((unsigned long)c[0] | (c[1]<<8) | (c[2]<<16) | (c[3]<<24)); + } else { + return 0xFFFFFFFF; + } +} + + +static unsigned long +read_ulong(MY_FILETYPE* file) { + unsigned char c[4]; + if (MY_READ(c, 4, 1, file) == 1) { + return c[0] | (c[1]<<8) | (c[2]<<16) | (c[3]<<24); + } else { + return 0xFFFFFFFF; + } +} + + +static int +name_is_pointer(char* name) { + int len = strlen(name); + /*fprintf(stderr,"[%s]",name);*/ + if (len >= 1) { + if (name[0] == '*') + return 1; + } + if (len >= 2) { + if (name[1] == '*') + return 1; + } + return 0; +} + + +/* note: we only deal with 1d or 2d arrays at the moment. haven't + seen any arrays of a higher order from Blender yet. */ +static int +name_is_array(char* name, int* dim1, int* dim2) { + int len = strlen(name); + /*fprintf(stderr,"[%s]",name);*/ + /*if (len >= 1) { + if (name[len-1] != ']') + return 1; + } + return 0;*/ + char *bp; + int num; + if (dim1) { + *dim1 = 1; + } + if (dim2) { + *dim2 = 1; + } + bp = strchr(name, '['); + if (!bp) { + return 0; + } + num = 0; + while (++bp < name+len-1) { + const char c = *bp; + if (c == ']') { + break; + } + if (c <= '9' && c >= '0') { + num *= 10; + num += (c - '0'); + } else { + dprintf(stderr, "array parse error.\n"); + return 0; + } + } + if (dim2) { + *dim2 = num; + } + + /* find second dim, if any. */ + bp = strchr(bp, '['); + if (!bp) { + return 1; /* at least we got the first dim. */ + } + num = 0; + while (++bp < name+len-1) { + const char c = *bp; + if (c == ']') { + break; + } + if (c <= '9' && c >= '0') { + num *= 10; + num += (c - '0'); + } else { + dprintf(stderr, "array2 parse error.\n"); + return 1; + } + } + if (dim1) { + if (dim2) { + *dim1 = *dim2; + *dim2 = num; + } else { + *dim1 = num; + } + } + + return 1; +} + + +#define SIZE_ROUNDUP(SIZE) (((SIZE) + sizeof(int) - 1) & ~(sizeof(int) - 1)) + + +static void +recursively_read_type(MY_FILETYPE* file, BlendFile* bf, + unsigned long section_type, + BlendField* field) +{ + char *new_data = NULL; + int new_data_size = 0; + int dim1, dim2; + + if (bf->types[section_type].is_struct) { + int i; + /*fprintf(stderr, "type%d(%s)is_struct(%d) ", section_type, bf->types[section_type].name, bf->types[section_type].size);*/ + for (i=0; itypes[section_type].fieldtypes_count; ++i) { + /*fprintf(stderr, "@%d ", i);*/ + int j,k; +#if 0 + if (name_is_array(bf->names[bf->types[section_type].fieldnames[i]], + &j,&k) || 1) { + dprintf(stderr, " %s/%s=[%d][%d] ", + bf->types[bf->types[section_type].fieldtypes[i]].name, + bf->names[bf->types[section_type].fieldnames[i]], j, k); + } +#endif + if (name_is_pointer(bf->names[bf->types[section_type].fieldnames[i]])) { + /*fprintf(stderr, "*(4) ");*/ + name_is_array(bf->names[bf->types[section_type].fieldnames[i]], + &dim1,&dim2); + new_data_size = SIZE_ROUNDUP(4); + new_data = malloc(new_data_size); + /*fprintf(stderr, " ");*/ + for (j=0; jfield_offsets, field->field_bytes_count); + EXPANDO_MULTIPLE(field->field_bytes, new_data, new_data_size); + /*fprintf(stderr, "N*(%d) ", new_data_size);*/ + } + } + free(new_data); + } else { + name_is_array(bf->names[bf->types[section_type].fieldnames[i]], + &dim1,&dim2); + /*fprintf(stderr, " ");*/ + for (j=0; jtypes[section_type].fieldtypes[i], + field); + } + } + } + } + } else { + /*fprintf(stderr, "type%d(%s)plain(%d) ", section_type, bf->types[section_type].name, bf->types[section_type].size); */ + new_data_size = SIZE_ROUNDUP(bf->types[section_type].size); + /*fprintf(stderr, "%d... ", bf->types[section_type].size); + if (bf->types[section_type].size > 4) { + fprintf(stderr, "%d ", field->field_bytes_count); + }*/ + if (new_data_size) { + new_data = malloc(new_data_size); + MY_READ(new_data, 1, bf->types[section_type].size, file); + EXPANDO(field->field_offsets, field->field_bytes_count); + EXPANDO_MULTIPLE(field->field_bytes, new_data, new_data_size); + /*fprintf(stderr, "ND(%d) ", new_data_size); */ + free(new_data); + } else { + dprintf(stderr, " ", + bf->types[section_type].size); + } + } + +} + + +static BlendField +read_type(MY_FILETYPE* file, BlendFile* bf, + unsigned long section_type) +{ + BlendField rtn; + + rtn.field_bytes = NULL; + rtn.field_bytes_count = 0; + rtn.field_offsets = NULL; + rtn.field_offsets_count = 0; + + recursively_read_type(file, bf, section_type, &rtn); + + return rtn; +} + + +static int +blend_read_data(MY_FILETYPE* file, BlendFile* bf) +{ + long next_block_start = 12; + int finished_extracting = 0; + char section_name[5] = {0,0, 0,0, 0}; + + /* slurp up the whole file block by block! */ + + do { + unsigned long section_size; + unsigned long section_pointer; + unsigned long section_type; + unsigned long section_ents; + + MY_SEEK(file, next_block_start); + + MY_READ(section_name, 4, 1, file); + + if (strcmp(section_name, "DNA1") != 0) { + int i; + BlendBlock block; + + section_size = read_ulong(file); + section_pointer = read_ulong(file); + section_type = read_ulong(file); + section_type = bf->strc_indices[section_type]; + section_ents = read_ulong(file); + + memcpy(block.tag, section_name, 4); + block.tag[4] = '\0'; + block.type_index = section_type; + block.blender_pointer = section_pointer; + /*block.fixed_pointer = NULL;*/ + block.array_entries = NULL; + block.array_entries_count = 0; + + /*dprintf(stderr, "\nsizeof(%s)=%ld: %s[%ld]\n", section_name, section_size, bf->types[section_type].name, section_ents); */ + + for (i=0; iblocks, block); + + next_block_start += 4+4+4+4+4 + section_size; + +#ifdef B_DEBUG + if (MY_TELL(file) > next_block_start) { + dprintf(stderr, " **OVER-READ(%ld,%ld)** ", + MY_TELL(file), next_block_start); + if (strcmp(bf->types[section_type].name, "Link") == 0 && + MY_TELL(file) - next_block_start == 4) { + dprintf(stderr, "<- don't panic, known Link struct weirdness."); + } else { + dprintf(stderr, "<- okay, PANIC!"); + } + dprintf(stderr, "\n"); + } else if (MY_TELL(file) < next_block_start) { + /*dprintf(stderr, " **under-READ(%ld,%ld)** ", + MY_TELL(file), next_block_start);*/ + } else { + /*dprintf(stderr, " :) ");*/ + } +#endif + + } else { + finished_extracting = 1; + } + + } while (!finished_extracting); + + return 1; +} + + +BlendFile* +blend_read(MY_FILETYPE* file) +{ + char blender_mark[8] = {0,0,0,0, 0,0,0,0}; + BlendFile *bf; + long sdnaname_offs, type_offs, tlen_offs, strc_offs, endb_offs; + long sdnaname_size, type_size, tlen_size, strc_size; + long sdnaname_ents, type_ents, tlen_ents, strc_ents; + + MY_REWIND(file); + + /* Check file signature */ + + MY_READ(blender_mark, 1, 7, file); + if (strcmp(blender_mark, "BLENDER") != 0) { + dprintf(stderr, "Not a Blender file.\n"); + return NULL; + } + + /* Alloc a handle to return */ + + bf = bf_new(); + + /* Scan the whole file (!) for file section markers */ + + sdnaname_offs = seek_past_string(file, "SDNANAME"); + sdnaname_ents = read_long(file); + type_offs = seek_past_string(file, "TYPE"); + type_ents = read_long(file); + tlen_offs = seek_past_string(file, "TLEN"); + tlen_ents = type_ents; + strc_offs = seek_past_string(file, "STRC"); + strc_ents = read_long(file); + endb_offs = seek_past_string(file, "ENDB"); + + if (sdnaname_offs == -1 || type_offs == -1 || tlen_offs == -1 || + strc_offs == -1 || endb_offs == -1) { + dprintf(stderr, "Couldn't find all necessary file markers. :(\n"); + return NULL; + } + + /* Move marker offsets to point to the start of each one's block */ + + sdnaname_offs += 8 + 4; + sdnaname_size = type_offs - sdnaname_offs; + + type_offs += 4 + 4; + type_size = tlen_offs - type_offs; + + tlen_offs += 4; + tlen_size = strc_offs - tlen_offs; + + strc_offs += 4 + 4; + strc_size = endb_offs - strc_offs; + + /* read the NAME table */ + + MY_SEEK(file, sdnaname_offs); + { + long offs = 0; + int i; + char *top_block_name; + + for (i=0; inames, this_name_chars); + } + + /* our top-block name */ +#define BLEND_TOP_BLOCK_NAME "READBLEND_TOP_BLOCK" + top_block_name = calloc(1, 1+strlen(BLEND_TOP_BLOCK_NAME)); + strcpy(top_block_name, BLEND_TOP_BLOCK_NAME); + bf->name_undef = bf->names_count; + EXPANDO(bf->names, top_block_name); + } + + /* read the TYPE table */ + + MY_SEEK(file, type_offs); + { + long offs = 0; + int i; + + for (i=0; itypes, bt); + } + } + + /* read the TLEN table */ + + MY_SEEK(file, tlen_offs); + { + int i; + for (i=0; itypes_count; ++i) { + unsigned short len = read_ushort(file); + bf->types[i].size = len; + /*fprintf(stderr, "sizeof(%s)=%d ", bf->types[i].name, len); */ + } + } + + /* Read the STRC table */ + + MY_SEEK(file, strc_offs); + { + int i,j; + for (i=0; itypes[struc_type_index].is_struct = 1; + EXPANDO(bf->strc_indices, struc_type_index); + /*dprintf(stderr, "\n%s: ", bf->types[struc_type_index].name); */ + for (j=0; jtypes[struc_type_index].fieldtypes, ftype); + EXPANDO(bf->types[struc_type_index].fieldnames, fname); + /*dprintf(stderr, "%s %s , ", bf->types[ftype].name, bf->names[fname]); */ + } + } + } + + blend_read_data(file, bf); + + /* Return the new handle */ + + return bf; +} + + +static void free_btype_inner(BlendType *btype) +{ + if (btype->name) { + free(btype->name); + } else { + dprintf(stderr, "null typename.\n"); + } + + if (btype->fieldtypes) { + free(btype->fieldtypes); + } + + if (btype->fieldnames) { + free(btype->fieldnames); + } +} + + +static void free_bfield_inner(BlendField *bfield) +{ + if (bfield->field_bytes) { + free(bfield->field_bytes); + } + + if (bfield->field_offsets) { + free(bfield->field_offsets); + } +} + + +static void free_bblock_inner(BlendBlock *bblock) +{ + int i; + + for (i=0; iarray_entries_count; ++i) { + free_bfield_inner(&bblock->array_entries[i]); + } + free(bblock->array_entries); +} + + +void +blend_free(BlendFile* blend_file) +{ + int i; + + for (i=0; itypes_count; ++i) { + free_btype_inner(&blend_file->types[i]); + } + if (blend_file->types) { + free(blend_file->types); + } + + for (i=0; inames_count; ++i) { + if (blend_file->names[i]) { + free(blend_file->names[i]); + } else { + dprintf(stderr, "null name.\n"); + } + } + if (blend_file->names) { + free(blend_file->names); + } + + for (i=0; iblocks_count; ++i) { + free_bblock_inner(&blend_file->blocks[i]); + } + if (blend_file->blocks) { + free(blend_file->blocks); + } + + if (blend_file->strc_indices) { + free(blend_file->strc_indices); + } + + free(blend_file); +} + + +/******************************************************************** + * done with the reading/parsing logic; now for the querying logic * + ********************************************************************/ + + + +/******************************************************************** + * LOW-LEVEL * + ********************************************************************/ + + + +const char* +blend_block_get_tagname(BlendFile* blend_file, + BlendBlockPointer block) +{ + const BlendBlock *const bb = block; + return bb->tag; +} + + +const char* +blend_block_get_typename(BlendFile* blend_file, + BlendBlockPointer block) +{ + const BlendBlock *const bb = block; + return blend_file->types[bb->type_index].name; +} + + +int +blend_block_get_entry_count(BlendFile* blend_file, + BlendBlockPointer block) +{ + const BlendBlock *const bb = block; + return bb->array_entries_count;; +} + + +void +blend_foreach_block(BlendFile* blend_file, + BlendBlockCallback* func, + void* userdata) +{ + int i; + for (i=0; iblocks_count; ++i) { + if (!func(&blend_file->blocks[i], blend_file, userdata)) return; + } +} + + +static int +blend_type_basename_compare(const char *fancy, const char *raw) { + const int flen = strlen(fancy); + const int rlen = strlen(raw); + int i, strcmp_result = 123; + + i = 0; + while (i < flen && (fancy[i]=='*' || fancy[i]=='(')) { + ++i; + } + + strcmp_result = strncmp(&fancy[i], raw, rlen); + + if (strcmp_result == 0 && flen > rlen+i) { + i = rlen + i; + if (fancy[i] != ')' && fancy[i] != '(' && fancy[i] != '[') { + strcmp_result = -1; + } + } + + return strcmp_result; +} + + +static BlendObjType +typestring_to_blendobj_type(BlendFile* blend_file, + const char* type_name) { + if (blend_type_basename_compare(type_name, "char") == 0) { + return BLEND_OBJ_CHAR8; + } else if (blend_type_basename_compare(type_name, "uchar") == 0) { + return BLEND_OBJ_UCHAR8; + } else if (blend_type_basename_compare(type_name, "short") == 0) { + return BLEND_OBJ_SHORT16; + } else if (blend_type_basename_compare(type_name, "ushort") == 0) { + return BLEND_OBJ_USHORT16; + } else if (blend_type_basename_compare(type_name, "int") == 0) { + return BLEND_OBJ_LONG32; + } else if (blend_type_basename_compare(type_name, "long") == 0) { + return BLEND_OBJ_LONG32; + } else if (blend_type_basename_compare(type_name, "ulong") == 0) { + return BLEND_OBJ_ULONG32; + } else if (blend_type_basename_compare(type_name, "float") == 0) { + return BLEND_OBJ_FLOAT; + } else if (blend_type_basename_compare(type_name, "double") == 0) { + return BLEND_OBJ_DOUBLE; + } else if (blend_type_basename_compare(type_name, "void") == 0) { + return BLEND_OBJ_OPAQUE; + } else { + return BLEND_OBJ_STRUCT; /* structure */ + } +} + + +static BlendObjType +typelong_to_blendobj_type(BlendFile* blend_file, + long btype, long bname) +{ + if (name_is_pointer(blend_file->names[bname])) { + return BLEND_OBJ_POINTER; + } else if (blend_file->types[btype].is_struct) { + return BLEND_OBJ_STRUCT; + } else { + return typestring_to_blendobj_type(blend_file, + blend_file->types[btype].name); + } +} + + +BlendObjType +blend_object_type(BlendFile* blend_file, + BlendObject obj) { + return typelong_to_blendobj_type(blend_file, + obj.type, + obj.name); +} + + +BlendBlockPointer +blend_block_from_blendpointer(BlendFile *blend_file, + uint32_t blendpointer) +{ + int i; + + /* fprintf(stderr, "%04x: ", blendpointer);*/ + + if (blendpointer != 0) { + for (i=0; iblocks_count; ++i) { + /*fprintf(stderr, "%04x? ", blend_file->blocks[i].blender_pointer); */ + if (blend_file->blocks[i].blender_pointer == blendpointer) { + return &blend_file->blocks[i]; + } + } + } + + return NULL; +} + + +static BlendBlockPointer +blend_block_from_object(BlendFile *blend_file, + BlendObject *obj) { + return obj->block; +} + + +int +blend_object_array_getdata(BlendFile* blend_file, + void* dest, BlendObject obj, + int dim_index_1, int dim_index_2) +{ + const char* type_name = blend_file->types[obj.type].name; + const BlendBlock *const bb = obj.block; + BlendField *bf = &bb->array_entries[obj.entry_index]; + void* data; + int dim1, dim2; + + name_is_array(blend_file->names[obj.name], &dim1, &dim2); + /*dprintf(stderr, "copying:'%s'[%d][%d] (of [%d][%d]) ", type_name, dim_index_1, dim_index_2, dim1, dim2);*/ + + if (dim_index_1 >= dim1 || + dim_index_2 >= dim2) { + dprintf(stderr, "Array index (%d,%d) out of bounds for dimensionality [%d][%d]\n", dim_index_1, dim_index_2, dim1, dim2); + return 0; + } + + data = &bf->field_bytes[bf->field_offsets[obj.field_index + + dim2*dim_index_1 + dim_index_2]]; + /*dprintf(stderr, "fi[%d]byteo[%d]", obj.field_index, + bf->field_offsets[obj.field_index + + dim2*dim_index_1 + dim_index_2]);*/ + + if (name_is_pointer(blend_file->names[obj.name])) { + *(BlendBlockPointer*)dest = + blend_block_from_blendpointer(blend_file, + BGETLEUINT32(data)); + return 1; + } + + /* FIXME: might be a good idea to do less-crappy word-size conversions + here -- these might read beyond the end of malloc'd blocks if we + ever change our field-padding policy. There were endian problems + too; these are believed fixed now. */ + /* The signed conversions look strange because they have to sign-expand + negative results without relying on right-shifts which have undefined + behaviour on negative data according to ANSI C. */ + if (blend_type_basename_compare(type_name, "char") == 0) { + *(char*)dest = (*(char*)data) << (8*sizeof(char)-8) / (1<<(8*sizeof(char)-8)); + } else if (blend_type_basename_compare(type_name, "uchar") == 0) { + *(unsigned char*)dest = *(unsigned char*)data; + } else if (blend_type_basename_compare(type_name, "short") == 0) { + *(int16_t*)dest = BGETLEINT16(data) << (8*sizeof(int16_t)-16) / (1<<(8*sizeof(int16_t)-16)); + } else if (blend_type_basename_compare(type_name, "ushort") == 0) { + *(uint16_t*)dest = BGETLEUINT16(data); + } else if (blend_type_basename_compare(type_name, "int") == 0) { + *(int32_t*)dest = BGETLEINT32(data) << (8*sizeof(int32_t)-32) / (1<<(8*sizeof(int32_t)-32)); + } else if (blend_type_basename_compare(type_name, "long") == 0) { + *(int32_t*)dest = BGETLEINT32(data) << (8*sizeof(int32_t)-32) / (1<<(8*sizeof(int32_t)-32)); + } else if (blend_type_basename_compare(type_name, "ulong") == 0) { + *(uint32_t*)dest = BGETLEUINT32(data); + } else if (blend_type_basename_compare(type_name, "float") == 0) { + *(float*)dest = BGETLEFLOAT32(data); + /*fprintf(stderr, "GOT{%f'%f} ", *(float*)dest, *(float*)data);*/ + } else if (blend_type_basename_compare(type_name, "double") == 0) { + *(double*)dest = BGETLEDOUBLE64(data); + } else if (blend_type_basename_compare(type_name, "void") == 0) { + dprintf(stderr, "Tried to fetch a void.\n"); + return 0; + } else { + dprintf(stderr, "Tried to fetch a whole structure.\n"); + return 0; + } + return 1; /* success */ +} + + +int +blend_object_getdata(BlendFile* blend_file, + void* dest, BlendObject obj) +{ + int dim1, dim2; + + if (name_is_array(blend_file->names[obj.name], &dim1, &dim2)) { + if (dim1 != 1 || dim2 != 1) { + dprintf(stderr, "Tried to fetch a whole array.\n"); + return 0; + } + } + + return (blend_object_array_getdata(blend_file, dest, obj, 0, 0)); +} + + +/* recursively count the number of fields and array items in this + structure, for the purposes of skipping in the field offset array */ +static long +get_num_type_segments(BlendFile* blend_file, + BlendObject obj) +{ + int i; + long rtn = 0; + int dim1,dim2; + + name_is_array(blend_file->names[obj.name], + &dim1, &dim2); + + if (name_is_pointer(blend_file->names[obj.name]) || + !blend_file->types[obj.type].is_struct) { + return (1 * dim1 * dim2); + } + + /* fprintf(stderr, "STRUCTYAYYY ");*/ + + for (i=0; itypes[obj.type].fieldnames_count; ++i) { + BlendObject qo = obj; + qo.type = blend_file->types[obj.type].fieldtypes[i]; + qo.name = blend_file->types[obj.type].fieldnames[i]; + qo.field_index = i; + rtn += get_num_type_segments(blend_file, qo) * dim1 * dim2; + } + + return (rtn); +} + + +int +blend_object_structure_getfield(BlendFile* blend_file, + BlendObject *result, + BlendObject obj, + const char* field_name) +{ + if (blend_file->types[obj.type].is_struct) { + int i; + int field_index = 0; + for (i=0; itypes[obj.type].fieldnames_count; ++i) { + if (blend_type_basename_compare( + blend_file->names[blend_file->types[obj.type].fieldnames[i]], + field_name) + == 0) { + result->type = blend_file->types[obj.type].fieldtypes[i]; + result->name = blend_file->types[obj.type].fieldnames[i]; + result->block = obj.block; + result->entry_index = obj.entry_index; + result->field_index = field_index; + return 1; + } + + { + BlendObject qo = obj; + int fos; + qo.type = blend_file->types[obj.type].fieldtypes[i]; + qo.name = blend_file->types[obj.type].fieldnames[i]; + qo.field_index = field_index; + fos = get_num_type_segments(blend_file, qo); + /*fprintf(stderr, ">>%s %s:%d ", + blend_file->types[qo.type].name, + blend_file->names[qo.name], fos);*/ + field_index += fos; + } + } + return 0; + } else { + dprintf(stderr, "Indexed object isn't a structure!\n"); + return 0; + } +} + + +void +blend_object_array_getdims(BlendFile* blend_file, + BlendObject obj, + int* dim1, int* dim2) +{ + name_is_array(blend_file->names[obj.name], + dim1, dim2); +} + + +BlendObject +blend_block_get_object(BlendFile* blend_file, + BlendBlockPointer block, + int entry_index) +{ + BlendObject bo; + const BlendBlock *const bb = block; + /*BlendField *bf = &bb->array_entries[entry_index]; */ + + bo.type = bb->type_index; + bo.name = blend_file->name_undef; + bo.block = block; + bo.entry_index = entry_index; + bo.field_index = 0; + + return bo; +} + + + + +/******************************************************************** + * MID-LEVEL * + ********************************************************************/ + + +/* general helpers */ + + +int +blend_object_getstring(BlendFile* blend_file, + BlendObject obj, + char *dest, int max_chars) +{ + int strpos = 0; + int dim1, dim2; + int rtn = 1; + BlendObjType bo_type; + + name_is_array(blend_file->names[obj.name], + &dim1, &dim2); + + if (dim2 < max_chars) { + max_chars = dim2; + } + + bo_type = blend_object_type(blend_file, obj); + if (! (bo_type==BLEND_OBJ_CHAR8 || bo_type==BLEND_OBJ_UCHAR8)) { + dprintf(stderr, "tried to get string from an object that's not of type uchar/char. (is type %d)\n", bo_type); + rtn = 0; + goto done; + } + + for (strpos=0; strposIDname) { /* don't freak out, but hmm, do we do the right thing? */ + const int strl = strlen(ifd->IDname); + char *obj_string = malloc(3+ strl); + + for (i=0; iIDname) == 0) { + ifd->found = obj; + ifd->success = 1; + want_more = 0; + goto done; + } else { + /* next entry please. */ + } + } + + done:; + free(obj_string); + } + + return want_more; +} + + +int +blend_object_get_by_IDname(BlendFile* blend_file, + BlendObject *result, + const char* IDname) +{ + IDFinderData ifd; + + ifd.success = 0; + ifd.IDname = IDname; + ifd.just_print_it = 0; + + blend_foreach_block(blend_file, block_ID_finder, &ifd); + + if (!ifd.success) { + return 0; + } + + *result = ifd.found; + return 1; +} + + +void +blend_dump_typedefs(BlendFile* bf) +{ + int i; + + /* dump out display of types and their sizes */ + for (i=0; itypes_count; ++i) { + /* if (!bf->types[i].is_struct)*/ + { + printf("%3d: sizeof(%s%s)=%d", + i, + bf->types[i].is_struct ? "struct " : "atomic ", + bf->types[i].name, bf->types[i].size); + if (bf->types[i].is_struct) { + int j; + printf(", %d fields: { ", bf->types[i].fieldtypes_count); + for (j=0; jtypes[i].fieldtypes_count; ++j) { + printf("%s %s", + bf->types[bf->types[i].fieldtypes[j]].name, + bf->names[bf->types[i].fieldnames[j]]); + if (j == bf->types[i].fieldtypes_count-1) { + printf("; }\n"); + } else { + printf("; "); + } + } + } + + } + } +} + + +void +blend_dump_blocks(BlendFile* bf) +{ + int i; + IDFinderData ifd; + + ifd.success = 0; + ifd.IDname = NULL; + ifd.just_print_it = 1; + + for (i=0; iblocks_count; ++i) { + BlendBlock* bb = &bf->blocks[i]; + printf("tag='%s'\tptr=%p\ttype=%s\t[%4d]", + bb->tag, /*bb->blender_pointer,*/ bb, + bf->types[bb->type_index].name, + bb->array_entries_count); + block_ID_finder(bb, bf, &ifd); + printf("\n"); + } +} + + +int +blend_obj_is_rootobject(BlendFile *bf, BlendObject *objobj) { + BlendObject obj; + BlendBlockPointer block; + if ((blend_object_structure_getfield(bf, &obj, *objobj, "parent") && + blend_object_getdata(bf, &block, obj))) { + return (block == NULL); + } + return 0; +} + + +BlendLayerMask +blend_obj_get_layermask(BlendFile *bf, BlendObject *objobj) { + BlendObject obj; + int32_t ldata; + if ((blend_object_structure_getfield(bf, &obj, *objobj, "lay") && + blend_object_getdata(bf, &ldata, obj))) { + return (BlendLayerMask) ldata; + } + return 0; +} + + +/* Bizarrely, blender doesn't seem to carry a mapping of parent + to children -- only of children to parent. So, to find the children + of an Object we have to scan all objects to see if their parent is + the Object in question. */ +typedef struct { + BlendBlockPointer wanted_parent; + BlendBlockPointer child; + int just_counting; + int num_so_far; + int wanted_index; +} childfinderData; +static BLENDBLOCKCALLBACK_RETURN +block_childfinder(BLENDBLOCKCALLBACK_ARGS) { + childfinderData *cfd = (childfinderData *)userdata; + const char *tagname = blend_block_get_typename(blend_file, block); + int entry_count = blend_block_get_entry_count(blend_file, block); + int want_more = 1; + int i; + + if (strcmp(tagname, "Object") == 0) { + /* Is Object */ + for (i=0; iwanted_parent) { + if ((cfd->num_so_far == cfd->wanted_index) && + !cfd->just_counting) { + cfd->child = block; + want_more = 0; + } + ++cfd->num_so_far; + } else { + cfd->child = NULL; + } + } + } + } + + return want_more; +} + +int +blend_obj_get_childcount(BlendFile *bf, BlendObject *objobj) { + childfinderData cfd; + cfd.wanted_parent = blend_block_from_object(bf, objobj); + cfd.child = NULL; + cfd.just_counting = 1; + cfd.num_so_far = 0; + cfd.wanted_index = 0; + blend_foreach_block(bf, block_childfinder, &cfd); + return cfd.num_so_far; +} + +BlendBlockPointer +blend_obj_get_child(BlendFile *bf, BlendObject *objobj, int childnum) { + childfinderData cfd; + cfd.wanted_parent = blend_block_from_object(bf, objobj); + cfd.child = NULL; + cfd.just_counting = 0; + cfd.num_so_far = 0; + cfd.wanted_index = childnum; + blend_foreach_block(bf, block_childfinder, &cfd); + return cfd.child; +} + + +/******************************************************************** + * HIGH-LEVEL * + ********************************************************************/ + +#ifndef LEN3POW2 +/* length squared */ +#define LEN3POW2(xd,yd,zd) ((xd)*(xd) + (yd)*(yd) + (zd)*(zd)) +#endif + +#ifndef LEN3 +/* length (expensive) */ +#define LEN3(xd,yd,zd) (sqrt(LEN3POW2((xd),(yd),(zd)))) +#endif + +#ifndef NORMALIZE3 +/* vector normalization (expensive) */ +#define NORMALIZE3(xd,yd,zd) \ +do { \ + const double norm3_macro_len3 = LEN3((xd),(yd),(zd)); \ + if (norm3_macro_len3 != 0.0F) \ + { \ + (xd) = (xd) / norm3_macro_len3; \ + (yd) = (yd) / norm3_macro_len3; \ + (zd) = (zd) / norm3_macro_len3; \ + } \ +} while (0) +#endif + + +static void bMatIdentity(bMatrix mat) { + int i,j; + for (i=0; i<4; ++i) { + for (j=0; j<4; ++j) { + mat[i][j] = 0.0f; + } + mat[i][i] = 1.0f; + } +} + +static void bMatMultVec(float xyz[3], bMatrix mat) { + int i; + float r[3]; + for (i=0; i<3; ++i) { + r[i] = 0.0f; + } + for (i=0; i<3; ++i) { + r[i] += xyz[0] * mat[0][i]; + r[i] += xyz[1] * mat[1][i]; + r[i] += xyz[2] * mat[2][i]; + r[i] += 1.0f * mat[3][i]; + } + for (i=0; i<3; ++i) { + xyz[i] = r[i]; + } +} + +bMesh *blend_alloc_mesh(void) { + return malloc(sizeof(bMesh)); +} + +void +blend_init_mesh(bMesh *mesh) +{ + int i,j; + mesh->vert = NULL; + mesh->vert_count = 0; + mesh->face = NULL; + mesh->face_count = 0; + mesh->material = NULL; +} + +void +blend_free_mesh_inner(bMesh *mesh) +{ + if (mesh->vert) { + free(mesh->vert); + } + if (mesh->face) { + free(mesh->face); + } + if (mesh->material) { + blend_free_material(mesh->material); + } +} + +void +blend_free_mesh(bMesh *mesh) +{ + blend_free_mesh_inner(mesh); + free(mesh); +} + + +bObj * +blend_alloc_obj(void) { + return malloc(sizeof(bObj)); +} + +void +blend_init_obj(bObj *obj) { + obj->type = BOBJ_TYPE_UNKNOWN; + obj->name = NULL; + bMatIdentity(obj->transform); + bMatIdentity(obj->parentimat); + obj->location[0] = + obj->location[1] = + obj->location[2] = 0.0f; + obj->scaling[0] = + obj->scaling[1] = + obj->scaling[2] = 1.0f; + obj->rotphr[0] = + obj->rotphr[1] = + obj->rotphr[2] = 0.0f; + obj->data.dummy = NULL; + obj->transflags = 0; +} + +void +blend_free_obj_inner(bObj *obj) { + if (obj->name) { + free(obj->name); + } + switch (obj->type) { + case BOBJ_TYPE_MESH: + blend_free_mesh(obj->data.mesh); + case BOBJ_TYPE_UNKNOWN: + case BOBJ_TYPE_NULL: + default: + break; + } +} + +void +blend_free_obj(bObj *obj) { + blend_free_obj_inner(obj); + free(obj); +} + +void +blend_acquire_obj_from_obj(BlendFile *bf, BlendObject *objobj, + bObj *outobj, BlendObject **mallocdoutblendobject) { + + BlendObject obj, dataobj; + BlendBlockPointer block; + short sdata = 12345; +#define B_IDNAME_MAX_SIZE 80 + char *idname = malloc(1 + B_IDNAME_MAX_SIZE); + float fdata1, fdata2, fdata3; + + if (mallocdoutblendobject) *mallocdoutblendobject = NULL; + + blend_init_obj(outobj); + + if (!(blend_object_structure_getfield(bf, &obj, *objobj, + "type") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); /* couldn't get type */ + } else { + switch (sdata) { + case 1: /* mesh */ + outobj->type = BOBJ_TYPE_MESH; break; + case 10: /* lamp */ + case 11: /* camera */ + default: + outobj->type = BOBJ_TYPE_UNKNOWN; break; + } + } + + if (blend_object_get_IDname(bf, *objobj, idname, B_IDNAME_MAX_SIZE)) { + outobj->name = idname; + } else { + free(idname); + abort(); /* couldn't get obj name */ + } + + /* now we override the mesh transform with the object's. should + we merge, instead??? - hm, dunno, don't think so. */ + + if (blend_object_structure_getfield(bf, &obj, *objobj, "loc") && + blend_object_array_getdata(bf, &fdata1, obj, 0,0) && + blend_object_array_getdata(bf, &fdata2, obj, 0,1) && + blend_object_array_getdata(bf, &fdata3, obj, 0,2)) { + outobj->location[0] = fdata1; + outobj->location[1] = fdata2; + outobj->location[2] = fdata3; + } else { + outobj->location[0] = + outobj->location[1] = + outobj->location[2] = 0.0f; + } + + if (blend_object_structure_getfield(bf, &obj, *objobj, "size") && + blend_object_array_getdata(bf, &fdata1, obj, 0,0) && + blend_object_array_getdata(bf, &fdata2, obj, 0,1) && + blend_object_array_getdata(bf, &fdata3, obj, 0,2)) { + outobj->scaling[0] = fdata1; + outobj->scaling[1] = fdata2; + outobj->scaling[2] = fdata3; + } else { + outobj->scaling[0] = + outobj->scaling[1] = + outobj->scaling[2] = 1.0f; + } + + if (blend_object_structure_getfield(bf, &obj, *objobj, "rot") && + blend_object_array_getdata(bf, &fdata1, obj, 0,0) && + blend_object_array_getdata(bf, &fdata2, obj, 0,1) && + blend_object_array_getdata(bf, &fdata3, obj, 0,2)) { + outobj->rotphr[0] = fdata1; + outobj->rotphr[1] = fdata2; + outobj->rotphr[2] = fdata3; + } else { + outobj->rotphr[0] = + outobj->rotphr[1] = + outobj->rotphr[2] = 0.0f; + } + + if (blend_object_structure_getfield(bf, &obj, *objobj, "parentinv")) { + int i,j; + for (i=0; i<4; ++i) { + for (j=0; j<4; ++j) { + blend_object_array_getdata(bf, &fdata1, obj, i,j); + outobj->parentimat[i][j] = fdata1; + } + } + } + + if (blend_object_structure_getfield(bf, &obj, *objobj, "transflag")) { + char cdata; + /* TODO: decode what these flags precisely mean. */ + /* top bit is 'powertrack' */ + if (blend_object_getdata(bf, &cdata, obj)) { + outobj->transflags = (unsigned char)cdata; + } + } + + if (blend_object_structure_getfield(bf, &obj, *objobj, "obmat")) { + int i,j; + for (i=0; i<4; ++i) { + for (j=0; j<4; ++j) { + blend_object_array_getdata(bf, &fdata1, obj, i,j); + outobj->transform[i][j] = fdata1; + dprintf(stderr, "%0.3f ", fdata1); + } + dprintf(stderr, "\n"); + } + } + + /* get actual obj here */ + + if (! (blend_object_structure_getfield(bf, &obj, *objobj, "data") && + blend_object_getdata(bf, &block, obj))) { + abort(); + } + + if (block == NULL) { + outobj->type = BOBJ_TYPE_NULL; + } else { + dataobj = blend_block_get_object(bf, block, 0); + if (mallocdoutblendobject) { + *mallocdoutblendobject = malloc(sizeof(BlendObject)); + **mallocdoutblendobject = dataobj; + } + } + + + switch (outobj->type) { + case BOBJ_TYPE_MESH: + outobj->data.mesh = blend_alloc_mesh(); + blend_acquire_mesh_from_obj(bf, &dataobj, outobj->data.mesh); + break; + case BOBJ_TYPE_UNKNOWN: + default: + case BOBJ_TYPE_NULL: + outobj->data.dummy = NULL; + break; + } +} + + + +bTexLayer * +blend_alloc_texlayer(void) { + return malloc(sizeof(bTexLayer)); +} + +void +blend_init_texlayer(bTexLayer *tl) { + tl->filename = NULL; + tl->affects_mask = 0; + tl->blend_mode = BTEX_BLEND_NORMAL; + tl->coords_type = BTEX_COORDS_NONE; + tl->is_st_clamped = 0; + tl->flags = 0; + tl->Nflags = tl->Ntype = 0; + tl->xrepeat = tl->yrepeat = 1; +} + +static void +blend_free_texlayer_inner(bTexLayer *tl) { + if (tl->filename) { + free(tl->filename); + } +} + +void +blend_free_texlayer(bTexLayer *tl) { + blend_free_texlayer_inner(tl); + free(tl); +} + + +void /* MTex */ +blend_acquire_texlayer_from_obj(BlendFile *bf, BlendObject *tlobj, + bTexLayer *tl) { + BlendObject obj; + BlendBlockPointer tex_block; + short sdata = 12345; + + blend_init_texlayer(tl); + + if (!(blend_object_structure_getfield(bf, &obj, *tlobj, + "mapto") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } + if (sdata & 0x01) { + tl->affects_mask |= BTEX_AFFECT_COLOUR; + } + if (sdata & 0x40) { + tl->affects_mask |= BTEX_AFFECT_EMIT; + } + if (sdata & 0x80) { + tl->affects_mask |= BTEX_AFFECT_ALPHA; + } + /* note: mapto not fully decoded. */ + + if (!(blend_object_structure_getfield(bf, &obj, *tlobj, + "texflag") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } + if (sdata & 0x02) { + tl->affects_mask |= BTEX_AFFECT_STENCIL; + } + + if (!(blend_object_structure_getfield(bf, &obj, *tlobj, + "texco") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } + switch (sdata) { + case 1: + case 2: + tl->coords_type = BTEX_COORDS_REFLECT; + break; + case 16: + tl->coords_type = BTEX_COORDS_UV; + break; + default: + /* I haven't seen this happen, but it probably does... */ + tl->coords_type = BTEX_COORDS_NONE; + break; + } + + if (!(blend_object_structure_getfield(bf, &obj, *tlobj, + "blendtype") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } + tl->blend_mode = sdata; /* not decoded yet :( */ + + if (blend_object_structure_getfield(bf, &obj, *tlobj, "tex") && + blend_object_getdata(bf, &tex_block, obj) && tex_block) { + BlendObject tobj = blend_block_get_object(bf, tex_block, 0); + BlendBlockPointer im_block; + BlendObject obj; + + if (!(blend_object_structure_getfield(bf, &obj, tobj, "extend") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } + tl->is_st_clamped = !(sdata & 2 /*'repeat'*/); + /*fprintf(stderr, "CLAMP=%d (was %d)\n", tl->is_st_clamped, sdata);*/ + if (!(blend_object_structure_getfield(bf, &obj, tobj, "xrepeat") && + blend_object_getdata(bf, &sdata, obj))) { + tl->xrepeat = 1; + } else { + tl->xrepeat = sdata; + } + if (!(blend_object_structure_getfield(bf, &obj, tobj, "yrepeat") && + blend_object_getdata(bf, &sdata, obj))) { + tl->yrepeat = 1; + } else { + tl->yrepeat = sdata; + } + if (!(blend_object_structure_getfield(bf, &obj, tobj, "flag") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } else { + tl->flags = sdata; + } + if (!(blend_object_structure_getfield(bf, &obj, tobj, "imaflag") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } else { + if (sdata & 0x0001) { + tl->flags |= BIMG_FLAG_INTERPOLATE; + } + if (sdata & 0x0004) { + tl->flags |= BIMG_FLAG_MIPMAP; + } + if (sdata & 0x0100) { + tl->flags |= BIMG_FLAG_ANTIALIAS; + } + } + if (!(blend_object_structure_getfield(bf, &obj, tobj, "type") && + blend_object_getdata(bf, &sdata, obj))) { + abort(); + } else { + tl->Ntype = sdata; + } + if (blend_object_structure_getfield(bf, &obj, tobj, "ima") && + blend_object_getdata(bf, &im_block, obj) && im_block) { + BlendObject imobj = blend_block_get_object(bf, im_block, 0); +#define BF_IMAGE_FILENAME_MAXSIZE 160 + tl->filename = malloc(BF_IMAGE_FILENAME_MAXSIZE); + if (!(blend_object_structure_getfield(bf, &obj, imobj, "name") && + blend_object_getstring(bf, obj, + tl->filename, BF_IMAGE_FILENAME_MAXSIZE))) { + abort(); + } + } + } else { + abort(); + } +} + + +bMaterial * +blend_alloc_material(void) { + return malloc(sizeof(bMaterial)); +} + +void +blend_init_material(bMaterial *mat) { + int i; + for (i=0; itex_layer[i] = NULL; + } + mat->feature_mask = 0; + for (i=0; i<4; ++i) { + mat->colour_rgba[i] = 1.0f; + } + mat->emit = 0.0f; +} + +void +blend_free_material(bMaterial *mat) { + int i; + for (i=0; itex_layer[i]) { + blend_free_texlayer(mat->tex_layer[i]); + } + } +} + +void +blend_acquire_material_from_obj(BlendFile *bf, BlendObject *matobj, + bMaterial *mat) { + BlendObject obj; + int i; + int32_t ldata = 123456; + float fdata = 123456.0; + + blend_init_material(mat); + + if ((blend_object_structure_getfield(bf, &obj, *matobj, "r") && + blend_object_getdata(bf, &fdata, obj))) { + mat->colour_rgba[0] = fdata; + } + if ((blend_object_structure_getfield(bf, &obj, *matobj, "g") && + blend_object_getdata(bf, &fdata, obj))) { + mat->colour_rgba[1] = fdata; + } + if ((blend_object_structure_getfield(bf, &obj, *matobj, "b") && + blend_object_getdata(bf, &fdata, obj))) { + mat->colour_rgba[2] = fdata; + } + if ((blend_object_structure_getfield(bf, &obj, *matobj, "alpha") && + blend_object_getdata(bf, &fdata, obj))) { + mat->colour_rgba[3] = fdata; + } + if ((blend_object_structure_getfield(bf, &obj, *matobj, "emit") && + blend_object_getdata(bf, &fdata, obj))) { + mat->emit = fdata; + } + + if (!(blend_object_structure_getfield(bf, &obj, *matobj, "mode") && + blend_object_getdata(bf, &ldata, obj))) { + abort(); + } + if (ldata & 0x04) { + mat->feature_mask |= BMAT_FEATURE_SHADELESS; + } + if (ldata & 0x08) { + mat->feature_mask |= BMAT_FEATURE_WIRE; + } + if (ldata & 0x10) { + mat->feature_mask |= BMAT_FEATURE_VCOLLIGHT; + } + if (ldata & 0x80) { + mat->feature_mask |= BMAT_FEATURE_VCOLPAINT; + } + if (ldata & (1024 | 512 | 256)) { /* not sure about this, it's strange. */ + mat->feature_mask |= BMAT_FEATURE_TEXFACE; + } + + for (i=0; itex_layer[i] = blend_alloc_texlayer(); + /*fprintf(stderr, "GETTING LAYER AT POS %d\n", i);*/ + blend_acquire_texlayer_from_obj(bf, &tlobj, mat->tex_layer[i]); + } else { + /*fprintf(stderr, "NOTHING FOUND AT POS %d\n", i);*/ + } + } +} + +void +blend_acquire_mesh_from_obj(BlendFile *bf, BlendObject *meobj, bMesh *mesh) +{ + { + BlendObject obj; + BlendBlockPointer vblock, fblock, cblock, tblock, + dblock, matlink; + int i; + int32_t ldata = 123456; + float fdata1, fdata2, fdata3; + + blend_init_mesh(mesh); + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "totvert") && + blend_object_getdata(bf, &ldata, obj))) { + abort(); + } + mesh->vert_count = ldata; + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "totface") && + blend_object_getdata(bf, &ldata, obj))) { + abort(); + } + mesh->face_count = ldata; + + dprintf(stderr, "%d verts, %d faces...\n", + mesh->vert_count, mesh->face_count); + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "mface") && + blend_object_getdata(bf, &fblock, obj))) { + abort(); + } + /* null fblock is okay */ + + if (blend_object_structure_getfield(bf, &obj, *meobj, + "mat") && + blend_object_getdata(bf, &matlink, obj) && matlink) { + /* found an indirect material link, follow it */ + BlendObject matlinkobj = blend_block_get_object(bf, matlink, 0); + BlendBlockPointer matblock; + if (blend_object_structure_getfield(bf, &obj, matlinkobj, + "next") && + blend_object_getdata(bf, &matblock, obj)) { + if (matblock) { + BlendObject matobj = blend_block_get_object(bf, matblock, 0); + mesh->material = blend_alloc_material(); + blend_acquire_material_from_obj(bf, &matobj, mesh->material); + } else { + /* um, okay, link went nowhere, leave mesh->material NULL */ + } + } else { + abort(); + } + } else { + /* no material -- mesh->material will remain NULL */ + } + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "mvert") && + blend_object_getdata(bf, &vblock, obj))) { + abort(); + } + /* null vblock is okay */ + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "tface") && + blend_object_getdata(bf, &tblock, obj))) { + abort(); + } + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "mcol") && + blend_object_getdata(bf, &cblock, obj))) { + abort(); + } + + if (!(blend_object_structure_getfield(bf, &obj, *meobj, + "dvert") && + blend_object_getdata(bf, &dblock, obj))) { + /* sometimes there isn't a dvert block... */ + dblock = NULL; + } + + mesh->vert = malloc(sizeof(bVert) * mesh->vert_count); + + for (i=0; ivert_count; ++i) { + BlendObject obj = blend_block_get_object(bf, vblock, i); + BlendObject aobj; + float fdata1, fdata2, fdata3; + int32_t sdata1, sdata2, sdata3; + char cdata; + + mesh->vert[i].xyz[0] = + mesh->vert[i].xyz[1] = + mesh->vert[i].xyz[2] = -12345; + mesh->vert[i].cnormal[0] = + mesh->vert[i].cnormal[1] = + mesh->vert[i].cnormal[2] = -12345; + mesh->vert[i].mat = -1; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "co") && + blend_object_array_getdata(bf, &fdata1, aobj, 0,0) && + blend_object_array_getdata(bf, &fdata2, aobj, 0,1) && + blend_object_array_getdata(bf, &fdata3, aobj, 0,2))) { + abort(); + } + mesh->vert[i].xyz[0] = fdata1; + mesh->vert[i].xyz[1] = fdata2; + mesh->vert[i].xyz[2] = fdata3; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "no") && + blend_object_array_getdata(bf, &sdata1, aobj, 0,0) && + blend_object_array_getdata(bf, &sdata2, aobj, 0,1) && + blend_object_array_getdata(bf, &sdata3, aobj, 0,2))) { + abort(); + } + mesh->vert[i].cnormal[0] = sdata1; + mesh->vert[i].cnormal[1] = sdata2; + mesh->vert[i].cnormal[2] = sdata3; + /*fprintf(stderr, "%f ", LEN3(mesh->vert[i].normal[0], + mesh->vert[i].normal[1], + mesh->vert[i].normal[2]));*/ + if (sdata1 != 0 || sdata2 != 0 || sdata3 != 0) { + NORMALIZE3(mesh->vert[i].cnormal[0], + mesh->vert[i].cnormal[1], + mesh->vert[i].cnormal[2]); + } + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "mat_nr") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->vert[i].mat = cdata; + } + + mesh->face = malloc(sizeof(bFace) * mesh->face_count); + + for (i=0; iface_count; ++i) { + int j,k; + BlendObject obj = blend_block_get_object(bf, fblock, i); + BlendObject aobj; + char cdata; + + mesh->face[i].v[0] = mesh->face[i].v[1] = + mesh->face[i].v[2] = mesh->face[i].v[3] = -1; + mesh->face[i].mat = -1; + mesh->face[i].flags = 0; + for (j=0; j<4; ++j) { + for (k=0; k<3; ++k) { + mesh->face[i].rgba[j][k] = 1.0; + } + mesh->face[i].rgba[j][3] = 1.0f; + mesh->face[i].uv[j][0] = 0.0f; + mesh->face[i].uv[j][1] = 0.0f; + mesh->face[i].image_id = NULL; + } + + if (blend_object_structure_getfield(bf, &aobj, obj, "v1")) { + if (0!=strcmp(bf->types[aobj.type].name,"int") && + 0!=strcmp(bf->types[aobj.type].name,"ushort")) { + dprintf(stderr, "Expected vertex-index type to be 'ushort' or 'int', got '%s'\n", bf->types[aobj.type].name); + abort(); + } + + if (0==strcmp(bf->types[aobj.type].name,"int")) { + /* index type is a 32bit int, generated by newish Blenders */ + int32_t idata; + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v1") && + blend_object_getdata(bf, &idata, aobj))) { + abort(); + } + mesh->face[i].v[0] = idata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v2") && + blend_object_getdata(bf, &idata, aobj))) { + abort(); + } + mesh->face[i].v[1] = idata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v3") && + blend_object_getdata(bf, &idata, aobj))) { + abort(); + } + mesh->face[i].v[2] = idata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v4") && + blend_object_getdata(bf, &idata, aobj))) { + abort(); + } + mesh->face[i].v[3] = idata; + } else { + /* index type is a 16bit ushort, generated by old Blenders */ + uint16_t usdata; + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v1") && + blend_object_getdata(bf, &usdata, aobj))) { + abort(); + } + mesh->face[i].v[0] = usdata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v2") && + blend_object_getdata(bf, &usdata, aobj))) { + abort(); + } + mesh->face[i].v[1] = usdata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v3") && + blend_object_getdata(bf, &usdata, aobj))) { + abort(); + } + mesh->face[i].v[2] = usdata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "v4") && + blend_object_getdata(bf, &usdata, aobj))) { + abort(); + } + mesh->face[i].v[3] = usdata; + } + } else { + abort(); + } + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "mat_nr") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].mat = cdata; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "flag") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].flags = cdata; + } + + if (cblock) { + /* we have vertex colours */ + for (i=0; iface_count; ++i) { + int j; + unsigned char cdata; + BlendObject aobj; + for (j=0; j<4; ++j) { + BlendObject obj = blend_block_get_object(bf, cblock, i*4+j); + if (!(blend_object_structure_getfield(bf, &aobj, obj, "b") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].rgba[j][0] = cdata / 255.0f; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "g") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].rgba[j][1] = cdata / 255.0f; + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "r") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].rgba[j][2] = cdata / 255.0f; + + /* alpha seems to be nonsense :( */ + /* + if (!(blend_object_structure_getfield(bf, &aobj, obj, "a") && + blend_object_getdata(bf, &cdata, aobj))) { + abort(); + } + mesh->face[i].rgba[j][3] = cdata / 255.0f; + */ + mesh->face[i].rgba[j][3] = 1.0f; + } + } + } else { + /* !cblock (no vertex colours) */ + for (i=0; iface_count; ++i) { + int j; + for (j=0; j<4; ++j) { + mesh->face[i].rgba[j][0] = 1.0f; + mesh->face[i].rgba[j][1] = 1.0f; + mesh->face[i].rgba[j][2] = 1.0f; + mesh->face[i].rgba[j][3] = 1.0f; + } + } + } + + if (tblock) { + /* we have tex co-ords */ + for (i=0; iface_count; ++i) { + int j,k; + void *pdata; + BlendObject aobj; + BlendObject obj = blend_block_get_object(bf, tblock, i); + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "tpage") && + blend_object_getdata(bf, &pdata, aobj))) { + abort(); + } + mesh->face[i].image_id = pdata; + + for (j=0; j<4; ++j) { + uint32_t uldata; + for (k=0; k<2; ++k) { + float fdata; + if (!(blend_object_structure_getfield(bf, &aobj, obj, "uv") && + blend_object_array_getdata(bf, &fdata, aobj, j,k))) { + abort(); + } + mesh->face[i].uv[j][k] = fdata; + } + mesh->face[i].uv[j][1] = 1.0f - mesh->face[i].uv[j][1]; + /* texture face colour... not sure how this conceptually + differs from the face vertex colours, but it does. */ + if (!(blend_object_structure_getfield(bf, &aobj, obj, "col") && + blend_object_array_getdata(bf, &uldata, aobj, 0,j))) { + abort(); + } + /* in its usual inconsistant style, blender packs this + RGBA value into the bytes of an unsigned long... */ + mesh->face[i].rgba2[j][0] = ((uldata >> 24) & 0xFF) / 255.0f; + mesh->face[i].rgba2[j][1] = ((uldata >> 16) & 0xFF) / 255.0f; + mesh->face[i].rgba2[j][2] = ((uldata >> 8) & 0xFF) / 255.0f; + mesh->face[i].rgba2[j][3] = ((uldata >> 0) & 0xFF) / 255.0f; + } + /*mesh->face[i].uv[0][0]=0; mesh->face[i].uv[0][1]=0; + mesh->face[i].uv[1][0]=1; mesh->face[i].uv[1][1]=0; + mesh->face[i].uv[2][0]=1; mesh->face[i].uv[2][1]=1; + mesh->face[i].uv[3][0]=0; mesh->face[i].uv[3][1]=1;*/ + + } + } else { + /* !tblock (no texture co-ords, no face tex-colours) */ + for (i=0; iface_count; ++i) { + int j; + for (j=0; j<4; ++j) { + mesh->face[i].rgba2[j][0] = 1.0f; + mesh->face[i].rgba2[j][1] = 1.0f; + mesh->face[i].rgba2[j][2] = 1.0f; + mesh->face[i].rgba2[j][3] = 1.0f; + } + } + } + + if (dblock) { + /* we have vertex deformation weights */ + for (i=0; ivert_count; ++i) { + int j; + int32_t ldata; + float fdata; + BlendBlockPointer pdata; + BlendObject aobj; + BlendObject obj = blend_block_get_object(bf, dblock, i); + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "totweight") && + blend_object_getdata(bf, &ldata, aobj))) { + abort(); + } + mesh->vert[i].deform_weights_count = ldata; + mesh->vert[i].deform_weights = malloc(ldata*sizeof(bDeformWeight)); + + if (!(blend_object_structure_getfield(bf, &aobj, obj, "dw") && + blend_object_getdata(bf, &pdata, aobj))) { + abort(); + } + + for (j=0; jvert[i].deform_weights_count; ++j) { + BlendObject dwobj = blend_block_get_object(bf, pdata, j); + + if (!(blend_object_structure_getfield(bf, &aobj, dwobj, "def_nr") + && blend_object_getdata(bf, &ldata, aobj))) { + abort(); + } + mesh->vert[i].deform_weights[j].bone_id = ldata; + + if (!(blend_object_structure_getfield(bf, &aobj, dwobj, "weight") + && blend_object_getdata(bf, &fdata, aobj))) { + abort(); + } + mesh->vert[i].deform_weights[j].weight = fdata; + } + } + } else { + /* !dblock (no vertex deformation weights) */ + for (i=0; ivert_count; ++i) { + mesh->vert[i].deform_weights = NULL; + mesh->vert[i].deform_weights_count = 0; + } + } + } +} + + + +void +blend_acquire_mesh(const char *fname, const char *want_name, bMesh *mesh) +{ + BlendFile* bf; + MY_FILETYPE *fp; + + fp = MY_OPEN_FOR_READ(fname); + + if (!fp) { + dprintf(stderr, "couldn't open file %s.\n", fname); + abort(); + } + + bf = blend_read(fp); + { + BlendObject meobj; + if (!blend_object_get_by_IDname(bf, &meobj, want_name)) { + dprintf(stderr, "couldn't find %s.\n", want_name); + abort(); + } + blend_dump_blocks(bf); + blend_acquire_mesh_from_obj(bf, &meobj, mesh); + } + blend_free(bf); + + MY_CLOSE(fp); +} + + +/* apply pitch, head, roll */ +static void bRotPHR(float xyz[3], const float rot[3]) { + float rx,ry,rz; + float ix,iy,iz; + float cosang, sinang; + + ix = xyz[0]; iy = xyz[1]; iz = xyz[2]; + + cosang = cos(rot[0]); + sinang = sin(rot[0]); + /* pitch */ + rx = ix; + ry = iy * cosang - iz * sinang; + rz = iy * sinang + iz * cosang; + + ix = rx; iy = ry; iz = rz; + + cosang = cos(rot[1]); + sinang = sin(rot[1]); + /* head */ + rx = ix * cosang + iz * sinang; + ry = iy; + rz = -ix * sinang + iz * cosang; + + ix = rx; iy = ry; iz = rz; + + cosang = cos(rot[2]); + sinang = sin(rot[2]); + /* roll */ + rx = ix * cosang - iy * sinang; + ry = ix * sinang + iy * cosang; + rz = iz; + + xyz[0] = rx; xyz[1] = ry; xyz[2] = rz; +} + + +void +blend_transform_mesh_from_obj(bMesh *mesh, bObj *obj) { + int i; + for (i=0; ivert_count; ++i) { + /* this one looks good. */ + bMatMultVec(mesh->vert[i].xyz, obj->transform); + bRotPHR(mesh->vert[i].cnormal, obj->rotphr); + } +} + diff --git a/Extras/readblend/readblend.h b/Extras/readblend/readblend.h new file mode 100644 index 000000000..49390cbfe --- /dev/null +++ b/Extras/readblend/readblend.h @@ -0,0 +1,459 @@ +/*************************************************************** + * readblend.h -- + * ReadBlend, a data extraction API for Blender's .blend files + * + * (c) 2003 Adam D. Moss + * + */ + +/* VERSION HISTORY + * 2003-04-05 : v1.0 + * 2008-10-05 : v1.1-beta + */ + +/* Blender files are 'curiously' structured and this is mirrored in + * the low-level data access API (the section labelled 'Low-level + * querying functions'). + * + * The mid-level data access API (the section labelled 'Mid-level + * querying functions') tries to provide some handy utilities to + * ease your data-extraction pain, and finally the (partially written) + * high-level data access API incorporates semantic knowledge of Blender's + * useful high-level types such as Meshes and Materials to shield you + * from the full horror. + */ +#ifndef _READBLEND_H +#define _READBLEND_H + +#include +//#include +#include +#include //size_t for MSVC 6.0 + +#include +#include + +#include "abs-file.h" + +/* TODO: Doxygen me. */ + + +/* don't worry yourself about this. */ +#ifndef BlendFile +#define BlendFile void +#endif + + + + +/**************************************/ +/* .blend handle load/free functions */ +/*************************************/ + + +/* Pass in an already-opened file handle; this function will then + examine the file to check that it is a Blender file and scan the + entire file to extract all vital information (quite expensive) + before any query operations can be performed. The resulting + .blend data handle is returned. +*/ + +BlendFile* blend_read(MY_FILETYPE* file); + + +/* Free all of the given BlendFile data (note: obviously, do not attempt + to extract data from a BlendFile which has been freed!) */ + +void blend_free(BlendFile* blend_file); + + +/********************************/ +/* Public data querying types */ +/********************************/ + + +/* heed this enum well. */ +typedef enum { + /* you're unlikely to encounter these in the wild. */ + BLEND_OBJ_NULL, + BLEND_OBJ_OPAQUE, + + /* these object types are fetchable as-is */ + BLEND_OBJ_UCHAR8, + BLEND_OBJ_CHAR8, + BLEND_OBJ_USHORT16, + BLEND_OBJ_SHORT16, + BLEND_OBJ_ULONG32, + BLEND_OBJ_LONG32, + BLEND_OBJ_FLOAT, + BLEND_OBJ_DOUBLE, + BLEND_OBJ_POINTER, + + /* you'll need to extract the fields from these individually */ + BLEND_OBJ_STRUCT +} BlendObjType; + +typedef void* BlendBlockPointer; + +/* note: treat this as an opaque type and you'll live longer. */ +typedef struct { + long type; long name; BlendBlockPointer block; + long entry_index; long field_index; +} BlendObject; + +/* The callback type for passing to blend_foreach_block() (the callback + should return zero if it doesn't want to see any more blocks.) */ +#define BLENDBLOCKCALLBACK_RETURN int +#define BLENDBLOCKCALLBACK_ARGS BlendBlockPointer block, \ + BlendFile* blend_file, \ + void* userdata +typedef BLENDBLOCKCALLBACK_RETURN(BlendBlockCallback) + (BLENDBLOCKCALLBACK_ARGS); + + +/********************************/ +/* File info dumping functions */ +/********************************/ + +/* these functions simply print dumps of information about the given + BlendFile. These are vital for familiarising yourself with the + structure of Blender's various scene objects if you're using the + low/mid-level APIs to construct queries to extract information about + specific types of objects that haven't been conveniently abstracted by + the high-level API yet. (That is, most of them right now, particularly + the more obscure or deprecated ones.) */ + +/* print out the size, field-types and field-names of all of the varieties + of types (atomic and struct) represented within this BlendFile. The + sizes are the in-file representations; the in-memory representations + are potentially subject to additional padding and conversions. +*/ +void blend_dump_typedefs(BlendFile* bf); + +/* For every top-level block of data in the file, print the block's + tag, its in-memory address, its type, and the number of entries of that + type. (Every pointer in a structure should point to one of these + in-memory block addresses because we convert from in-file pointers to + in-memory pointers automagically; a few in-file pointers within structures + do actually point somewhere into the middle of a block or nowhere, which we + don't currently support and silently convert to NULL.) Also, any structs + at the top-level with an id->name have that name printed out. */ +void blend_dump_blocks(BlendFile* bf); + + +/********************************/ +/* Low-level querying functions */ +/********************************/ + + +/* Calls the user-supplied callback function for every top-level + data block in the .blend file. You can use this to count blocks + (which is pretty pointless) or search for a particular block you're + interested in. */ +void blend_foreach_block(BlendFile* blend_file, + BlendBlockCallback* func, + void* userdata); + +/* Returns the tag-name ('IM', 'DATA', 'ME', etc) for the given + top-level data block. */ +const char* blend_block_get_tagname(BlendFile* blend_file, + BlendBlockPointer block); + +/* Returns the type of the given top-level data block in raw + string form ('uchar', 'float', 'MFace', 'Mesh', etc). */ +const char* blend_block_get_typename(BlendFile* blend_file, + BlendBlockPointer block); + +/* Translate a pointer from the file's raw data into a BlendBlockPointer + that you can query via the API. You will usually NOT need to ever + use this unless you're manually extracting pointers from opaque raw data + types. Returns NULL on failure or if the input pointer is really NULL. */ +BlendBlockPointer blend_block_from_blendpointer(BlendFile *blend_file, + uint32_t blendpointer); + +/* Returns the number of entries there are in the given top-level + data block (a top-level data block is like an array of entries of a specific + Blender type, this type usually being one of Blender's structs). */ +int blend_block_get_entry_count(BlendFile* blend_file, + BlendBlockPointer block); + +/* This gets an object handle on the Nth piece of data (in the range + 0..TOTAL-1, where TOTAL is the figure returned by + blend_block_get_entry_count() ) in the given top-level block. */ +BlendObject blend_block_get_object(BlendFile* blend_file, + BlendBlockPointer block, + int entry_index); + +/* Returns a BlendObjType enum handy for checking that the general type + of the object you have a handle on is what you're expecting. */ +BlendObjType blend_object_type(BlendFile* blend_file, + BlendObject obj); + +/* Given a BlendObject of type BLEND_OBJ_STRUCT in 'obj', fill in the + BlendObject pointed to by 'result' with a handle to the named + field of that structure (note that the resulting data object might + itself be a structure!). 0 is returned on failure (i.e. this structure + does not have a field of the requested name, or you supplied an + object which is not a structure). */ +int blend_object_structure_getfield(BlendFile* blend_file, + BlendObject *result, + BlendObject obj, + const char* field_name); + +/* Gets the size (in elements) of an object which is a 1D or 2D array. + A non-array is equivalent to an array of size 1x1. A 1D array will + always have dim1 == 1. */ +void blend_object_array_getdims(BlendFile* blend_file, + BlendObject obj, + int* dim1, int* dim2); + +/* This fetches a piece of data from the .blend file in a format + suitable for your architecture (i.e. ints will be of proper size + and endianness, pointers are transformed to valid BlendBlockPointers + or NULL, etc) into the address pointed to by dest. It's up to you to + check that the data you're asking for will fit into the type you're + trying to put it in (use blend_object_type() and see the BlendObjType + enum to check that the object's type is the one you're expecting.) + + Composite structures (BLEND_OBJ_STRUCT) are not handled atomically; + use blend_object_structure_getfield() to extract each named field from + a structure individually. + + 1 is returned on success. Failure occurs when you try to extract + data from a structure (see blend_object_structure_getfield()) or + an array (use blend_object_array_getdata()). +*/ +int blend_object_getdata(BlendFile* blend_file, + void* dest, BlendObject obj); + +/* This operates like blend_object_getdata() except that it is happy + to copy an item of data out of an array. The array is always treated + as two-dimensional (data[dimension1][dimension2]); if you're accessing + a one-dimensional array then simply specify dim_index_1 as 0. + (Indices are in the range 0..DIMSIZE-1.) + + Similarly, plain non-array data can be fetched by specifying + dim_index_1 == dim_index_2 == 0. This makes + blend_object_array_getdata(bf, dest, obj, 0, 0) equivalent to + blend_object_getdata(bf, dest, obj) except that it is happy to + be called with an array, from which it will extract the first + element. + + Like blend_object_getdata(), it will not fetch a structure (from + an array of structures; fortunately I've not seen a .blend file + featuring an array of structures so we'll bridge that API gap + when we come to it). + */ +int blend_object_array_getdata(BlendFile* blend_file, + void* dest, BlendObject obj, + int dim_index_1, int dim_index_2); + +/********************************/ +/* Mid-level querying functions */ +/********************************/ + +typedef unsigned long BlendLayerMask; + +/* extract a string from a char-array or uchar-array object, up to + max_chars in length including the \0 terminator. If the object is + a two-dimensional array then the first string is extracted. Returns + 0 on failure. +*/ +int blend_object_getstring(BlendFile* blend_file, + BlendObject obj, + char *dest, int max_chars); + +/* Searches the file for a top-level object with the given ID name. + Typical ID names are 'OBCamera', 'MECircle.003', 'MAplastic', 'TEgrass' etc. + The first two characters of an ID name keep the namespaces separate, + so that a material (MA) with the name 'metal' is distinguishable from + a texture (TE) with the name 'metal'. That's a foible of Blender itself. + + blend_object_get_by_IDname() returns 0 on failure. It only returns one + object of the given IDname; there should indeed be only one, as Blender + enforces this uniqueness. + */ +int blend_object_get_by_IDname(BlendFile* blend_file, + BlendObject *result, + const char* IDname); +/* get string from [obj].ID.name -- caller allocs/frees */ +int blend_object_get_IDname(BlendFile* blend_file, + BlendObject obj, + char *dest, int max_chars); + +/* returns !0 if the blender Object whose handle is objobj is a top-level + object -- that is, it's not a child of another Object. */ +int blend_obj_is_rootobject(BlendFile *bf, BlendObject *objobj); +/* return the layers (bitmask) that an Object lives on. */ +BlendLayerMask blend_obj_get_layermask(BlendFile *bf, BlendObject *objobj); + +/* These functions can be a bit slow -- each one requires a linear scan + of the file's blocks. But they're handy... */ +/* Return number of children that this Object has. Can be 0. */ +int blend_obj_get_childcount(BlendFile *bf, BlendObject *objobj); +/* Gets child number childnum of the Object. childnum=0 returns + the first, childnum=1 returns the second, etc. */ +BlendBlockPointer blend_obj_get_child(BlendFile *bf, BlendObject *objobj, + int childnum); + +/*********************************/ +/* High-level querying functions */ +/*********************************/ + +typedef struct { + int bone_id; + float weight; +} bDeformWeight; + +typedef struct { + float xyz[3]; + float cnormal[3]; /* cosmetic normal */ + int mat; + bDeformWeight* deform_weights; + int deform_weights_count; +} bVert; +#define BVERT_HAS_CNORMAL(BV) ((BV)->cnormal[0] != 0.0f || \ + (BV)->cnormal[1] != 0.0f || \ + (BV)->cnormal[2] != 0.0f) + +#define BFACE_FLAG_SMOOTH 0x01 +typedef struct { + int v[4]; + float rgba[4][4]; /* vertex colours */ + float rgba2[4][4]; /* texture face colours (errrr...?) */ + float uv[4][2]; + BlendBlockPointer image_id; + int mat; + unsigned char flags; +} bFace; +#define BFACE_HAS_TEXTURE(BF) ((BF)->image_id != NULL) +#define BFACE_IS_QUAD(BF) ((BF)->v[3] != 0) +#define BFACE_IS_TRI(BF) ((BF)->v[2] != 0 && (BF)->v[3] == 0) +#define BFACE_IS_LINE(BF) ((BF)->v[1] != 0 && (BF)->v[2] == 0) + +typedef enum { + BTEX_AFFECT_COLOUR = 0x01, + BTEX_AFFECT_ALPHA = 0x02, + BTEX_AFFECT_EMIT = 0x04, + BTEX_AFFECT_NORMAL = 0x08, + BTEX_AFFECT_NEGNORM = 0x10, + BTEX_AFFECT_STENCIL = 0x20 /* not really an 'affect' in blender, but it is */ +} bTexLayerAffects; + +typedef enum { + BTEX_BLEND_NORMAL, /* 'mix' */ + BTEX_BLEND_MULTIPLY, /* 'mul' -- modulate. */ + BTEX_BLEND_ADD, + BTEX_BLEND_SUB +} bTexLayerBlendmode; + +typedef enum { + BTEX_COORDS_NONE, + BTEX_COORDS_UV, + BTEX_COORDS_REFLECT +} bTexLayerCoordsType; + +typedef enum { + BIMG_FLAG_INTERPOLATE = 0x01, /* upsample */ + BIMG_FLAG_ANTIALIAS = 0x02, /* downsample */ + BIMG_FLAG_MIPMAP = 0x04 /* use mipmaps */ +} bTexImageFlags; + +typedef struct { + char *filename; /* image file name -- absolute path! -- NULL if not image. */ + bTexLayerAffects affects_mask; + bTexLayerBlendmode blend_mode; + bTexLayerCoordsType coords_type; + /* Tex substruct */ + int is_st_clamped; /* otherwise, repeat... (GL texwrap mode.) */ + bTexImageFlags flags; + short Nflags, Ntype; /* not decoded yet */ + short xrepeat, yrepeat; /* amounts to scale texcoords by, really */ +} bTexLayer; + +typedef enum { + BMAT_FEATURE_VCOLLIGHT = 0x01, + BMAT_FEATURE_VCOLPAINT = 0x02, + BMAT_FEATURE_TEXFACE = 0x04, + BMAT_FEATURE_SHADELESS = 0x08, + BMAT_FEATURE_WIRE = 0x10 /* wireframe rendering */ +} bMatFeatureMask; + +#define BLENDER_MAX_TEX_LAYERS 8 +typedef struct { + bTexLayer *tex_layer[BLENDER_MAX_TEX_LAYERS]; + bMatFeatureMask feature_mask; + float colour_rgba[4]; /* main material colour */ + float emit; /* emissive strength, 0..1 */ +} bMaterial; + +typedef float bMatrix[4][4]; + +typedef struct { + bVert *vert; + int vert_count; + bFace *face; + int face_count; + bMaterial *material; /* or NULL if none */ +} bMesh; + +typedef enum { + BOBJ_TYPE_UNKNOWN, + BOBJ_TYPE_NULL, /* indicates object has no data associated with it! */ + BOBJ_TYPE_MESH +} bObjType; + +typedef enum { + BAQ_INCLUDE_CHILDREN = 0x0001 +} bAcquireFlags; + +typedef struct { + bObjType type; + char *name; + + bMatrix transform; /* local transformation matrix */ + bMatrix parentimat; /* parent's inverse transform matrix */ + float scaling[3]; /* scaling component of transform */ + float rotphr[3]; /* pitch/head/roll rotation component of transform (use for normals) */ + float location[3]; /* location component of transform */ + unsigned char transflags; /* NOT DECODED YET, RAW BYTE */ + + union { + void *dummy; + bMesh *mesh; + } data; +} bObj; + + +bObj *blend_alloc_obj(void); +void blend_init_obj(bObj *obj); +void blend_free_obj(bObj *obj); +void blend_free_obj_inner(bObj *obj); +void blend_acquire_obj_from_obj(BlendFile *bf, BlendObject *objobj, + bObj *outobj, + /* malloc'd BlendObject will be written here if pointer if non-NULL: */ BlendObject **outblendobject); + +bMesh *blend_alloc_mesh(void); +void blend_init_mesh(bMesh *mesh); +void blend_free_mesh_inner(bMesh *mesh); +void blend_free_mesh(bMesh *mesh); +void blend_acquire_mesh(const char *fname, const char *want_name, bMesh *mesh); +void blend_acquire_mesh_from_obj(BlendFile *bf, + BlendObject *meobj, + bMesh *mesh); +/* apply transformation found in 'obj' to 'mesh' */ +void blend_transform_mesh_from_obj(bMesh *mesh, bObj *obj); + +bMaterial *blend_alloc_material(void); +void blend_init_material(bMaterial *mat); +void blend_free_material(bMaterial *mat); +void blend_acquire_material_from_obj(BlendFile *bf, BlendObject *meobj, + bMaterial *mat); + +bTexLayer *blend_alloc_texlayer(void); +void blend_init_texlayer(bTexLayer *tl); +void blend_free_texlayer(bTexLayer *tl); +void blend_acquire_texlayer_from_obj(BlendFile *bf, BlendObject *tlobj, + bTexLayer *tl); + +#endif diff --git a/Extras/readblend/testblend.c b/Extras/readblend/testblend.c new file mode 100644 index 000000000..488294274 --- /dev/null +++ b/Extras/readblend/testblend.c @@ -0,0 +1,110 @@ +#include +#include + +#include "abs-file.h" + +#include "readblend.h" + +void crawl(BlendFile* blend_file, + BlendObject obj) +{ + BlendObject data_obj; + + if (blend_object_structure_getfield(blend_file, &data_obj, obj, "totvert")) { + if (blend_object_type(blend_file, data_obj) == BLEND_OBJ_LONG32) { + long data1 = 123456; + if (blend_object_getdata(blend_file, &data1, data_obj)) { + fprintf(stderr, "TOTVERT=(%ld) ", data1); + } else { + fprintf(stderr, "FETCH ERROR\n"); + } + } + } + + if (blend_object_structure_getfield(blend_file, &data_obj, obj, "co")) { + if (blend_object_type(blend_file, data_obj) == BLEND_OBJ_FLOAT) { + float data1 = 123456.7; + float data2 = 123456.8; + float data3 = 123456.9; + if (blend_object_array_getdata(blend_file, &data1, data_obj, 0,0) && + blend_object_array_getdata(blend_file, &data2, data_obj, 0,1) && + blend_object_array_getdata(blend_file, &data3, data_obj, 0,2)) { + fprintf(stderr, "CO=(%f,%f,%f) ", data1, data2, data3); + } else { + fprintf(stderr, "FETCH ERROR\n"); + } + } + } + +} + +static BLENDBLOCKCALLBACK_RETURN +blockiter(BLENDBLOCKCALLBACK_ARGS) { + int i; + int entry_count = blend_block_get_entry_count(blend_file, block); +#if 0 + const char* tagname = blend_block_get_tagname(blend_file, block); + const char* typename = blend_block_get_typename(blend_file, block); + fprintf(stderr, "block: tag=%s type=%s entries=%d ", + tagname, typename, entry_count); +#endif + for (i=0; i