Repository: ezrec/uv3dp
Pull request #28 (2020-03-19 03:03:55)

Issue 27 zcodex


Patch:
diff --git a/README.md b/README.md
index 1080061..20392da 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,7 @@ The command line tool is designed to be used in a 'pipeline' style, for example:
     
     Options for '.cbddlp':
     
-      -a, --anti-alias int   Override antialias level (1,2,4,8) (default 4)
+      -a, --anti-alias int   Override antialias level (1,2,4,8) (default 1)
       -v, --version int      Override header Version (default 2)
     
     Options for '.photon':
@@ -101,6 +101,9 @@ The command line tool is designed to be used in a 'pipeline' style, for example:
     
       -m, --material-name string   config.init entry 'materialName' (default "3DM-ABS @")
     
+    Options for '.zcodex':
+    
+    
     Options for 'empty':
     
       -M, --machine string             Size preset by machine type (default "EPAX-X1")
@@ -116,11 +119,12 @@ The command line tool is designed to be used in a 'pipeline' style, for example:
         EPAX-X156            2160x3840, 194x345 mm
         EPAX-X9              1600x2560, 120x192 mm
         Elogoo-Mars          1440x2560, 68x121 mm
+        Zortrax-Inkspire     1440x2560, 72x128 mm
     
     Known resins: (from local user ChiTuBox config)
     
         Anycubic Gray                            bottom 5 slow layers, 1m0s; nominal 22s
         Geeetech Washable Crystal                bottom 5 slow layers, 1m0s; nominal 14s
-        Siraya Blue + Anycubic Grey              bottom 4 slow layers, 1m0s; nominal 20s
+        Siraya Blue + Anycubic Grey              bottom 4 slow layers, 1m0s; nominal 16s
         Siraya Tech Fast Grey                    bottom 5 slow layers, 1m0s; nominal 14s
         Tech Gray 2                              bottom 8 slow layers, 1m0s; nominal 14s
diff --git a/cbddlp/format.go b/cbddlp/format.go
index a26a6a0..f4b1b22 100644
--- a/cbddlp/format.go
+++ b/cbddlp/format.go
@@ -132,7 +132,7 @@ func NewCbddlpFormatter(suffix string) (cf *CbddlpFormatter) {
 	switch suffix {
 	case ".cbddlp":
 		version = 2
-		antialias = 4
+		antialias = 1
 	case ".photon":
 		version = 1
 		antialias = 1
diff --git a/cmd/uv3dp/machine.go b/cmd/uv3dp/machine.go
index 373ef74..c7597a5 100644
--- a/cmd/uv3dp/machine.go
+++ b/cmd/uv3dp/machine.go
@@ -22,13 +22,14 @@ type MachineInfo struct {
 // Predefined bed layouts
 var (
 	MachineMap = map[string]MachineInfo{
-		"Anycubic-Photon": {Size: MachineSize{1440, 2560, 68.04, 120.96}},
-		"Elogoo-Mars":     {Size: MachineSize{1440, 2560, 68.04, 120.96}},
-		"EPAX-X1":         {Size: MachineSize{1440, 2560, 68.04, 120.96}},
-		"EPAX-X9":         {Size: MachineSize{1600, 2560, 120.0, 192.0}},
-		"EPAX-X10":        {Size: MachineSize{1600, 2560, 135.0, 216.0}},
-		"EPAX-X133":       {Size: MachineSize{2160, 3840, 165.0, 293.0}},
-		"EPAX-X156":       {Size: MachineSize{2160, 3840, 194.0, 345.0}},
+		"Anycubic-Photon":  {Size: MachineSize{1440, 2560, 68.04, 120.96}},
+		"Elogoo-Mars":      {Size: MachineSize{1440, 2560, 68.04, 120.96}},
+		"EPAX-X1":          {Size: MachineSize{1440, 2560, 68.04, 120.96}},
+		"EPAX-X9":          {Size: MachineSize{1600, 2560, 120.0, 192.0}},
+		"EPAX-X10":         {Size: MachineSize{1600, 2560, 135.0, 216.0}},
+		"EPAX-X133":        {Size: MachineSize{2160, 3840, 165.0, 293.0}},
+		"EPAX-X156":        {Size: MachineSize{2160, 3840, 194.0, 345.0}},
+		"Zortrax-Inkspire": {Size: MachineSize{1440, 2560, 72.0, 128.0}},
 	}
 )
 
diff --git a/cmd/uv3dp/main.go b/cmd/uv3dp/main.go
index 64a71a9..013f1e1 100644
--- a/cmd/uv3dp/main.go
+++ b/cmd/uv3dp/main.go
@@ -12,6 +12,7 @@ import (
 	"github.com/ezrec/uv3dp"
 	_ "github.com/ezrec/uv3dp/cbddlp"
 	_ "github.com/ezrec/uv3dp/sl1"
+	_ "github.com/ezrec/uv3dp/zcodex"
 
 	"github.com/spf13/pflag"
 )
diff --git a/go.sum b/go.sum
index a88af66..28624a7 100644
--- a/go.sum
+++ b/go.sum
@@ -17,6 +17,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
 golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/properties.go b/properties.go
index e090e7f..f9b45fb 100644
--- a/properties.go
+++ b/properties.go
@@ -91,6 +91,7 @@ type Properties struct {
 	Exposure Exposure
 	Bottom   Bottom
 	Preview  map[PreviewType]image.Image
+	Metadata map[string](interface{})
 }
 
 // Get image bounds
diff --git a/zcodex/format.go b/zcodex/format.go
new file mode 100644
index 0000000..b4f734f
--- /dev/null
+++ b/zcodex/format.go
@@ -0,0 +1,426 @@
+//
+// Copyright (c) 2020 Jason S. McMullan <jason.mcmullan@gmail.com>
+//
+
+package zcodex
+
+import (
+	"archive/zip"
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"image"
+	"image/color"
+	"image/png"
+	"io"
+	"io/ioutil"
+	"strings"
+	"time"
+
+	"github.com/ezrec/uv3dp"
+	"github.com/spf13/pflag"
+)
+
+var (
+	time_Now = time.Now
+)
+
+const (
+	mmPerPixel       = 0.05
+	defaultPixelsX   = 1440
+	defaultPixelsY   = 2560
+	defaultCacheSize = 16
+)
+
+type UserSettingsData struct {
+	MaxLayer                         int
+	PrintTime                        string
+	MaterialVolume                   float32
+	IsAdvanced                       int
+	Printer                          string
+	MaterialType                     string
+	MaterialId                       string
+	LayerThickness                   string
+	RaftEnabled                      int
+	RaftHeight                       float32
+	RaftOffset                       float32
+	ModelLiftEnabled                 int
+	ModelLiftHeight                  float32
+	CrossSupportEnabled              int
+	LayerExposureTime                int
+	LayerThicknessesDisplayTime      []string
+	ExposureOffTime                  int
+	BottomLayerExposureTime          int
+	BottomLayersCount                int
+	SupportAdditionalExposureEnabled int
+	SupportAdditionalExposureTime    int
+	ZLiftDistance                    float32
+	ZLiftRetractRate                 float32
+	ZLiftFeedRate                    float32
+	AntiAliasing                     int
+	XCorrection                      int
+	YCorrection                      int
+	HollowEnabled                    int
+	HollowThickness                  float32
+	InfillDensity                    float32
+}
+
+type ResinMetadataLayer struct {
+	Layer              int
+	UsedMaterialVolume float32
+}
+
+type ResinMetadata struct {
+	Guid                       string
+	Material                   string
+	MaterialId                 int
+	LayerThickness             float32
+	PrintTime                  int
+	LayerTime                  int
+	BottomLayersTime           int
+	AdditionalSupportLayerTime int
+	BottomLayersNumber         int
+	BlankingLayerTime          int
+	TotalMaterialVolumeUsed    float32
+	TotalMaterialWeightUsed    float32
+	TotalLayersCount           int
+	DisableSettingsChanges     bool
+	Pauses                     []int
+	Layers                     []ResinMetadataLayer
+}
+
+type Zcodex struct {
+	properties uv3dp.Properties
+	layerPng   []([]byte)
+}
+
+type ZcodexFormat struct {
+	*pflag.FlagSet
+}
+
+func NewZcodexFormatter(suffix string) (sf *ZcodexFormat) {
+	flagSet := pflag.NewFlagSet(suffix, pflag.ContinueOnError)
+
+	sf = &ZcodexFormat{
+		FlagSet: flagSet,
+	}
+
+	sf.SetInterspersed(false)
+
+	return
+}
+
+func (sf *ZcodexFormat) Encode(writer uv3dp.Writer, printable uv3dp.Printable) (err error) {
+	prop := printable.Properties()
+
+	archive := zip.NewWriter(writer)
+	defer archive.Close()
+
+	var rm ResinMetadata
+	anon, ok := prop.Metadata["zcodex/ResinMetadata"]
+	if ok {
+		rmptr, ok := anon.(*ResinMetadata)
+		if ok {
+			rm = *rmptr
+		}
+	}
+
+	rm.LayerThickness = prop.Size.LayerHeight
+	rm.LayerTime = int(prop.Exposure.LightOnTime / time.Millisecond)
+	rm.BottomLayersTime = int(prop.Bottom.Exposure.LightOnTime / time.Millisecond)
+	rm.TotalLayersCount = prop.Size.Layers
+	rm.BottomLayersNumber = prop.Bottom.Count
+	rm.BlankingLayerTime = int(prop.Exposure.LightOffTime / time.Millisecond)
+
+	var us UserSettingsData
+	anon, ok = prop.Metadata["zcodex/UserSettingsData"]
+	if ok {
+		usptr, ok := anon.(*UserSettingsData)
+		if ok {
+			us = *usptr
+		}
+	}
+
+	us.MaxLayer = prop.Size.Layers
+	us.LayerThickness = fmt.Sprintf("%.2g mm", prop.Size.LayerHeight)
+	us.LayerExposureTime = int(prop.Exposure.LightOnTime / time.Millisecond)
+	us.ExposureOffTime = int(prop.Exposure.LightOffTime / time.Millisecond)
+	us.BottomLayerExposureTime = int(prop.Bottom.Exposure.LightOnTime / time.Millisecond)
+	us.BottomLayersCount = prop.Bottom.Count
+	us.ZLiftDistance = prop.Exposure.LiftHeight
+	us.ZLiftRetractRate = prop.Exposure.RetractSpeed
+	us.ZLiftFeedRate = prop.Exposure.LiftSpeed
+
+	size := prop.Size
+
+	rm.Layers = make([]ResinMetadataLayer, size.Layers)
+
+	// Create all the layers
+	for n := 0; n < size.Layers; n++ {
+		layer := printable.Layer(n)
+
+		filename := fmt.Sprintf("ResinSlicesData/Slice%05d.png", n)
+
+		writer, err = archive.Create(filename)
+		if err != nil {
+			return
+		}
+
+		err = png.Encode(writer, layer.Image)
+		if err != nil {
+			return
+		}
+
+		rm.Layers[n] = ResinMetadataLayer{Layer: n, UsedMaterialVolume: 0.0}
+	}
+
+	// Save the UserSettingsData
+	writer, err = archive.Create("UserSettingsData")
+	if err != nil {
+		return
+	}
+
+	err = json.NewEncoder(writer).Encode(&us)
+	if err != nil {
+		return
+	}
+
+	// Save the ResinMetadata
+	writer, err = archive.Create("ResinMetadata")
+	if err != nil {
+		return
+	}
+
+	err = json.NewEncoder(writer).Encode(&rm)
+	if err != nil {
+		return
+	}
+
+	// Save the thumbnails
+	image, ok := prop.Preview[uv3dp.PreviewTypeTiny]
+	if !ok {
+		image, ok = prop.Preview[uv3dp.PreviewTypeHuge]
+	}
+	if ok {
+		writer, err = archive.Create("Preview.png")
+		if err != nil {
+			return
+		}
+
+		err = png.Encode(writer, image)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+func loadJSON(filemap map[string](*zip.File), filename string, msg interface{}) (err error) {
+	zfile, found := filemap[filename]
+	if !found {
+		err = fmt.Errorf("%v not found in archive", filename)
+		return
+	}
+
+	reader, err := zfile.Open()
+	if err != nil {
+		return
+	}
+	defer reader.Close()
+
+	err = json.NewDecoder(reader).Decode(msg)
+
+	return
+}
+
+func (sf *ZcodexFormat) Decode(reader uv3dp.Reader, filesize int64) (printable uv3dp.Printable, err error) {
+	archive, err := zip.NewReader(reader, filesize)
+	if err != nil {
+		return
+	}
+
+	fileMap := make(map[string](*zip.File))
+
+	for _, file := range archive.File {
+		fileMap[file.Name] = file
+	}
+
+	gcode_file, ok := fileMap["ResinGCodeData"]
+	if !ok {
+		err = fmt.Errorf("ResinGCodeData not in archive")
+		return
+	}
+
+	gcode_reader, err := gcode_file.Open()
+	if err != nil {
+		return
+	}
+	defer gcode_reader.Close()
+
+	sliceMap := []int{}
+
+	gcode_scanner := bufio.NewScanner(gcode_reader)
+	var slice int
+	for gcode_scanner.Scan() {
+		text := gcode_scanner.Text()
+		switch {
+		case text == "<Delay_model>":
+			sliceMap = append(sliceMap, slice)
+		case strings.HasPrefix(text, "<Slice> "):
+			var rest string
+			var n int
+			n, err = fmt.Sscanf(text[8:], "%d%s", &slice, &rest)
+			if n != 1 || err != io.EOF {
+				err = fmt.Errorf("ResinGCodeData: Invalid slice load: '%v'", text)
+				return
+			}
+		}
+	}
+
+	var us UserSettingsData
+	err = loadJSON(fileMap, "UserSettingsData", &us)
+	if err != nil {
+		return
+	}
+
+	var rm ResinMetadata
+	err = loadJSON(fileMap, "ResinMetadata", &rm)
+	if err != nil {
+		return
+	}
+
+	// Collect the layer files
+	layerPng := make([]([]byte), len(rm.Layers))
+	for n := 0; n < cap(layerPng); n++ {
+		name := fmt.Sprintf("ResinSlicesData/Slice%05d.png", sliceMap[n])
+		file, ok := fileMap[name]
+		if !ok {
+			err = errors.New(fmt.Sprintf("%s: Missing from archive", name))
+			return
+		}
+		var reader io.ReadCloser
+		reader, err = file.Open()
+		if err != nil {
+			return
+		}
+		defer reader.Close()
+
+		layerPng[n], err = ioutil.ReadAll(reader)
+		if err != nil {
+			return
+		}
+	}
+
+	// Collect the thumbnails
+	thumbs := map[uv3dp.PreviewType]string{
+		uv3dp.PreviewTypeTiny: "Preview.png",
+		uv3dp.PreviewTypeHuge: "Preview.png", // There's only one preview in the archive...
+	}
+
+	thumbImage := make(map[uv3dp.PreviewType]image.Image)
+	for pt, pn := range thumbs {
+		file, ok := fileMap[pn]
+		if !ok {
+			continue
+		}
+
+		var reader io.ReadCloser
+		reader, err = file.Open()
+		if err != nil {
+			return
+		}
+		defer func() { reader.Close() }()
+
+		var thumb image.Image
+		thumb, err = png.Decode(reader)
+		if err != nil {
+			return
+		}
+		thumbImage[pt] = thumb
+	}
+
+	prop := uv3dp.Properties{}
+
+	size := &prop.Size
+	size.X = defaultPixelsX
+	size.Y = defaultPixelsY
+	size.Layers = len(rm.Layers)
+
+	size.Millimeter.X = float32(size.X) * mmPerPixel
+	size.Millimeter.Y = float32(size.Y) * mmPerPixel
+	size.LayerHeight = rm.LayerThickness
+
+	exp := &prop.Exposure
+	exp.LightOnTime = time.Duration(rm.LayerTime) * time.Millisecond
+	exp.LightOffTime = time.Duration(rm.BlankingLayerTime) * time.Millisecond
+
+	exp.LiftHeight = us.ZLiftDistance
+	exp.RetractSpeed = us.ZLiftRetractRate
+	exp.LiftSpeed = us.ZLiftFeedRate
+
+	bot := &prop.Bottom
+	bot.Exposure = *exp
+
+	bot.Exposure.LightOnTime = time.Duration(rm.BottomLayersTime) * time.Millisecond
+
+	bot.Count = rm.BottomLayersNumber
+	bot.Style = uv3dp.BottomStyleSlow
+
+	prop.Preview = thumbImage
+
+	prop.Metadata = make(map[string](interface{}))
+	prop.Metadata["zcodex/UserSettingsData"] = &us
+	prop.Metadata["zcodex/ResinMetadata"] = &rm
+
+	zcodex := &Zcodex{
+		properties: prop,
+		layerPng:   layerPng,
+	}
+
+	printable = zcodex
+
+	return
+}
+
+func (zcodex *Zcodex) Close() {
+}
+
+func (zcodex *Zcodex) Properties() (prop uv3dp.Properties) {
+	prop = zcodex.properties
+	return
+}
+
+func asGray(in image.Image) (out *image.Gray) {
+	bounds := in.Bounds()
+	out = image.NewGray(bounds)
+
+	for y := 0; y < bounds.Size().Y; y++ {
+		for x := 0; x < bounds.Size().X; x++ {
+			out.Set(x, y, color.GrayModel.Convert(in.At(x, y)))
+		}
+	}
+
+	return
+}
+
+func (zcodex *Zcodex) Layer(index int) (layer uv3dp.Layer) {
+	pngImage, err := png.Decode(bytes.NewReader(zcodex.layerPng[index]))
+	if err != nil {
+		panic(err)
+	}
+
+	grayImage, ok := pngImage.(*image.Gray)
+	if !ok {
+		grayImage = asGray(pngImage)
+	}
+
+	layer.Z = float32(index) * zcodex.properties.Size.LayerHeight
+	layer.Image = grayImage
+	exposure := zcodex.properties.LayerExposure(index)
+	layer.Exposure = &exposure
+
+	return
+}
diff --git a/zcodex/init.go b/zcodex/init.go
new file mode 100644
index 0000000..d95bf42
--- /dev/null
+++ b/zcodex/init.go
@@ -0,0 +1,16 @@
+//
+// Copyright (c) 2020 Jason S. McMullan <jason.mcmullan@gmail.com>
+//
+
+// Package zcodex handles input and output of Prusa SL1 DLP/LCD printables
+package zcodex
+
+import (
+	"github.com/ezrec/uv3dp"
+)
+
+func init() {
+	newFormatter := func(suffix string) uv3dp.Formatter { return NewZcodexFormatter(suffix) }
+
+	uv3dp.RegisterFormatter(".zcodex", newFormatter)
+}


Test patch:
diff --git a/zcodex/format_test.go b/zcodex/format_test.go
new file mode 100644
index 0000000..52c7222
--- /dev/null
+++ b/zcodex/format_test.go
@@ -0,0 +1,137 @@
+//
+// Copyright (c) 2020 Jason S. McMullan <jason.mcmullan@gmail.com>
+//
+
+// Package zcodex is a set of tools for data exchange in the Prusa SL1 format
+package zcodex
+
+import (
+	"archive/zip"
+	"bytes"
+	"encoding/json"
+	"image"
+	"image/png"
+	"io/ioutil"
+	"time"
+
+	"testing"
+
+	"github.com/ezrec/uv3dp"
+)
+
+type bufferReader struct {
+	data []byte
+}
+
+func (br *bufferReader) ReadAt(p []byte, off int64) (n int, err error) {
+	copy(p, br.data[off:])
+	n = len(p)
+
+	return
+}
+
+func (br *bufferReader) Len() int64 {
+	return int64(len(br.data))
+}
+
+var (
+	testProperties = uv3dp.Properties{
+		Size: uv3dp.Size{
+			X: 10,
+			Y: 20,
+			Millimeter: uv3dp.SizeMillimeter{
+				X: 20.0,
+				Y: 40.0,
+			},
+			Layers:      4, // 2 bottom, 2 normal
+			LayerHeight: 0.05,
+		},
+		Exposure: uv3dp.Exposure{
+			LightOnTime:   time.Millisecond * 16500,
+			LightOffTime:  time.Millisecond * 2250,
+			LiftHeight:    5.5,
+			LiftSpeed:     120.0,
+			RetractHeight: 3.3,
+			RetractSpeed:  200.0,
+		},
+		Bottom: uv3dp.Bottom{
+			Count: 2,
+			Style: uv3dp.BottomStyleFade,
+			Exposure: uv3dp.Exposure{
+				LightOnTime:   time.Millisecond * 80000,
+				LightOffTime:  time.Millisecond * 2250,
+				LiftHeight:    5.5,
+				LiftSpeed:     120.0,
+				RetractHeight: 3.3,
+				RetractSpeed:  200.0,
+			},
+		},
+		Preview: map[uv3dp.PreviewType]image.Image{
+			uv3dp.PreviewTypeTiny: image.NewRGBA(image.Rect(0, 0, 10, 10)),
+			uv3dp.PreviewTypeHuge: image.NewCMYK(image.Rect(0, 0, 20, 12)),
+		},
+		Metadata: map[string](interface{}){},
+	}
+)
+
+const (
+	testResinMetadata = `{"Guid":"62FBB25B-1E22-4B4D-A7CA-A2013F22785D","Material":"BASIC GREY","MaterialId":1,"LayerThickness":0.05,"PrintTime":16886,"LayerTime":16500,"BottomLayersTime":80000,"AdditionalSupportLayerTime":0,"BottomLayersNumber":2,"BlankingLayerTime":2250,"TotalMaterialVolumeUsed":16.21,"TotalMaterialWeightUsed":0,"TotalLayersCount":4,"DisableSettingsChanges":false,"Pauses":[],"Layers":[{"Layer":0,"UsedMaterialVolume":0},{"Layer":1,"UsedMaterialVolume":0},{"Layer":2,"UsedMaterialVolume":0},{"Layer":3,"UsedMaterialVolume":0}]}
+`
+)
+
+func TestEncodeEmptyZcodex(t *testing.T) {
+	// Collect an empty printable
+	time_Now = func() (now time.Time) { return }
+
+	buffPng := &bytes.Buffer{}
+	png.Encode(buffPng, image.NewGray(testProperties.Bounds()))
+	png_empty := buffPng.Bytes()
+
+	expected_zip := map[string]([]byte){
+		"ResinMetadata":                  []byte(testResinMetadata),
+		"ResinSlicesData/Slice00000.png": png_empty,
+		"ResinSlicesData/Slice00001.png": png_empty,
+		"ResinSlicesData/Slice00002.png": png_empty,
+		"ResinSlicesData/Slice00003.png": png_empty,
+	}
+
+	var rm ResinMetadata
+	json.Unmarshal([]byte(testResinMetadata), &rm)
+	testProperties.Metadata["zcodex/ResinMetadata"] = &rm
+
+	empty := uv3dp.NewEmptyPrintable(testProperties)
+
+	formatter := NewZcodexFormatter(".zcodex")
+
+	buffWriter := &bytes.Buffer{}
+	formatter.Encode(buffWriter, empty)
+
+	buffReader := &bufferReader{buffWriter.Bytes()}
+
+	archive, _ := zip.NewReader(buffReader, buffReader.Len())
+
+	fileMap := map[string](*zip.File){}
+	for _, file := range archive.File {
+		fileMap[file.Name] = file
+	}
+
+	for name, expected := range expected_zip {
+		file, found := fileMap[name]
+		if !found {
+			t.Errorf("%v: Not found in encoded archive", name)
+			continue
+		}
+
+		rc, _ := file.Open()
+		defer rc.Close()
+		got, _ := ioutil.ReadAll(rc)
+
+		if !bytes.Equal(expected, got) {
+			if name == "ResinMetadata!" {
+				t.Errorf("%s: expected:\n%v\n  got:\n%v", name, string(expected), string(got))
+			} else {
+				t.Errorf("%s: expected:\n%#v\n, got:\n%#v", name, expected, got)
+			}
+		}
+	}
+}