GithubHelp home page GithubHelp logo

deadsy / sdfx Goto Github PK

View Code? Open in Web Editor NEW
503.0 17.0 51.0 8.31 MB

A simple CAD package using signed distance functions

License: MIT License

Go 68.07% Makefile 0.07% Python 31.86%
signed-distance-functions cad 3d-printing 3d-models signed-distance-field go golang stl 3mf dxf

sdfx's Introduction

Go Report Card GoDoc

sdfx

A simple CAD package written in Go (https://golang.org/)

  • Objects are modelled with 2d and 3d signed distance functions (SDFs).
  • Objects are defined with Go code.
  • Objects are rendered to an STL/3MF file to be viewed and/or 3d printed.

How To

  1. See the examples.
  2. Write some Go code to define your own object.
  3. Build and run the Go code.
  4. Preview the output in an 3d file viewer (e.g. http://www.meshlab.net/)
  5. Print the STL/3MF file if you like it enough.

SDF Viewer Go or SDFX-UI allow faster development iterations, replacing steps 3 and 4 until the final build.

Why?

  • SDFs make CSG easy.
  • As a language Golang > OpenSCAD.
  • SDFs can easily do filleting and chamfering (hard to do with OpenSCAD).
  • SDFs are hackable to try out oddball ideas.

Development

Gallery

wheel core_box cylinder_head msquare axoloti text gyroid icosahedron cc16a cc16b cc18b cc18c gear camshaft geneva nutsandbolts extrude1 extrude2 bezier1 bezier2 voronoi

sdfx's People

Contributors

danieltwagner avatar deadsy avatar dependabot[bot] avatar gmlewis avatar koshchei avatar mastercactapus avatar megidd avatar oct2pus avatar ryan-pelo avatar soypat avatar stevegt avatar yeicor avatar zostay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sdfx's Issues

Removing returned errors from shape generation functions

Background

I had previously mentioned in this issue:

An error on a function like Cylinder3D can only be handled one way really: correcting the argument to it in the source code as one generates the shape! This is even implied with the implementation of the ErrMsg function: it includes the line number of the function that yielded the error. panic already does that and saves us having to formally handle the error message.

@deadsy has mentioned the reason for this

It was done this way because: [...]
I wanted to support the programmatic generation of shapes (E.g. evolutionary algorithms) and that implied bad parameter values needed a non-panic response.

I initially agreed it was desirable that functions do not panic but I have now rolled back on this opinion. The reason being most users (+99% I'd guess) will suffer from this decision needlessly. My argument in this issue is that we can get the best of both worlds without compromising on robustness of an SDF program which requires this strict error handling. Let me explain.

Proposal

I propose changing

func Cylinder(radius, round float64) (SDF3, error)

to

func Cylinder(radius, round float64) SDF3 // panic on error

It is proposed that:

  • incomplete/impossible geometry parameters cause a panic.
  • No parameter correcting be done under the covers

I argue most users today do not make use of error handling of sdfx errors and this will be a welcome change.

Impact

Pros

  • Will positively impact the sdfx user developer experience
  • Simplify internal implementations greatly.
  • More familiar usage for people coming from simpler languages

Cons

  • Eliminate sdf error handling

Impact mitigation

If error handling were required then wrapper functions can be declared in the future with little effort. See this example:
https://go.dev/play/p/lMZnaJV7o4Y

How to duplicate SDF?

I have an sdf.SDF3 instance. What's the best way to duplicate or clone it? I couldn't find a function for it. I'm not sure how to do it. Thanks.

md5sum regression

Hey Jason, we were pretty happy when we saw you merge #30 -- thanks. We put a lot of work into that tool, with days of testing and debate over implementation. In case you haven't figured it out yet, you essentially have a team of paid devs available to you right now. The way we see this, you've got your head wrapped around the low-level geometry and sdf math; our best contribution, with a couple of us having decades of dev, sysadmin, and test harness experience in multiple languages and platforms, is testing, error handling, API use cases, and other package infrastructure. I think sdfx can go pretty far if we team up on this right.

For instance, I could have written a Python or Bash version pretty quickly, but we wrote the md5sum tool in Go so it would be more portable. Going back to Python doesn't seem right for a Go package. There are some things I might do differently in md5tool/main.go myself, but balancing my own preferences versus those of team members is something I have to consider as well; I had decided to defer md5tool cleanup until later commits.

For another example, the new ./mk/example.mk doesn't work on a Mac -- @koshchei is on a Mac, for instance. We used Go's md5 algo, because on a Mac the CLI command is named md5 rather than md5sum, and the output is radically different than Linux.

So can you pretty please revert this weekend's changes, looks like mostly ac530c6? If you need me to do it, I'd be happy to do the grunt work.

I'd keep the common example.mk -- we were thinking of doing something like that, probably putting it under examples/example.mk, but decided not to tackle that on Friday because we are trying not to overwhelm you with pull requests. We had only gotten as far as thinking of using symlinks; I like your include method better, again because it will be more cross-platform.

Thanks again,

Steve

Openscad minkowski or hull possible?

Wanted to port some openscad into SDF however I don't see anything like these two functions available. Is it something that's not possible in SDF?

STL is not manifold

First, thank you for building this amazing project!
I was trying to programmatically generate 3d-printable enclosures and came across sdfx and it seems like a very good fit.

I ran examples/box/main.go but when loading the resulting top.stl file in Cura I get a message saying that my model is not manifold, which I take to mean that it doesn't have closed surfaces all around.

I can't really see any marked areas so perhaps those are internal? Potentially related, I also notice that the resulting STL file is very large for the given geometry, which I take is because of the marching cubes algorithm used in sdfx?

screenshot

The bottom is marked red but I believe that's different from Cura's way of marking non-manifold areas (red + green).
Any suggestions for how to address this?

Feature Request: Connectors

I'm trying to use your library to design a laser cutter and it would be really helpful if I could add "connectors" to parts and then call a function to connect them together. Basically something like this as discussed here.

The general idea being to store a collection of locations and orientations with each object that get changed as object does so that the locations and orientations remain fixed relative to the part. Then connecting two parts can be done by calculating the transform required to align the connector on the second part with the connector on the first part and applying the transform to the second part.

I made a couple attempts at adding connectors myself but I couldn't figure out how to do it without major changes to the API. I'm new to golang which doesn't help.

I had a little bit of success by storing the connectors separately and creating a transform function that could take a map of connectors in addition to the SDF, but handling the connectors separately would quickly become tedious.

If you could point me in the direction of the best way to store a map[string]Connector with the SDF, I'll take a stab at implementing the rest of it myself.

Guidance in creating a manifold

Know how to 3d model and program in Go so this seems way better than dealing with openscad. [Want to create a 6 in to dual 4 in manifold for my dust collector.
manifold

There are many parametric details to this and seemed like the perfect project to use sdfx for but don't know where to get started.

Please add a LICENSE file to this project

First off, thank you for this very cool project!

It would be great if you could please add a LICENSE file to this project so that it can be more widely used.
Preferably, something like the Apache-2.0, MIT, or BSD license would be used, but that is obviously your choice.

Thanks!

test cases

We've already done a bunch of test case cleanup while working #24. I'm worried that some of the stuff we've done so far, not to mention the API refactoring we're talking about in #22 and #16, could silently break or subtly alter the output of one of the examples and we might not catch it.

So @koshchei is right now working on a platform-neutral tool in Go that hashes the output files in the examples. We'd run that on a version from a few days ago before all of the current work started, and it would store the hashes in something like examples/*/.sums.

Every test run after that would re-hash after running the example tests and compare the results with the reference hashes. We'll see how this goes, but I think it could work.

We're looking at storing just the hashes rather than the output files themselves due to the fact that, right now, the .dxf, .stl, .svg, and .png output files together total up to over a gigabyte in size already. ;-)

Rounded union results in a bump and a missing face

Probably related to Haskell-Things/ImplicitCAD#204 except your implementation miscalculates the bounding box.
Example code:

         box1 := sdf.Box2D(sdf.V2{10, 10}, 0)
         box2 := sdf.Box2D(sdf.V2{10, 20}, 0)
         box2 = sdf.Transform2D(box2, sdf.Translate2d(sdf.V2{10, 5}))
         uni2d := sdf.Union2D(box1, box2)
         uni2d.(*sdf.UnionSDF2).SetMin(sdf.RoundMin(10.0))
         uni := sdf.Extrude3D(uni2d, 1)
         render.RenderSTL(sdf.ScaleUniform3D(uni, shrink), 400, "test.stl")

Result:
screenshot-messed

How to check if intersection is empty or not

I have a code like this, checking the intersection/collision of some SDFs with each other:

	for i, sdfi := range mySDFs {
		for j, sdfj := range mySDFs {
			if i == j {
				continue
			}

			sdfCollision := sdf.Intersect3D(sdfi, sdfj)
			// TODO: How to check if the intersection is empty or not?
		}
	}

I'm not sure how to check if sdfCollision is actually empty or not. I mean, I intend to figure out whether any two 3D models are intersecting with each other.

Sorry if it's not the best question to ask. But for some reason I cannot figure it out.

Initialize a 4x4 matrix i.e. `sdf.M44`

Problem

There is no public method to declare a new instance of sdf.M44 with the initialization of its values.

The fields of the sdf.M44 are all private:

// M44 is a 4x4 matrix.
type M44 struct {
	x00, x01, x02, x03 float64
	x10, x11, x12, x13 float64
	x20, x21, x22, x23 float64
	x30, x31, x32, x33 float64
}

Why

The reason that I'm running into this problem is that I need to create a Quaternion type:

import (
	"math"

	"github.com/deadsy/sdfx/sdf"
	v3 "github.com/deadsy/sdfx/vec/v3"
)

// Quaternion represents a quaternion.
type Quaternion struct {
	W, X, Y, Z float64
}

// FromVectors creates a quaternion representing the rotation from vector a to vector b.
func FromVectors(a, b v3.Vec) Quaternion {
	v := a.Cross(b)
	w := math.Sqrt(a.Length2()*b.Length2()) + a.Dot(b)
	q := Quaternion{W: w, X: v.X, Y: v.Y, Z: v.Z}
	return q.Normalize()
}

// Normalize normalizes the quaternion.
func (q Quaternion) Normalize() Quaternion {
	length := math.Sqrt(q.W*q.W + q.X*q.X + q.Y*q.Y + q.Z*q.Z)
	return Quaternion{W: q.W / length, X: q.X / length, Y: q.Y / length, Z: q.Z / length}
}

// ToMatrix converts the quaternion to a 4x4 matrix.
func (q Quaternion) ToMatrix() sdf.M44 {
	xx := q.X * q.X
	yy := q.Y * q.Y
	zz := q.Z * q.Z
	xy := q.X * q.Y
	xz := q.X * q.Z
	yz := q.Y * q.Z
	wx := q.W * q.X
	wy := q.W * q.Y
	wz := q.W * q.Z

	return sdf.M44{
		1 - 2*(yy+zz), 2 * (xy - wz), 2 * (xz + wy), 0,
		2 * (xy + wz), 1 - 2*(xx+zz), 2 * (yz - wx), 0,
		2 * (xz - wy), 2 * (yz + wx), 1 - 2*(xx+yy), 0,
		0, 0, 0, 1,
	}
}

But, the last statement cannot be done, I mean:

	return sdf.M44{
		1 - 2*(yy+zz), 2 * (xy - wz), 2 * (xz + wy), 0,
		2 * (xy + wz), 1 - 2*(xx+zz), 2 * (yz - wx), 0,
		2 * (xz - wy), 2 * (yz + wx), 1 - 2*(xx+yy), 0,
		0, 0, 0, 1,
	}

There is no way to initialize a sdf.M44, as far as I understand. Am I right? 🙄

Better STL Generation

Problem: The marching cubes algorithm gives STLs that are large in size and have aliasing defects on sharp edges.

Solution: Have a mesh generation algorithm that is adaptive to the curvature of the object. ie- big triangles on flat surfaces, small triangles for sharper curves. A quality metric could allow trade-offs between file size and mesh accuracy.

Build error

Having updated to the latest version by go get -u all and then building, an error is thrown:

..\..\pkg\mod\github.com\deadsy\[email protected]\sdf\triangle3.go:73:9: cannot use r (variable of type rtreego.Rect) as *rtreego.Rect value in return statement

2024-01-06 04_11_06-Developer Command Prompt for VS 2022

The error is related to this statement:

// Bounds returns a r-tree bounding rectangle for the triangle.
func (t *Triangle3) Bounds() *rtreego.Rect {
	b := t.BoundingBox()
	r, _ := rtreego.NewRectFromPoints(v3ToPoint(b.Min), v3ToPoint(b.Max))
	return r // ** error here
}

2024-01-06 04_15_47-triangle3 go - sdfx - Visual Studio Code

Description Language idea..

Was looking at for example https://github.com/deadsy/sdfx/blob/master/examples/bolt_container/main.go and it’s pretty clear that we could make a text file based description language exactky like how Deck does it .

Deck just reads each line to then run in a mini golang vm

the deck vm and description text format: https://github.com/ajstarks/decksh

it already has 2D cad primitives :) we could layer in SDF primitives too.

there is a golang viewer here:
https://github.com/ajstarks/giocanvas/tree/master/gcdeck

AJ ( maintainer ) working up to it being a wysiwyg editor too . It’s getting there slowly :)
I am also working on sone aspects and hope to push into the repo if he takes my PE’s relating to editing and also publishing

Guidance on function translation

When will it become a problem for me to approximate a y=f(x) function to a distance in y?

(Forgive me if this is not an appropriate venue for such a question.)

Specifically I have a curve: y = o / x * t * (m * x + b) range: o < x < p
If Evaluate(p sdf.V2) did something like return p.Y - y (when p.X was in the right range) I'd get the sign right at least.

a) when would such an approach be wanting?
b) how might one approach a more correct sdf?

Zero length line segments ruin Polygon2D objects

I'm not really sure this is a bug, but I recently discovered that if you build a polygon from a set of points, you need to be careful to make sure that no consecutive points are equal. This results in a 0 length line segment and causes the SDF code to fail. It will either end up finding no object at all or it will find a series of points that evaluate to NaN coordinates.

I was working on extruding objects from some dirty polygon data that included such. Also my manipulation code expects a closed polygon to be represented with the first and last point being equal. So, I had to make sure to write my code such that the last point is skipped and with code to guard against 0 length segments.

It might be nice to add those the Polygon2D constructor, or maybe just add a note in the docs about the problem.

Gyroid shape

Gyroid cube

There is an example code to create Gyroid cubes:

s0, err := gyroidCube()

I tried the example but with smaller marching cube input of 50:

render.ToSTL(s0, "gyroid_cube.stl", render.NewMarchingCubesUniform(50))

The result is a gyroid with the shape of a cube;

Screen Shot 2022-12-15 at 13 00 17

Gyroid in any shape?

Now, I have a question. How to create Gyroid in any shape? For example, assume I have a surface mesh represented by an STL file. Assume the shape of the surface mesh is a teapot:

Screen Shot 2022-12-15 at 13 11 02

I can import and covert the surface mesh into SDF by:

	inputPth := "input.stl"

	// read the stl file.
	file, err := os.OpenFile(inputPth, os.O_RDONLY, 0400)

	// create the SDF from the mesh
	imported, err := obj.ImportSTL(file, 20, 3, 5)

	// TODO: How to create a gyroid with the imported shape?

I cannot figure out how to create a gyroid that has the imported shape. I mean, the final gyroid must have the shape of the teapot. How can I do that?

Redesigning Vec family API

Using the method approach to managing vectors feels clunky and less readable than defining functions that take all vectors needed as arguments.

Compare

center := a.f(t) 
n := a.f(t + dt2)
n = n.Sub(a.f(t - dt2))
pv := p.Sub(center) 

with

center := a.f(t)
n := sdfv.Sub(a.f(t + dt2),  a.f(t - dt2))
pv := sdfv.Sub(p, center) 

If this were to be done it could be moved to a subpackage and the methods be left for backwards compatibility

More exports in rendering code or better save functions

I want to use this library in a situation where it would be better to save the output directly to an io.Writer than to be forced to save directly to disk. However, there's no RenderXXX function that takes an io.Writer as an argument. I was going to see if I could create my own writer using one of the sampling methods directly, but the sampling methods are not exported. So, basically, I'd have to do something convoluted and slow (write to disk, close, read from disk to write somewhere else) or copy and paste the sampling code to create my own render package.

I'd like to propose that the RenderXXX functions really ought to be handled in two phases:

  1. Render to raw object (either triangles or points depending on the output)
  2. Serialize raw object and write to io.Writer

The existing functions that combine these two functions should remain, but exist as a convenience.

I'd also like to eliminate the duplication between the two different file writing paths eliminated. The differences between SaveXXX and WriteXXX can be combined without sacrificing the performance gained by the async writing performed by WriteXXX. Just define render.XXXWriter that provides a WriteTriangle3() method and then provide a constructor that returns that when given an io.Writer.

Finally, it might be nice to have more access to the rendered triangles directly. You never know when someone might want to do their own post-processing on the triangle mesh to perform some specialized work. Splitting the steps up makes that possible.

There's no reason the existing render API needs to change. It could all be implemented in terms of this new API. Anyway, without filling in the functions, the new code for STL could look something like this:

package render

// Triangle3Writer allows someone to receive the triangles as a writer 
// and write their own thing and provide the general interface to be 
// implemented by STLWriter.
type Triangle3Writer interface {
    WriteTriangle3(t *Triangle3) (int, error)
    WriteTriangle3s(m *Triangle3) (int, error)
}

type STLWriter struct {
    io.Writer
    // plus whatever we need to track state, efficiently buffer, etc.
}

func (w *STLWriter) WriteTriangle3(t *Triangle3) (int, error) {
    // triangle writing stuff gets moved into here
}

func (w *STLWriter) WriteTriangle3s(m []*Triangle3) (int, error) {
    // write lots of triangles
}

func NewSTLWriter(w io.Writer) *STLWriter {
    // do other STLHeader setup and what-not....
    return &STLWriter{w}
}

func SaveSTL(path string, mesh []*Triangle3) error {
    w, err := os.Create(path)
    if err != nil {
        return err
    }
    defer w.Close()

    sw := NewSTLWriter(w)
    for _, t := range mesh {
        _, err := sw.WriteTriangle(t)
        if err != nil {
            return err
        }
    }

    return nil
}

func WriteSTL(wg *sync.WaitGroup, path string) (chan<- *Triangle3, error) {
    w, err := os.Create(path)
    if err != nil {
        return err
    }

    sw := NewSTLWriter(w)

    c := make(chan *Triangle3)

    wg.Add(1)
    go func() {
        wg.Done()
        defer w.Close()

        for _, t := range c {
            sw.WriteTriangle(t)
        }
    }()
}

func RenderTriangle3s(
    s sdf.SDF3,
    meshCells int,
    w Triangle3Writer,
) error { 
    // Implement RenderSTL's rendering stuff into the Triangl3Writer
}

func RenderTriangle3sSlow(
    s sd.SDF3,
    meshCells int,
    w Triangle3Writer,
) error {
    // Implement RenderSTLSlow rendering stuff into the Triangle3Writer
}

func RenderSTL(
	s sdf.SDF3, //sdf3 to render
	meshCells int, //number of cells on the longest axis. e.g 200
	path string, //path to filename
) {
    // Open a file, wrap it in STLWriter, call RenderSTLTriangle3s
}

func RenderSTLSlow(
	s sdf.SDF3, //sdf3 to render
	meshCells int, //number of cells on the longest axis. e.g 200
	path string, //path to filename
) {
    // Open a file, wrap it in STLWriter, call RenderSTLSlowTriangle3s
}

I'm going to see about working on a PR to flesh that out, if you're interested. Otherwise, I'm going to fork the render package because I don't want the overhead of writing to disk and then reading it again for what I'm trying to do.

obj/screw.go: NPT thread

First off, love the project! I'd love to put in some love and right now I'd be needing NPT threads. I've looked at the source in screw.go and have gotten some idea of what's going on though I could use a pointer in how I'd go about adding a new thread. It seems like the problem lies in Screw3D which would need an additional taper parameter, no? Also adding NPT to threadDB.

Also I believe I'd have to modify func (s *ScrewSDF3) Evaluate(p V3) float64. Still having some trouble wrapping my head around the 2D mapping.

NPT threads

API refactoring or expansion to support assemblies, drawings, importers, external renderers, and CAM

Jason, how stable were you planning to keep the sdfx public API at this stage? Are you considering uppercased things such as WriteDFX to be frozen, for instance? How simple do you want sdfx to remain -- do you want pull requests of higher-level features that would make sdfx more complex, or would you prefer to keep it as a simple low-level library that others call?

tl;dr I'm veering towards option (3) -- skip to the Proposal section below. This means keep it simple and avoid breaking changes to the public API by exporting some things that aren't yet exported. This would enable sdfx to function as a low-level library for other higher-level Go-based libraries and tools. This looks like a better way of addressing feature requests like #16 and #21, and would enable external implementations of alternate renderers as in #6, as well as third-party viewers, CAM, and physics engines.

Details

While struggling to implement #21, it finally dawned on me that labels, dimensions, and even features such as a title block or drawing frame are similar to subcomponents in assemblies. Similar to what #16 mentions, I'm finding that adding these features using the existing rendering pipeline means making some pretty drastic changes to the APIs, both internal and external.

Looking at #16, I see that @mrsimicsak, in trying to create assemblies, ran into some of the same issues I've been hitting in #21 the last few days -- specifically, a need to access the internals of SDF structs while encapsulating them such that they can be associated with non-SDF structs such as assembly connectors or text. Those encapsulated subcomponents then need to be able to be transformed in sync with each other and rendered into some external file format, emitting both SDF and non-SDF types into the rendered stream, using the right primitives for the target format, while preserving spatial relationships between components. Implementing this in the existing rendering code paths would cause a lot of refactoring of existing code.

For example, if the target format is DXF and we're assembling a 2D drawing, we would want to be able to emit Line{} as LINE and Text{} as TEXT, rather than converting Text{} to Line{} segments as in TextSDF2(). Text{} or an encapsulating struct would need to include X and Y fields, and if the Text{} is a label for an SDF object, then something like the connectors in #16 would then be used to associate the Text{} with the SDF object. Those X and Y fields in Text{} would then be altered in sync with the SDF coordinates during any transform. Later, when the WriteDXF goroutine is running, it would need to be able to handle an interface encapsulating both Text{} and Line{} types on its input chan, with either accessor methods or a type switch for dispatching to the right Line() and Text() calls in the yofu/dxf library. And so on.

This all feels like the wrong direction. Here are the available options that I can see:

Option 1

Option (1) would be that we implement the above as breaking changes to the public API. That alteration to the chan type, for instance, alters the WriteDXF signature. Internally, the chan type change also propagates all the way down from RenderDXF through marchingSquaresQuadtree() to processSquare(), since they all share that chan. Alternatively, RenderDXF becomes more of a chan router and type converter, getting Line{} types from marchingSquaresQuadTree() and converting them to the new encapsulating type that WriteDXF would need.

I don't know what other things would be impacted, but so far it looks like it would be a significant refactoring as opposed to a simple pull request.

This feels wrong to me.

Option 2

Option (2) avoids breaking changes by creating a whole new set of encapsulating and rendering structs and methods to implement the above. Much of this code would be redundant with what now exists in e.g. sdf*.go, render.go, dxf.go, and march*.go. For example, #16 led to an experimental and partially redundant connectors.go, and for #21 I'm finding myself writing a whole parallel universe in a new sdf/drawing.go file, with its own slightly altered versions of RenderDXF, WriteDXF, etc.

This is leading to a lot of copying and pasting of code, and just feels wrong.

Option 3

Option (3) avoids the breaking changes of (1) and code duplication of (2) by turning sdfx into more of a library for higher-level tools. We do this by uppercasing and exporting most of the existing SDF struct fields and more of the existing functions and methods, expanding the public API. (I acknowledge the paradox here -- proposing export of more things so we don't have to make breaking changes to what's already exported.)

Proposal

I think I prefer option (3), because it opens up the opportunity for an entire ecosystem around sdfx. This enables higher-level layers in other Go libraries, using sdfx as a backend for more complex assemblies, renderers, viewers, CAM pipelines, etc. I would expect the existing renderers in sdfx to remain as reference code, and new renderers to be implemented as separate libraries to be maintained by whoever needs them.

Uppercasing everything in sight would still be a nontrivial pull request as well as a policy decision for @deadsy to make, but at least would be straightforward to do and and easy to test.

Jason, what do you think? Should I go ahead and try (3) in my own fork and see how it goes?

Use case examples

Aside from the use cases mentioned in #16 and #21, I'm also thinking of other CAM pipelines as well as simulation code -- finite mesh and physics engines, for example, could hook into the more complete public API that option (3) would provide.

With option (3), it should be possible for a calling library to generate CNC milling G-code from SDF objects. The caller might be able to use Evaluate() and an exported marching cubes function, for example, to compare the SDF object being milled with another SDF object that represents the tool. (Efficient open-source milling tool path generation is something I've been after for more than a decade myself -- this could finally make it possible.)

Option (3) would enable slicers to be built on top of sdfx. For example, here's a description of direct slicer rendering from SDF objects to printer g-code, skipping the STL stage entirely: https://on-demand.gputechconf.com/gtc/2017/presentation/s7131-storti-modern-cad-cam-workflow.pdf.

Feature Request: Extract Vertex Data to an Array

Hi, I have been attempting to write a very basic slicer using the SDFX library by iterating over an imported STL with a Slice2D, but am unable to find a way to extract the 2D vertex data from that slice.

I am very new to using Go, so I may have missed that part of the documentation, and any help would be greatly appreciated!

Threads on package import

SDFX package starts empty routines even though it is not used. It means just importing the package itself opens up some threads by the init method. Is it the intended behavior?

func init() {

goroutine 20 [chan receive, 3 minutes]:
github.com/deadsy/sdfx/render.init.0.func1()
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:57 +0x75
created by github.com/deadsy/sdfx/render.init.0
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:54 +0x29

goroutine 21 [chan receive, 3 minutes]:
github.com/deadsy/sdfx/render.init.0.func1()
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:57 +0x75
created by github.com/deadsy/sdfx/render.init.0
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:54 +0x29

goroutine 22 [chan receive, 3 minutes]:
github.com/deadsy/sdfx/render.init.0.func1()
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:57 +0x75
created by github.com/deadsy/sdfx/render.init.0
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:54 +0x29

goroutine 23 [chan receive, 3 minutes]:
github.com/deadsy/sdfx/render.init.0.func1()
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:57 +0x75
created by github.com/deadsy/sdfx/render.init.0
    /home/***/go/pkg/mod/github.com/deadsy/sdfx@v0.0.0-20221027125250-c456ed660b0c/render/march3.go:54 +0x29

render as engineering drawing (with title block etc.)

Overview

I'm working on being able to generate 2D engineering drawings (with title block etc.) from sdfx. It looks like, in order to do this, I'd need to:

  • Be able to generate filled fonts rather than the outline fonts that TextSDF2() generates right now.
  • Generate lines or boxes for the title block, any specification tables, etc.
  • Find or implement some equivalent of the openscad projection() function to convert 3D models to 2D views or slices.

Does anyone have any ideas for how to best deal with the fonts? Looking at text.go and render.go right now, I suspect that I'd have to render in some way other than the existing marching squares (I think). It might even be better to skip that code path and render text as a <text> element in SVG or a TEXT entity for DXF.

I'm not as worried about the title block and tables -- am assuming that if I for some reason can't use boxes to build those I'll use Line{] or Line2{} directly.

Any opinions, and has anyone else had a go at this yet?

Architecture

Based on feedback below, so far I'm tentatively thinking the font bits might go in sdf/render.go, but the frame and title block, as well as any projection() function, might better fit in something like an sdf/view.go file or even a separate library. Am entirely open to suggestions.

Error on build

Congratulations on this project!
It's my first time with golang and i got error while build
Steps to reproduce:
git clone [email protected]:deadsy/sdfx.git
cd sdfx/examples/nutsandbolts/
go build main.go

✘ licensed@licensed  ~/gitz/sdfx/examples/nutsandbolts   master  go build main.go
main.go:11:8: cannot find package "github.com/deadsy/sdfx/sdf" in any of:
/usr/lib/go/src/github.com/deadsy/sdfx/sdf (from $GOROOT)
/home/licensed/go/src/github.com/deadsy/sdfx/sdf (from $GOPATH)

Line 11 is: import . "github.com/deadsy/sdfx/sdf"

How to avoid chamfered edges on cubes?

Apologies if this is a stupid question. I don't really know anything about the theory behind SDF functions, but -

Whenever I create a cube it seems to chamfer the edges slightly and I can't figure out how to get it to stop. Here is what my generated model looks like (+ the code):

https://github.com/warmans/pi3d

To make the model I just create a bunch of boxes with varying heights, move them into place, then union the whole thing together. But with this approach it leaves some weird artifacts and makes the whole model slow to print.

Could you offer any advice?

fixing panics

Hey Jason, just wanted to let you know that @koshchei is fixing all of the 50+ panics right now throughout the code base, and @Ryan-pelo is finding and fixing the few places where there are errors being printed without an actual err return. We've been working together on our own in-house video calls on this over the last couple of days. I'd expect the panics pull request to be ready for you by Monday or Tuesday.

@koshchei has pulled your spiral commits so he already has those panic fixes now -- just in the nick of time; he was about to fix spiral next. ;-)

Import STL?

Hi,

I am looking to import an existing STL (from a 3D scan) and I wanted to see if this was possible, I didn't see the code in your library to do so.

Bolts smaller than expected

I'm playing a bit with the generation of bolts, but there might be an issue with the generation of the STLs.

For example, when generating an M4x0.7 bolt, I get a diameter closer to 3.5mm. Initially, I thought it was an issue with my 3d printer or with the filament I was using, but opening the STL in freeCAD shows that it is indeed the generated STL that has the wrong dimensions. The diameter of the corresponding nut is around 3.8mm so it's closer to the expected 4mm. Obviously, it's way too big for the generated bolt.

Code:

package main

import (
	"fmt"

	"github.com/deadsy/sdfx/obj"
	"github.com/deadsy/sdfx/render"
	"github.com/deadsy/sdfx/sdf"
)


// Tolerance: Measured in mm. Typically 0.0 to 0.4. Larger is looser.
// Smaller is tighter. Heuristically it could be set to some fraction
// of an FDM nozzle size. It's worth experimenting to find out a good
// value for the specific application and printer.
// const mmTolerance = 0.4 // a bit loose
// const mmTolerance = 0.2 // very tight
// const mmTolerance = 0.3 // good plastic to plastic fit
const mmTolerance = 0.3
const inchTolerance = mmTolerance / sdf.MillimetresPerInch

// Quality: The long axis of the model is rendered with n cells. A larger
// value will take longer to generate, give a better resolution and a
// larger STL file size.
const quality = 200

func main() {
	boltParms := obj.BoltParms{
		Thread:      "M4x0.7",
		Style:       "knurl",
		Tolerance:   mmTolerance,
		TotalLength: 10.0,
		ShankLength: 0.0,
	}
	bolt, err := obj.Bolt(&boltParms)
	if err != nil {
		fmt.Printf("%s\n", err)
	}
	render.ToSTL(bolt, "M4x0.7_bolt.stl", render.NewMarchingCubesUniform(quality))
}

Screenshot 2022-11-04 at 21 28 04

Screenshot 2022-11-04 at 21 28 30

Time consumption of marching cubes algorithm

I tested the marching cubes algorithm with a 245.8 MiB input STL file.

myTris := wrapper.Tris(/* Load my STL file of 245.8 MiB */)
mySDF := obj.ImportTriMesh(myTris, 20, 3, 5)
myMesh := wrapper.Mesh(mySDF, render.NewMarchingCubesUniform(resolution))

Resolution is number of cells on the longest axis of b-box for uniform marching cubes.

resolution Time consumed by marching cubes sec
30 29s
60 46.15s
90 69.19s
120 127.65s
150 228.27s
180 355.89s

Question

Could a relationship be inferred between time consumption and resolution?

Cubic relationship?

I suspected that the time consumed by uniform marching cubes algorithm may be increased by the cubic power of resolution. But above observations don't approve it.

Time ~ Resolution ^ 3

I've added sdfx to curated-code-cad

First of all, nice project.

I might seem small I appreciate that you have pictures of mechanical parts in the readme instead of weird hypothetical CSG objects.

I've added the project to my ever-increasing curation of Code-CAD projects repo, webpage. Not having used sdfx I'm not in the best position to add a description but I've had a go at adding something. If you want to give me a paragraph or two summary I'd be happy to include it. Since it's in the context of lots of other similar projects, how its different is always a good point to make. What I've added so far is only.

sdfx

  • Repo
  • Community
  • Docs
  • License: MIT
  • Online editor

Go-based Code-CAD package that uses a signed distance functions (SDFs) kernel. Is capable of doing fillets and chamfering.

Cheers,
Kurt.

Hollow out 3D meshes by this tool?

Background

There is a Go source code already available. The source code imports 3D STL surface meshes and stores the index and vertex buffers corresponding to the 3D STL surface mesh.

It's desired to hollow out the imported 3D STL surface meshes by a thickness. To do so, this algorithm might be possible:

  1. Represent the original 3D surface mesh by signed distance field - SDF.
  2. Extract an iso-surface from SDF.
    1. Marching Cube algorithm to extract the iso-surface from SDF.
    2. A negative offset/distance is the threshold passed to marching cube algorithm.
    3. Negative offset means the new iso-surface would be inside SDF.
    4. The offset/distance is actually the thickness of the hollowing process.
  3. Flip the normal vectors of the new internal iso-surface.
    1. This step is trivial.
  4. Merge new internal iso-surface with the original external surface mesh.
    1. This step is trivial.

Question

It's noticed that this package may contain the necessary code for steps 1 and 2 above. Is it right? Is it possible to hollow out 3D STL surface meshes by this package? Can anyone provide a boilerplate code to do so?

Pack models together tightly

It's intended to find some ways of arranging 3D models to minimize wasted space. In other words, it's desired to reduce the total volume of N 3D models packed together. The figures below show 2 and 4 models packed together.

Converting the 3D models to SDF might help with developing such an algorithm. I'm exploring the feasibility of using SDF for such an algorithm. Looks like SDF might provide a lot of conveniences.

My question is whether SDF is a good fit for such a problem domain. Is anyone aware of an SDF tool that does the above process? Are there any SDF tools that I might want to look at them to develop such an algorithm?

Screen Shot 2023-01-10 at 14 08 22

STL to SDF evaluation

At the point {X: -0.8164382918936324, Y: 2.542909114087213, Z: 5.006102143191411}, the SDF value for the teapot model is returned as a positive number. It means the point is outside the teapot. But we are sure that the point is definitely inside the teapot.

Reproduce

The bug is reproduced by PR #73.

Side effects

The bug has side effects observed here: #68 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.