Repository: ezrec/uv3dp
Pull request #86 (2020-07-14 03:25:09)

Issue 85 layer z off


Patch:
diff --git a/README.md b/README.md
index d9469fe..b56d935 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,7 @@ The command line tool is designed to be used in a 'pipeline' style, for example:
     
     Options for '.sl1':
     
+      -f, --bottom-fade            Fade bottom layers exposure time
       -m, --material-name string   config.init entry 'materialName' (default "3DM-ABS @")
     
     Options for '.uvj':
diff --git a/cbddlp/format.go b/cbddlp/format.go
index af87339..7f0a5e4 100644
--- a/cbddlp/format.go
+++ b/cbddlp/format.go
@@ -311,6 +311,13 @@ func (cf *CbddlpFormatter) Encode(writer uv3dp.Writer, p uv3dp.Printable) (err e
 	header.Projector = 1 // LCD_X_MIRROR
 
 	if header.Version >= 2 {
+		if exp.LightPWM == 0 {
+			exp.LightPWM = 255
+		}
+		if bot.Exposure.LightPWM == 0 {
+			bot.Exposure.LightPWM = 255
+		}
+
 		header.ParamOffset = paramBase
 		header.ParamSize = uint32(paramSize)
 		header.AntiAliasLevel = uint32(cf.AntiAlias)
diff --git a/cmd/uv3dp/bottom.go b/cmd/uv3dp/bottom.go
index d69441a..d39757b 100644
--- a/cmd/uv3dp/bottom.go
+++ b/cmd/uv3dp/bottom.go
@@ -5,11 +5,8 @@
 package main
 
 import (
-	"fmt"
-
-	"github.com/spf13/pflag"
-
 	"github.com/ezrec/uv3dp"
+	"github.com/spf13/pflag"
 )
 
 type BottomCommand struct {
@@ -76,19 +73,6 @@ func (cmd *BottomCommand) Filter(input uv3dp.Printable) (output uv3dp.Printable,
 		bot.Count = int(cmd.Count)
 	}
 
-	if cmd.Changed("style") {
-		TraceVerbosef(VerbosityNotice, "  Setting default bottom layer style %v", cmd.Style)
-		styleMap := map[string]uv3dp.BottomStyle{
-			"slow": uv3dp.BottomStyleSlow,
-			"fade": uv3dp.BottomStyleFade,
-		}
-		style, found := styleMap[cmd.Style]
-		if !found {
-			panic(fmt.Sprintf("exposure: Invalid --style=%v", cmd.Style))
-		}
-		bot.Style = style
-	}
-
 	if cmd.Changed("light-on") {
 		TraceVerbosef(VerbosityNotice, "  Setting default bottom time to %v", cmd.LightOnTime)
 		bot.Exposure.LightOnTime = cmd.LightOnTime
diff --git a/cmd/uv3dp/info.go b/cmd/uv3dp/info.go
index 09a7004..bbc33b5 100644
--- a/cmd/uv3dp/info.go
+++ b/cmd/uv3dp/info.go
@@ -49,15 +49,6 @@ func (info *InfoCommand) Filter(input uv3dp.Printable) (output uv3dp.Printable,
 	if info.ExposureSummary {
 		exp := &prop.Exposure
 		bot := &prop.Bottom
-		var bottomStyle string
-		switch bot.Style {
-		case uv3dp.BottomStyleSlow:
-			bottomStyle = "slow"
-		case uv3dp.BottomStyleFade:
-			bottomStyle = "fade"
-		default:
-			bottomStyle = fmt.Sprintf("%+v", bot.Style)
-		}
 
 		fmt.Printf("Exposure: %.2gs on, %.2gs off",
 			exp.LightOnTime,
@@ -72,7 +63,7 @@ func (info *InfoCommand) Filter(input uv3dp.Printable) (output uv3dp.Printable,
 		if bot.Exposure.LightPWM != 255 {
 			fmt.Printf(", PWM %v", bot.Exposure.LightPWM)
 		}
-		fmt.Printf(" (%v %v layers)\n", bot.Count, bottomStyle)
+		fmt.Printf(" (%v layers)\n", bot.Count)
 		fmt.Printf("Lift: %v mm, %v mm/min\n",
 			exp.LiftHeight, exp.LiftSpeed)
 		fmt.Printf("Retract: %v mm, %v mm/min\n",
diff --git a/cmd/uv3dp/resin_map.go b/cmd/uv3dp/resin_map.go
index 11c5283..759a042 100644
--- a/cmd/uv3dp/resin_map.go
+++ b/cmd/uv3dp/resin_map.go
@@ -79,7 +79,7 @@ func init() {
 	defResin := &Resin{
 		Name:     "",
 		Exposure: defExposure,
-		Bottom:   uv3dp.Bottom{Count: -1, Exposure: defExposure, Style: uv3dp.BottomStyleSlow},
+		Bottom:   uv3dp.Bottom{Count: -1, Exposure: defExposure},
 	}
 
 	// Set reasonable defaults
@@ -109,7 +109,7 @@ func init() {
 				resin = &Resin{
 					Name:     name,
 					Exposure: defExposure,
-					Bottom:   uv3dp.Bottom{Count: -1, Exposure: defExposure, Style: uv3dp.BottomStyleSlow},
+					Bottom:   uv3dp.Bottom{Count: -1, Exposure: defExposure},
 				}
 			}
 
@@ -170,9 +170,8 @@ func PrintResins() {
 
 	for _, key := range keys {
 		item := ResinMap[key]
-		fmt.Fprintf(os.Stderr, "    %-40s bottom %v %v layers, %v; nominal %v\n", key,
+		fmt.Fprintf(os.Stderr, "    %-40s bottom %v layers, %v; nominal %v\n", key,
 			item.Bottom.Count,
-			item.Bottom.Style,
 			item.Bottom.Exposure.LightOnTime,
 			item.Exposure.LightOnTime)
 	}
diff --git a/ctb/format.go b/ctb/format.go
index a00e81d..05f62fe 100644
--- a/ctb/format.go
+++ b/ctb/format.go
@@ -315,6 +315,14 @@ func (cf *CtbFormatter) Encode(writer uv3dp.Writer, p uv3dp.Printable) (err erro
 
 	header.AntiAliasLevel = 1
 
+	if exp.LightPWM == 0 {
+		exp.LightPWM = 255
+	}
+
+	if bot.Exposure.LightPWM == 0 {
+		bot.Exposure.LightPWM = 255
+	}
+
 	header.LightPWM = uint16(exp.LightPWM)
 	header.BottomLightPWM = uint16(bot.Exposure.LightPWM)
 
diff --git a/cws/format.go b/cws/format.go
index 0bdab6c..62d9862 100644
--- a/cws/format.go
+++ b/cws/format.go
@@ -517,7 +517,7 @@ func (cws *CWS) Layer(index int) (layer uv3dp.Layer) {
 		panic(err)
 	}
 
-	layer.Z = float32(index) * cws.properties.Size.LayerHeight
+	layer.Z = cws.properties.LayerZ(index)
 	layer.Image = pngImage.(*image.Gray)
 	layer.Exposure = cws.properties.LayerExposure(index)
 
diff --git a/empty.go b/empty.go
index 4ddba58..57faa35 100644
--- a/empty.go
+++ b/empty.go
@@ -29,7 +29,7 @@ func (empty *EmptyPrintable) Properties() (prop Properties) {
 func (empty *EmptyPrintable) Layer(index int) (layer Layer) {
 	prop := empty.Properties()
 
-	layer.Z = prop.Size.LayerHeight * float32(index)
+	layer.Z = prop.LayerZ(index)
 	layer.Image = image.NewGray(prop.Bounds())
 
 	if index < prop.Bottom.Count {
diff --git a/phz/format.go b/phz/format.go
index de31d87..7c04f34 100644
--- a/phz/format.go
+++ b/phz/format.go
@@ -275,6 +275,14 @@ func (pf *PhzFormatter) Encode(writer uv3dp.Writer, p uv3dp.Printable) (err erro
 
 	header.AntiAliasLevel = 1
 
+	if exp.LightPWM == 0 {
+		exp.LightPWM = 255
+	}
+
+	if bot.Exposure.LightPWM == 0 {
+		bot.Exposure.LightPWM = 255
+	}
+
 	header.LightPWM = uint16(exp.LightPWM)
 	header.BottomLightPWM = uint16(bot.Exposure.LightPWM)
 
diff --git a/properties.go b/properties.go
index 4f053a3..c42f961 100644
--- a/properties.go
+++ b/properties.go
@@ -5,8 +5,8 @@
 package uv3dp
 
 import (
-	"fmt"
 	"image"
+	"math"
 )
 
 type SizeMillimeter struct {
@@ -24,11 +24,11 @@ type Size struct {
 type Exposure struct {
 	LightOnTime   float32 // Exposure time
 	LightOffTime  float32 // Cool down time
-	LightPWM      uint8   // PWM from 1..255
+	LightPWM      uint8   `json:",omitempty"` // PWM from 1..255
 	LiftHeight    float32 // mm
 	LiftSpeed     float32 // mm/min
-	RetractHeight float32 // mm
-	RetractSpeed  float32 // mm/min
+	RetractHeight float32 `json:",omitempty"` // mm
+	RetractSpeed  float32 `json:",omitempty"` // mm/min
 }
 
 // Total duration of an exposure
@@ -53,30 +53,10 @@ func (exp *Exposure) Interpolate(target Exposure, scale float32) (result Exposur
 	return
 }
 
-// Bottom layer style
-type BottomStyle int
-
-const (
-	BottomStyleSlow = BottomStyle(iota) // Abruptly transition from slow to normal exposure
-	BottomStyleFade                     // Gradually transition for slow to normal layers
-)
-
-func (bs BottomStyle) String() string {
-	switch bs {
-	case BottomStyleSlow:
-		return "slow"
-	case BottomStyleFade:
-		return "fade"
-	default:
-		return "unknown"
-	}
-}
-
 // Bottom layer exposure
 type Bottom struct {
-	Exposure             // Exposure
-	Count    int         // Number of bottom layers
-	Style    BottomStyle // Transition style
+	Exposure     // Exposure
+	Count    int // Number of bottom layers
 }
 
 type PreviewType uint
@@ -135,14 +115,12 @@ func (prop *Properties) LayerExposure(index int) (exposure Exposure) {
 		return
 	}
 
-	switch prop.Bottom.Style {
-	case BottomStyleSlow:
-		exposure = prop.Bottom.Exposure
-	case BottomStyleFade:
-		exposure = prop.Bottom.Exposure.Interpolate(prop.Exposure, float32(index)/float32(prop.Bottom.Count))
-	default:
-		panic(fmt.Sprintf("Unknown Properties.Bottom.Style: %+v", prop.Bottom.Style))
-	}
+	exposure = prop.Bottom.Exposure
 
 	return
 }
+
+// LayerZ get the default Z height at a layer index
+func (prop *Properties) LayerZ(index int) (z float32) {
+	return float32(math.Round(float64(prop.Size.LayerHeight)*float64(index+1)*100) / 100.0)
+}
diff --git a/pws/format.go b/pws/format.go
index 90a89ef..e966714 100644
--- a/pws/format.go
+++ b/pws/format.go
@@ -589,7 +589,7 @@ func (pws *Print) Layer(index int) (layer uv3dp.Layer) {
 		panic(fmt.Sprintf("pws: layer %v/%v: %s", index+1, prop.Size.Layers, err))
 	}
 
-	layer.Z = float32(index) * prop.Size.LayerHeight
+	layer.Z = prop.LayerZ(index)
 	layer.Image = slice
 	return
 }
diff --git a/sl1/format.go b/sl1/format.go
index 7889b81..648a56f 100644
--- a/sl1/format.go
+++ b/sl1/format.go
@@ -139,6 +139,7 @@ type Sl1Format struct {
 	*pflag.FlagSet
 
 	MaterialName string
+	BottomFade   bool
 }
 
 func NewSl1Formatter(suffix string) (sf *Sl1Format) {
@@ -149,6 +150,7 @@ func NewSl1Formatter(suffix string) (sf *Sl1Format) {
 	}
 
 	sf.StringVarP(&sf.MaterialName, "material-name", "m", "3DM-ABS @", "config.init entry 'materialName'")
+	sf.BoolVarP(&sf.BottomFade, "bottom-fade", "f", false, "Fade bottom layers exposure time")
 	sf.SetInterspersed(false)
 
 	return
@@ -179,7 +181,7 @@ func (sf *Sl1Format) Encode(writer uv3dp.Writer, printable uv3dp.Printable) (err
 
 	numFade := 0
 	numSlow := prop.Bottom.Count
-	if prop.Bottom.Style == uv3dp.BottomStyleFade {
+	if sf.BottomFade {
 		numFade = prop.Bottom.Count
 		numSlow = 0
 	}
@@ -361,10 +363,8 @@ func (sf *Sl1Format) Decode(reader uv3dp.Reader, filesize int64) (printable uv3d
 
 	if config.numFade > 0 {
 		bot.Count = int(config.numFade)
-		bot.Style = uv3dp.BottomStyleFade
 	} else {
 		bot.Count = int(config.numSlow)
-		bot.Style = uv3dp.BottomStyleSlow
 	}
 
 	exp := &prop.Exposure
@@ -409,7 +409,7 @@ func (sl1 *Sl1) Layer(index int) (layer uv3dp.Layer) {
 		panic(err)
 	}
 
-	layer.Z = float32(index) * sl1.properties.Size.LayerHeight
+	layer.Z = sl1.properties.LayerZ(index)
 	layer.Image = pngImage.(*image.Gray)
 	layer.Exposure = sl1.properties.LayerExposure(index)
 
diff --git a/uvj/format.go b/uvj/format.go
index 64c9e0d..22662ce 100644
--- a/uvj/format.go
+++ b/uvj/format.go
@@ -79,6 +79,14 @@ func (sf *UVJFormat) Encode(writer uv3dp.Writer, printable uv3dp.Printable) (err
 	preview := prop.Preview
 	prop.Preview = nil
 
+	// If LightPWM is set to 255, don't encode it
+	if prop.Exposure.LightPWM == 255 {
+		prop.Exposure.LightPWM = 0
+	}
+	if prop.Bottom.Exposure.LightPWM == 255 {
+		prop.Bottom.Exposure.LightPWM = 0
+	}
+
 	config := UVJConfig{
 		Properties: prop,
 		Layers:     make([]uv3dp.Layer, prop.Size.Layers),
@@ -99,6 +107,11 @@ func (sf *UVJFormat) Encode(writer uv3dp.Writer, printable uv3dp.Printable) (err
 			return
 		}
 
+		// Trigger the JSON 'omitdefault' as needed
+		if layer.Exposure.LightPWM == 255 {
+			layer.Exposure.LightPWM = 0
+		}
+
 		layer.Image = nil
 		config.Layers[n] = layer
 	})
@@ -179,11 +192,16 @@ func (sf *UVJFormat) Decode(reader uv3dp.Reader, filesize int64) (printable uv3d
 
 	var config UVJConfig
 
+	// Set some non-zero defaults
+	config.Properties.Exposure.LightPWM = 255
+	config.Properties.Bottom.Exposure.LightPWM = 255
+
 	err = json.Unmarshal(data, &config)
 	if err != nil {
 		return
 	}
 
+	// Check layers
 	if len(config.Layers) > 0 && len(config.Layers) != config.Properties.Size.Layers {
 		err = fmt.Errorf("config.json: expected %v layers, found %v layers", config.Properties.Size.Layers, config.Layers)
 		return
@@ -269,7 +287,7 @@ func (uvj *UVJ) Layer(index int) (layer uv3dp.Layer) {
 
 	if len(uvj.Config.Layers) == 0 {
 		layer = uv3dp.Layer{
-			Z:        float32(index) * uvj.Config.Properties.Size.LayerHeight,
+			Z:        uvj.Config.Properties.LayerZ(index),
 			Exposure: uvj.Config.Properties.LayerExposure(index),
 		}
 	} else {
diff --git a/zcodex/format.go b/zcodex/format.go
index 6e5e03c..ab7d3a7 100644
--- a/zcodex/format.go
+++ b/zcodex/format.go
@@ -369,7 +369,6 @@ func (sf *ZcodexFormat) Decode(reader uv3dp.Reader, filesize int64) (printable u
 	bot.Exposure.LightPWM = 255
 
 	bot.Count = rm.BottomLayersNumber
-	bot.Style = uv3dp.BottomStyleSlow
 
 	prop.Preview = thumbImage
 
@@ -419,7 +418,7 @@ func (zcodex *Zcodex) Layer(index int) (layer uv3dp.Layer) {
 		grayImage = asGray(pngImage)
 	}
 
-	layer.Z = float32(index) * zcodex.properties.Size.LayerHeight
+	layer.Z = zcodex.properties.LayerZ(index)
 	layer.Image = grayImage
 	layer.Exposure = zcodex.properties.LayerExposure(index)
 


Test patch:
diff --git a/cbddlp/format_empty_test.go b/cbddlp/format_empty_test.go
index 95cb808..4e14379 100644
--- a/cbddlp/format_empty_test.go
+++ b/cbddlp/format_empty_test.go
@@ -31,9 +31,9 @@ var (
 			LayerHeight: 0.05,
 		},
 		Exposure: uv3dp.Exposure{
-			LightOnTime:   16.500,
-			LightOffTime:  2.250,
-			LightPWM:      255,
+			LightOnTime:   11.500,
+			LightOffTime:  4.250,
+			LightPWM:      123,
 			LiftHeight:    5.5,
 			LiftSpeed:     120.0,
 			RetractHeight: defaultRetractHeight + 1.0,
@@ -42,13 +42,13 @@ var (
 		Bottom: uv3dp.Bottom{
 			Count: 2,
 			Exposure: uv3dp.Exposure{
-				LightOnTime:   16.500,
+				LightOnTime:   61.500,
 				LightOffTime:  2.250,
 				LightPWM:      255,
 				LiftHeight:    5.5,
-				LiftSpeed:     120.0,
+				LiftSpeed:     90.0,
 				RetractHeight: defaultRetractHeight + 2.0,
-				RetractSpeed:  200.0,
+				RetractSpeed:  100.0,
 			},
 		},
 		Preview: map[uv3dp.PreviewType]image.Image{
@@ -57,43 +57,9 @@ var (
 		},
 	})
 
-	emptyRawV1 = []byte{0x19, 0x0, 0xfd, 0x12, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42,
-		0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40,
-		0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0,
-		0xb8, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0,
-		0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, 0x4b}
-
-	emptyRawV2 = []byte{0x19, 0x0, 0xfd, 0x12, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42,
-		0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x00, 0x00, 0x10, 0x40,
-		0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0,
-		0xf4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0,
-		0x1, 0x0, 0x0, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
-		0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0xff, 0xff, 0xef, 0x30, 0xa,
-		0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x20, 0x0, 0x63, 0x30, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0,
-		0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x10, 0x40,
-		0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x84, 0x41,
-		0x00, 0x00, 0x10, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41,
-		0x00, 0x00, 0x10, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x00, 0x00, 0x10, 0x40, 0x84, 0x1,
-		0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41,
-		0x00, 0x00, 0x10, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, 0x4b}
+	emptyRawV1 = []byte{0x19, 0x0, 0xfd, 0x12, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x88, 0x40, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x9f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x10, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x88, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x88, 0x40, 0x48, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, 0x4b}
+
+	emptyRawV2 = []byte{0x19, 0x0, 0xfd, 0x12, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x88, 0x40, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0xf4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x9f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xb4, 0x42, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x40, 0x0, 0x0, 0x88, 0x40, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x10, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x76, 0x42, 0x0, 0x0, 0x10, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x88, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0x38, 0x41, 0x0, 0x0, 0x88, 0x40, 0x84, 0x1, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, 0x4b}
 )
 
 type bufferMap struct {
@@ -140,6 +106,7 @@ func TestEmptyToRaw(t *testing.T) {
 		emptyRaw := item.Raw
 		if !bytes.Equal(encoded_buff, emptyRaw) {
 			t.Errorf("%v: expected [%d byte encoding], got [%d byte encoding]", item.Format, len(emptyRaw), len(encoded_buff))
+			t.Errorf("%#v", encoded_buff)
 			if len(encoded_buff) == len(emptyRaw) {
 				for i := 0; i < len(emptyRaw)-3; i += 4 {
 					a := binary.LittleEndian.Uint32(emptyRaw[i : i+4])
@@ -159,10 +126,13 @@ func fixupV1(prop *uv3dp.Properties) {
 	prop.Exposure.RetractSpeed = defaultRetractSpeed
 	prop.Exposure.LiftHeight = defaultLiftHeight
 	prop.Exposure.LiftSpeed = defaultLiftSpeed
+	prop.Exposure.LightPWM = 123
 	prop.Bottom.Exposure.RetractHeight = defaultRetractHeight
 	prop.Bottom.Exposure.RetractSpeed = defaultRetractSpeed
 	prop.Bottom.Exposure.LiftHeight = defaultLiftHeight
 	prop.Bottom.Exposure.LiftSpeed = defaultLiftSpeed
+	prop.Bottom.Exposure.LightPWM = 255
+	prop.Bottom.Exposure.LightOffTime = 2.25
 	prop.Preview = nil
 }
 
@@ -170,6 +140,7 @@ func fixupV2(prop *uv3dp.Properties) {
 	// Fill in all the properties that the V2 (.cbddlp) format can't save
 	prop.Exposure.RetractHeight = defaultRetractHeight
 	prop.Bottom.Exposure.RetractHeight = defaultRetractHeight
+	prop.Bottom.Exposure.RetractSpeed = prop.Exposure.RetractSpeed
 	prop.Preview = nil
 }
 
@@ -202,7 +173,7 @@ func TestRawToEmpty(t *testing.T) {
 		item.Fixup(&rProp)
 
 		if !cmp.Equal(eProp, rProp) {
-			t.Errorf("expected input printable to match expected printable!")
+			t.Errorf("%v: expected input printable to match expected printable!", item.Format)
 			t.Logf("%+v", eProp)
 			t.Logf("%+v", rProp)
 		}
diff --git a/ctb/format_empty_test.go b/ctb/format_empty_test.go
index 474032c..0f7903e 100644
--- a/ctb/format_empty_test.go
+++ b/ctb/format_empty_test.go
@@ -56,7 +56,7 @@ var (
 		},
 	})
 
-	emptyRaw = []byte{0x86, 0x0, 0xfd, 0x12, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x47, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x42, 0x4, 0xcb, 0x9a, 0xf4, 0x0, 0x0, 0x0, 0x4c, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x40, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x78, 0x56, 0x34, 0x12, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xd7, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xdc, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xe0, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xe4, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x61, 0x23, 0x0, 0x0, 0x7e, 0x35, 0x46, 0x0, 0xfd, 0xa, 0xf9, 0x0, 0x7c, 0xde, 0x1c}
+	emptyRaw = []byte{0x86, 0x0, 0xfd, 0x12, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x47, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x94, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x42, 0x4, 0xcb, 0x9a, 0xf4, 0x0, 0x0, 0x0, 0x4c, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x40, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x78, 0x56, 0x34, 0x12, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xd7, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xdc, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xe0, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xe4, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x61, 0x23, 0x0, 0x0, 0x7e, 0x35, 0x46, 0x0, 0xfd, 0xa, 0xf9, 0x0, 0x7c, 0xde, 0x1c}
 )
 
 type bufferMap struct {
diff --git a/cws/format_test.go b/cws/format_test.go
index 5e484cd..b6c5c98 100644
--- a/cws/format_test.go
+++ b/cws/format_test.go
@@ -56,7 +56,6 @@ var (
 		},
 		Bottom: uv3dp.Bottom{
 			Count: 2,
-			Style: uv3dp.BottomStyleFade,
 			Exposure: uv3dp.Exposure{
 				LightOnTime:   16.500,
 				LightOffTime:  2.250,
diff --git a/phz/format_empty_test.go b/phz/format_empty_test.go
index 949caae..e4f67e4 100644
--- a/phz/format_empty_test.go
+++ b/phz/format_empty_test.go
@@ -56,7 +56,7 @@ var (
 		},
 	})
 
-	emptyRaw = []byte{0xae, 0x83, 0xda, 0x9f, 0x2, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xd8, 0x0, 0x0, 0x0, 0x27, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x40, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xf8, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x1c, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4}
+	emptyRaw = []byte{0xae, 0x83, 0xda, 0x9f, 0x2, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x84, 0x41, 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xd8, 0x0, 0x0, 0x0, 0x27, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0xa0, 0x41, 0x0, 0x0, 0x20, 0x42, 0x0, 0x0, 0x1b, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x40, 0x0, 0x0, 0x10, 0x40, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0xb0, 0x40, 0x0, 0x0, 0xf0, 0x42, 0x0, 0x0, 0x48, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xf8, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xef, 0x30, 0xa, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x1c, 0x1, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x63, 0x30, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0xcd, 0xcc, 0x4c, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0xcc, 0x3d, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0x99, 0x19, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0xcc, 0x4c, 0x3e, 0x0, 0x0, 0x84, 0x41, 0x0, 0x0, 0x10, 0x40, 0xb7, 0x1, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4, 0x80, 0x4}
 )
 
 type bufferMap struct {
@@ -101,6 +101,7 @@ func TestEmptyToRaw(t *testing.T) {
 		emptyRaw := item.Raw
 		if !bytes.Equal(encoded_buff, emptyRaw) {
 			t.Errorf("%v: expected [%d byte encoding], got [%d byte encoding]", item.Format, len(emptyRaw), len(encoded_buff))
+			t.Errorf("%+#v", encoded_buff)
 			if len(encoded_buff) == len(emptyRaw) {
 				for i := 0; i < len(emptyRaw)-3; i += 4 {
 					a := binary.LittleEndian.Uint32(emptyRaw[i : i+4])
diff --git a/sl1/format_test.go b/sl1/format_test.go
index 5592c4d..5e94e0a 100644
--- a/sl1/format_test.go
+++ b/sl1/format_test.go
@@ -56,7 +56,6 @@ var (
 		},
 		Bottom: uv3dp.Bottom{
 			Count: 2,
-			Style: uv3dp.BottomStyleFade,
 			Exposure: uv3dp.Exposure{
 				LightOnTime:   16.500,
 				LightOffTime:  2.250,
@@ -112,6 +111,7 @@ func TestEncodeEmptySl1(t *testing.T) {
 	empty := uv3dp.NewEmptyPrintable(testProperties)
 
 	formatter := NewSl1Formatter(".sl1")
+	formatter.BottomFade = true
 
 	buffWriter := &bytes.Buffer{}
 	formatter.Encode(buffWriter, empty)
diff --git a/uvj/format_test.go b/uvj/format_test.go
index 5905e57..d08c3ae 100644
--- a/uvj/format_test.go
+++ b/uvj/format_test.go
@@ -49,7 +49,7 @@ var (
 		Exposure: uv3dp.Exposure{
 			LightOnTime:   16.500,
 			LightOffTime:  2.250,
-			LightPWM:      255,
+			LightPWM:      123,
 			LiftHeight:    5.5,
 			LiftSpeed:     120.0,
 			RetractHeight: 3.3,
@@ -57,9 +57,8 @@ var (
 		},
 		Bottom: uv3dp.Bottom{
 			Count: 2,
-			Style: uv3dp.BottomStyleFade,
 			Exposure: uv3dp.Exposure{
-				LightOnTime:   16.500,
+				LightOnTime:   30.500,
 				LightOffTime:  2.250,
 				LightPWM:      255,
 				LiftHeight:    5.5,
@@ -91,31 +90,28 @@ const (
     "Exposure": {
       "LightOnTime": 16.5,
       "LightOffTime": 2.25,
-      "LightPWM": 255,
+      "LightPWM": 123,
       "LiftHeight": 5.5,
       "LiftSpeed": 120,
       "RetractHeight": 3.3,
       "RetractSpeed": 200
     },
     "Bottom": {
-      "LightOnTime": 16.5,
+      "LightOnTime": 30.5,
       "LightOffTime": 2.25,
-      "LightPWM": 255,
       "LiftHeight": 5.5,
       "LiftSpeed": 120,
       "RetractHeight": 3.3,
       "RetractSpeed": 200,
-      "Count": 2,
-      "Style": 1
+      "Count": 2
     }
   },
   "Layers": [
     {
-      "Z": 0,
+      "Z": 0.05,
       "Exposure": {
-        "LightOnTime": 16.5,
+        "LightOnTime": 30.5,
         "LightOffTime": 2.25,
-        "LightPWM": 255,
         "LiftHeight": 5.5,
         "LiftSpeed": 120,
         "RetractHeight": 3.3,
@@ -123,11 +119,10 @@ const (
       }
     },
     {
-      "Z": 0.05,
+      "Z": 0.1,
       "Exposure": {
-        "LightOnTime": 16.5,
+        "LightOnTime": 30.5,
         "LightOffTime": 2.25,
-        "LightPWM": 255,
         "LiftHeight": 5.5,
         "LiftSpeed": 120,
         "RetractHeight": 3.3,
@@ -135,11 +130,11 @@ const (
       }
     },
     {
-      "Z": 0.1,
+      "Z": 0.15,
       "Exposure": {
         "LightOnTime": 16.5,
         "LightOffTime": 2.25,
-        "LightPWM": 255,
+        "LightPWM": 123,
         "LiftHeight": 5.5,
         "LiftSpeed": 120,
         "RetractHeight": 3.3,
@@ -147,11 +142,11 @@ const (
       }
     },
     {
-      "Z": 0.15,
+      "Z": 0.2,
       "Exposure": {
         "LightOnTime": 16.5,
         "LightOffTime": 2.25,
-        "LightPWM": 255,
+        "LightPWM": 123,
         "LiftHeight": 5.5,
         "LiftSpeed": 120,
         "RetractHeight": 3.3,
@@ -207,10 +202,10 @@ func TestEncodeEmptyUVJ(t *testing.T) {
 		got, _ := ioutil.ReadAll(rc)
 
 		if !bytes.Equal(expected, got) {
-			if strings.HasSuffix(name, ".json_") {
+			if strings.HasSuffix(name, ".json") {
 				t.Errorf("%s: expected:\n%v\n  got:\n%v", name, string(expected), string(got))
 			} else {
-				t.Errorf("%s: expected %d bytes, got %d bytes", name, expected, got)
+				t.Errorf("%s: expected %d bytes, got %d bytes", name, len(expected), len(got))
 			}
 		}
 	}
diff --git a/zcodex/format_test.go b/zcodex/format_test.go
index 4f3f5b4..59dd01a 100644
--- a/zcodex/format_test.go
+++ b/zcodex/format_test.go
@@ -56,7 +56,6 @@ var (
 		},
 		Bottom: uv3dp.Bottom{
 			Count: 2,
-			Style: uv3dp.BottomStyleFade,
 			Exposure: uv3dp.Exposure{
 				LightOnTime:   80.000,
 				LightOffTime:  2.250,