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.