Skip to content

Commit

Permalink
Merge pull request #7 from qmuntal/decode-exts
Browse files Browse the repository at this point in the history
Decode Extensions
  • Loading branch information
qmuntal committed Feb 18, 2020
2 parents 07eba6b + 2348d6f commit 9b7b643
Show file tree
Hide file tree
Showing 44 changed files with 2,221 additions and 1,715 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The 3D Manufacturing Format (3MF) is a 3D printing format that allows design app
* Clean API.
* 3MF i/o
* [x] Read from io.ReaderAt.
* [ ] Save to io.Writer.
* [x] Save to io.Writer.
* [x] Boilerplate to read from disk.
* [x] Validation and complete non-conformity report.
* [x] Read from ASCII and Binary STL.
Expand Down Expand Up @@ -52,7 +52,6 @@ func main() {
package main

import (
"archive/zip"
"bytes"
"fmt"
"io/ioutil"
Expand All @@ -71,3 +70,20 @@ func main() {
fmt.Println(model)
}
```
### Write to file
```go
package main

import (
"fmt"
"os"

"github.com/qmuntal/go3mf"
)

func main() {
file := os.Create("/testdata/cube.3mf")
model := new(go3mf.Model)
go3mf.NewEncoder(file).Encode(model)
}
```
4 changes: 2 additions & 2 deletions archive/stl/binary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func (w *errorWriter) Write(p []byte) (n int, err error) {
return 0, nil
}

func createMeshTriangle(id uint32) *go3mf.ObjectResource {
m := go3mf.NewMeshResource()
func createMeshTriangle(id uint32) *go3mf.Object {
m := go3mf.NewMeshObject()
m.ID = id
mb := go3mf.NewMeshBuilder(m.Mesh)
n1 := mb.AddNode(go3mf.Point3D{-20.0, -20.0, 0.0})
Expand Down
7 changes: 3 additions & 4 deletions archive/stl/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (d *Decoder) DecodeContext(ctx context.Context, m *go3mf.Model) error {
if err != nil {
return err
}
newMesh := go3mf.NewMeshResource()
newMesh := go3mf.NewMeshObject()
if isASCII {
decoder := asciiDecoder{r: b}
err = decoder.decode(ctx, newMesh.Mesh)
Expand All @@ -48,9 +48,8 @@ func (d *Decoder) DecodeContext(ctx context.Context, m *go3mf.Model) error {
err = decoder.decode(ctx, newMesh.Mesh)
}
if err == nil {
newMesh.ModelPath = m.Path
newMesh.ID = m.UnusedID()
m.Resources = append(m.Resources, newMesh)
newMesh.ID = m.Resources.UnusedID()
m.Resources.Objects = append(m.Resources.Objects, newMesh)
}
return err
}
Expand Down
4 changes: 2 additions & 2 deletions archive/stl/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestDecoder_Decode(t *testing.T) {
tests := []struct {
name string
d *Decoder
want *go3mf.ObjectResource
want *go3mf.Object
wantErr bool
}{
{"empty", NewDecoder(new(bytes.Buffer)), nil, true},
Expand All @@ -57,7 +57,7 @@ func TestDecoder_Decode(t *testing.T) {
return
}
if !tt.wantErr {
if diff := deep.Equal(got.Resources[0], tt.want); diff != nil {
if diff := deep.Equal(got.Resources.Objects[0], tt.want); diff != nil {
t.Errorf("Decoder.Decode() = %v", diff)
return
}
Expand Down
8 changes: 4 additions & 4 deletions beamlattice/beamlattice.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ type Beam struct {
// MeshBeamLattice extracts the BeamLattice attributes from an Mesh.
// If it does not exist a new one is added.
func MeshBeamLattice(o *go3mf.Mesh) *BeamLattice {
if attr, ok := o.Extensions[ExtensionName]; ok {
if attr, ok := o.Extension[ExtensionName]; ok {
return attr.(*BeamLattice)
}
if o.Extensions == nil {
o.Extensions = make(go3mf.Extensions)
if o.Extension == nil {
o.Extension = make(go3mf.Extension)
}
attr := &BeamLattice{}
o.Extensions[ExtensionName] = attr
o.Extension[ExtensionName] = attr
return attr
}

Expand Down
22 changes: 9 additions & 13 deletions beamlattice/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type beamLatticeDecoder struct {
mesh *go3mf.Mesh
}

func (d *beamLatticeDecoder) Attributes(attrs []xml.Attr) {
func (d *beamLatticeDecoder) Start(attrs []xml.Attr) {
var hasRadius, hasMinLength bool
beamLattice := MeshBeamLattice(d.mesh)
for _, a := range attrs {
Expand Down Expand Up @@ -97,7 +97,7 @@ type beamsDecoder struct {
beamDecoder beamDecoder
}

func (d *beamsDecoder) Open() {
func (d *beamsDecoder) Start(_ []xml.Attr) {
d.beamDecoder.mesh = d.mesh
}

Expand All @@ -113,7 +113,7 @@ type beamDecoder struct {
mesh *go3mf.Mesh
}

func (d *beamDecoder) Attributes(attrs []xml.Attr) {
func (d *beamDecoder) Start(attrs []xml.Attr) {
beam := Beam{}
var (
hasV1, hasV2, hasCap1, hasCap2 bool
Expand Down Expand Up @@ -204,16 +204,13 @@ type beamSetDecoder struct {
beamRefDecoder beamRefDecoder
}

func (d *beamSetDecoder) Open() {
d.beamRefDecoder.beamSet = &d.beamSet
}

func (d *beamSetDecoder) Close() {
func (d *beamSetDecoder) End() {
ext := MeshBeamLattice(d.mesh)
ext.BeamSets = append(ext.BeamSets, d.beamSet)
}

func (d *beamSetDecoder) Attributes(attrs []xml.Attr) {
func (d *beamSetDecoder) Start(attrs []xml.Attr) {
d.beamRefDecoder.beamSet = &d.beamSet
for _, a := range attrs {
if a.Name.Space != "" {
continue
Expand All @@ -239,7 +236,7 @@ type beamRefDecoder struct {
beamSet *BeamSet
}

func (d *beamRefDecoder) Attributes(attrs []xml.Attr) {
func (d *beamRefDecoder) Start(attrs []xml.Attr) {
for _, a := range attrs {
if a.Name.Space == "" && a.Name.Local == attrIndex {
val, err := strconv.ParseUint(a.Value, 10, 32)
Expand All @@ -257,9 +254,8 @@ type baseDecoder struct {
Scanner *go3mf.Scanner
}

func (d *baseDecoder) Open() {}
func (d *baseDecoder) Attributes([]xml.Attr) {}
func (d *baseDecoder) Start([]xml.Attr) {}
func (d *baseDecoder) Text([]byte) {}
func (d *baseDecoder) Child(xml.Name) go3mf.NodeDecoder { return nil }
func (d *baseDecoder) Close() {}
func (d *baseDecoder) End() {}
func (d *baseDecoder) SetScanner(s *go3mf.Scanner) { d.Scanner = s }
11 changes: 6 additions & 5 deletions beamlattice/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
)

func TestDecode(t *testing.T) {
meshLattice := &go3mf.ObjectResource{
ID: 15, Name: "Box", ModelPath: "/3D/3dmodel.model",
meshLattice := &go3mf.Object{
ID: 15, Name: "Box",
Mesh: &go3mf.Mesh{
Extensions: go3mf.Extensions{
Extension: go3mf.Extension{
ExtensionName: &BeamLattice{ClipMode: ClipInside, ClippingMeshID: 8, RepresentationMeshID: 8},
}},
}
Expand Down Expand Up @@ -46,8 +46,9 @@ func TestDecode(t *testing.T) {
{NodeIndices: [2]uint32{0, 5}, Radius: [2]float32{1.5, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeButt}},
}...)

want := &go3mf.Model{Path: "/3D/3dmodel.model", Namespaces: []xml.Name{{Space: ExtensionName, Local: "b"}}}
want.Resources = append(want.Resources, meshLattice)
want := &go3mf.Model{Path: "/3D/3dmodel.model", Namespaces: []xml.Name{{Space: ExtensionName, Local: "b"}}, Resources: go3mf.Resources{
Objects: []*go3mf.Object{meshLattice},
}}
got := new(go3mf.Model)
got.Path = "/3D/3dmodel.model"
rootFile := `
Expand Down
98 changes: 98 additions & 0 deletions beamlattice/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package beamlattice

import (
"encoding/xml"
"strconv"

"github.com/qmuntal/go3mf"
)

// Marshal3MF encodes the resource.
func (m *BeamLattice) Marshal3MF(x *go3mf.XMLEncoder) error {
xs := xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrBeamLattice}, Attr: []xml.Attr{
{Name: xml.Name{Local: attrMinLength}, Value: strconv.FormatFloat(float64(m.MinLength), 'f', x.FloatPresicion(), 32)},
{Name: xml.Name{Local: attrRadius}, Value: strconv.FormatFloat(float64(m.DefaultRadius), 'f', x.FloatPresicion(), 32)},
}}
if m.ClipMode != ClipNone {
xs.Attr = append(xs.Attr, xml.Attr{Name: xml.Name{Local: attrClippingMode}, Value: m.ClipMode.String()})
}
if m.ClippingMeshID != 0 {
xs.Attr = append(xs.Attr, xml.Attr{
Name: xml.Name{Local: attrClippingMesh},
Value: strconv.FormatUint(uint64(m.ClippingMeshID), 10)},
)
}
if m.RepresentationMeshID != 0 {
xs.Attr = append(xs.Attr, xml.Attr{
Name: xml.Name{Local: attrRepresentationMesh},
Value: strconv.FormatUint(uint64(m.RepresentationMeshID), 10)},
)
}
if m.CapMode != CapModeSphere {
xs.Attr = append(xs.Attr, xml.Attr{Name: xml.Name{Local: attrCap}, Value: m.CapMode.String()})
}
x.EncodeToken(xs)

marshalBeams(x, m)
marshalBeamsets(x, m)

x.EncodeToken(xs.End())
return nil
}

func marshalBeamsets(x *go3mf.XMLEncoder, m *BeamLattice) {
xb := xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrBeamSets}}
x.EncodeToken(xb)
for _, bs := range m.BeamSets {
xbs := xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrBeamSet}}
if bs.Name != "" {
xbs.Attr = append(xbs.Attr, xml.Attr{Name: xml.Name{Local: attrName}, Value: bs.Name})
}
if bs.Identifier != "" {
xbs.Attr = append(xbs.Attr, xml.Attr{Name: xml.Name{Local: attrIdentifier}, Value: bs.Identifier})
}
x.EncodeToken(xbs)
x.SetAutoClose(true)
for _, ref := range bs.Refs {
x.EncodeToken(xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrRef}, Attr: []xml.Attr{
{Name: xml.Name{Local: attrIndex}, Value: strconv.FormatUint(uint64(ref), 10)},
}})
}
x.SetAutoClose(false)
x.EncodeToken(xbs.End())
}
x.EncodeToken(xb.End())
}

func marshalBeams(x *go3mf.XMLEncoder, m *BeamLattice) {
xb := xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrBeams}}
x.EncodeToken(xb)
x.SetAutoClose(true)
for _, b := range m.Beams {
xbeam := xml.StartElement{Name: xml.Name{Space: ExtensionName, Local: attrBeam}, Attr: []xml.Attr{
{Name: xml.Name{Local: attrV1}, Value: strconv.FormatUint(uint64(b.NodeIndices[0]), 10)},
{Name: xml.Name{Local: attrV2}, Value: strconv.FormatUint(uint64(b.NodeIndices[1]), 10)},
}}
if b.Radius[0] > 0 && b.Radius[0] != m.DefaultRadius {
xbeam.Attr = append(xbeam.Attr, xml.Attr{
Name: xml.Name{Local: attrR1},
Value: strconv.FormatFloat(float64(b.Radius[0]), 'f', x.FloatPresicion(), 32),
})
}
if b.Radius[1] > 0 && b.Radius[1] != m.DefaultRadius {
xbeam.Attr = append(xbeam.Attr, xml.Attr{
Name: xml.Name{Local: attrR2},
Value: strconv.FormatFloat(float64(b.Radius[1]), 'f', x.FloatPresicion(), 32),
})
}
if b.CapMode[0] != m.CapMode {
xbeam.Attr = append(xbeam.Attr, xml.Attr{Name: xml.Name{Local: attrCap1}, Value: b.CapMode[0].String()})
}
if b.CapMode[1] != m.CapMode {
xbeam.Attr = append(xbeam.Attr, xml.Attr{Name: xml.Name{Local: attrCap2}, Value: b.CapMode[1].String()})
}
x.EncodeToken(xbeam)
}
x.SetAutoClose(false)
x.EncodeToken(xb.End())
}
72 changes: 72 additions & 0 deletions beamlattice/encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package beamlattice

import (
"encoding/xml"
"testing"

"github.com/go-test/deep"
"github.com/qmuntal/go3mf"
)

func TestMarshalModel(t *testing.T) {
meshLattice := &go3mf.Object{
ID: 15, Name: "Box",
Mesh: &go3mf.Mesh{
Faces: []go3mf.Face{},
Extension: go3mf.Extension{
ExtensionName: &BeamLattice{ClipMode: ClipInside, ClippingMeshID: 8, RepresentationMeshID: 8},
}},
}
beamLattice := MeshBeamLattice(meshLattice.Mesh)
beamLattice.MinLength = 0.0001
beamLattice.CapMode = CapModeHemisphere
beamLattice.DefaultRadius = 1
meshLattice.Mesh.Nodes = append(meshLattice.Mesh.Nodes, []go3mf.Point3D{
{45, 55, 55},
{45, 45, 55},
{45, 55, 45},
{45, 45, 45},
{55, 55, 45},
{55, 55, 55},
{55, 45, 55},
{55, 45, 45},
}...)
beamLattice.BeamSets = append(beamLattice.BeamSets, BeamSet{Name: "test", Identifier: "set_id", Refs: []uint32{1}})
beamLattice.Beams = append(beamLattice.Beams, []Beam{
{NodeIndices: [2]uint32{0, 1}, Radius: [2]float32{1.5, 1.6}, CapMode: [2]CapMode{CapModeSphere, CapModeButt}},
{NodeIndices: [2]uint32{2, 0}, Radius: [2]float32{3, 1.5}, CapMode: [2]CapMode{CapModeSphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{1, 3}, Radius: [2]float32{1.6, 3}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{3, 2}, Radius: [2]float32{1, 1}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{2, 4}, Radius: [2]float32{3, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{4, 5}, Radius: [2]float32{2, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{5, 6}, Radius: [2]float32{2, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{7, 6}, Radius: [2]float32{2, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{1, 6}, Radius: [2]float32{1.6, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{7, 4}, Radius: [2]float32{2, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{7, 3}, Radius: [2]float32{2, 3}, CapMode: [2]CapMode{CapModeHemisphere, CapModeHemisphere}},
{NodeIndices: [2]uint32{0, 5}, Radius: [2]float32{1.5, 2}, CapMode: [2]CapMode{CapModeHemisphere, CapModeButt}},
}...)

m := &go3mf.Model{Path: "/3D/3dmodel.model", Namespaces: []xml.Name{{Space: ExtensionName, Local: "b"}}, Resources: go3mf.Resources{
Objects: []*go3mf.Object{meshLattice},
}}

t.Run("base", func(t *testing.T) {
b, err := go3mf.MarshalModel(m)
if err != nil {
t.Errorf("beamlattice.MarshalModel() error = %v", err)
return
}
d := go3mf.NewDecoder(nil, 0)
RegisterExtension(d)
newModel := new(go3mf.Model)
newModel.Path = m.Path
if err := d.UnmarshalModel(b, newModel); err != nil {
t.Errorf("beamlattice.MarshalModel() error decoding = %v, s = %s", err, string(b))
return
}
if diff := deep.Equal(m, newModel); diff != nil {
t.Errorf("beamlattice.MarshalModel() = %v, s = %s", diff, string(b))
}
})
}
Loading

0 comments on commit 9b7b643

Please sign in to comment.