Skip to content

Conversation

@Samtanner12
Copy link

Added Cubic and PolyBezier Curves to the proto definition. Polybezier curves allow for a quicker curve calculation and more accurate position on the curve facilitating a smoother surface finish. This change will allow us to use OVF with Dyndrite for our additive solution.

Added Cubic and Quadratic Bezier curves for smoother outskin edge calculations leading to a smoother surface finish
@SebastianDirks
Copy link
Contributor

SebastianDirks commented Oct 17, 2025

Hello Mr. Tanner,

thank you for your Pull Request. In general, we endorse the integration of additional geometric spline types, especially if there is industry need. Because OVF is based on Protocol Buffers (protobuf) and code generators, we can roll out format additions quickly.

The rules for protobuf forwards-/ backwards compatibility

  • We can add new tags without breaking anything. Parsers using older versions will read the added field in new files, add it to unknown fields and then continue parsing known fields. New parsers will read older files and the new field will be empty. In the case of vector block one of, the new cases will simply never be used.

  • We cannot remove a field or change its definition; this would break all applications using the previous definition.

  • It is possible to deprecate fields ([obsolete], reserves tag to prevent reuse) in protobuf. Up to now we have not done so and would like to avoid it. These types of changes need time to trickle down to all software using OVF

Because we cannot remove or rename a field after adding, I would like to discuss a few points before merging.

Observations

  1. Your suggestion uses a flattened control point form, not a coefficient form. I agree because this is consistent with the linear (Hatches, Polylines) definitions, which is also stored in end point coordinate from, not as angle and length.

  2. For linear segments, OVF (and all other AM formats I know) offer 2 definitions

    1. Polylines form a continuous line. There are no jumps between segments, consecutive segments share the same end point. In OVF a closed polyline (polygon) is represented by the first and last point having identical coordinates.
    2. Hatches form discontinuous lines. Jumps are inserted between segments.
      1. We do not store each hatch in its own vector block as a Polyline because that causes significant overhead for vector block (meta-) data (marking_params_key, laser_index etc.)
      2. A common source of wasteful storage and bugs is storing polylines in a hatches block by duplicating endpoints, so they are coincident. Because hatches are often handled differently by machine controllers (no polydelays/angle-based corner cutting), this can result in unexpected skywriting behaviour and build time degradation. Zero length jump commands can also cause delays if they are not filtered properly.
  3. To me it is not completely clear from your definition if it is representing multiple Bezier segments with jumps between them or a continuous spline. Because of the similar structure and problems in linear segments, I would propose offering either both definitions or one precise definition. Example:

    1. CubicBezierSpline: continuous, control points xy are shared between adjacent segments
    2. CubicBezierHatches: discontinuous, jumps are inserted between segments, control points xy exist for each segment for start and end.

Discussion Points Summary

  1. Do you plan to use the Bezier segments as continuous splines or as hatches or both?
  2. If your definition is meant for splines, does it cause any problems for you to compact the layout by collapsing identical x/y points? This e.g. results in 4 floats per segment in QuadraticBezier [x0, y0, cx0, cy0, x1, y1, cx2, cy2, x3, y3, cx3, cy3, …]. I suggest the nth tail segment to be shortened to only contain [xn, yn] instead of [xn, yn, cxn, cyn] with the last 2 control points unused. Reasoning: This enables memory casting to the expanded definition (with start and end point in one struct) by using a stride pattern.
  3. Do you have any suggestions to improve the precision of the naming? My suggestions are BezierSpline and BezierHatches to convey the type of connection between segments (jump or continuous).
  4. Do you see need for any other types of splines, e.g. NURBS?

@Samtanner12
Copy link
Author

Samtanner12 commented Oct 17, 2025

Hello Mr Dirks,

Thank you for getting back with me so quickly. Our specific use case Includes a jump to a specific start point followed by a sequence of curves following the format control point, target point. [Cx0,Cy0,Tx0,Tx0,Cx1,Cy1,Tx1…]. For Quadratic Bezier this involves two control points. In the future we may also utilize the hatching feature for infill on our parts.

I agree that compressing identical points is a good idea, however dropping the control points at the nth tail segment causes issues for our use case. We are drawing a curve relative to our current position and those control points define the curve to our target.

I think one solution that works well with our use case would be

` //Quadratic Bézier segments.
//Each segment is (x0, y0, cx, cy, x1, y1) => 6 floats per segment.
//Pack N segments as: [x0,y0,cx,cy,x1,y1, x0,y0,cx,cy,x1,y1, ...]
message QuadraticBezierHatches {
repeated float segments = 1;
}

//Cubic Bézier segments.
//Each segment is (x0, y0, c1x, c1y, c2x, c2y, x1, y1) => 8 floats per segment.
//Pack N segments as: [x0,y0,c1x,c1y,c2x,c2y,x1,y1, ...]
message CubicBezierHatches {
repeated float segments = 1;
}

//Linked Quadratic Bézier segments.
//Initial segment contains start point (Ix, Iy)
//Each segment is (cx0, cy0, x0, y0) in the format control, target
//=> 2 for start then 4 per segment.
message QuadraticBezierSpline{
repeated float data = 1;
}

//Linked Cubic Bézier segments.
//Initial segment contains start point (Ix, Iy)
//Each segment is (c1x0, c1y0, c2x0, c2y0, x0, y0) in the format control, control, target
//=> 2 floats for start then 6 per segment.
message CubicBezierSpline {
repeated float data = 1;
}`

As for NURBS and other curve types I do not see an urgent need at them moment. With the flexibility provided by the protobuf format OVF can adapt with the industry.

Thank you so much and I look forward to your feedback.

Updates to compress data and differentiate between hatches and splines
@SebastianDirks
Copy link
Contributor

Hello Mr. Tanner,

thank you for the update. We have requested a review from Dyndrite as well for this PR. This should be done in the next few days, afterwards I will merge. I have other additions for lookahead based scanners on a branch that I will also merge. Then I will put together a new release version of OVF.

Best
Sebastian

@Samtanner12
Copy link
Author

Hello Mr. Dirks,

Thank you. I look forward to their feedback and to the next release version. Thank you for your help.

Respectfully,
Sam Tanner

Copy link

@dyn-sdunn dyn-sdunn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a reason why we couldn't proceed on our end with these changes. That said, I did have some observations and comments to share.

  • Several of these messages put special meaning on values in a sequence, such as the various "Initial segment" values. Does it make sense to pull these out into explicit fields so that the repeated field is homogenous in intent? Other messages within the spec seem to do similar (Eg. the Arc message)
  • I have concerns regarding the unstructured nature of using repeated float instead of composing and repeating more meaningful messages. There could be too many / too few values, or value-misalignment, within the message. Seeing as extant parts of the OVF protobuf specification already leverage repeated float, I don't see a compelling reason to depart from convention. That said, I do think it is worthwhile to document expected behavior when such fields are malformed. (Or otherwise specifically citing that handling of such malformed entities is undefined by the specification).
  • I'm curious about constraining the order of the curve to N ∈ {2, 3} rather than leveraging the general Bézier curve definition? Is there a computational constraint or scalability concern that I'm not accounting for?
    • A rough draft of using general curve definitions:

      // NOTE: 2d definitions for all here, 3d left as an exercise to the reader.
      
      message GeneralBezierCurve {
          float x_0 = 1;
          float y_0 = 2;
          float x_n = 3;
          float y_n = 4;
          repeated float control_point_data = 5;
      }
      
      message BezierHatches {
          repeated GeneralBezierCurve segments = 1;
      }
      
      // A component within a spline composed by individual bezier curves.
      // Each starts from the previous components target, is interpolated through its
      // control poitns to its own target x/y values.
      message GeneralBezierCurveSplineComponent {
          
          // the target point.
          float x = 1; 
          float y = 2;
      
          // The control points to use leading into `{x, y}`
          repeated float control_point_data = 3;
      }
      
      message BezierSpline {
          float ix = 1;
          float iy = 2;
          repeated GeneralBezierCurveSplineComponent = 3;
      }

@Samtanner12
Copy link
Author

Thank you so much for your timely response, I understand what you are attempting to accomplish, but I am unsure of the practicality and usefulness of such a change.

Supporting arbitrary-degree Béziers forces variable-degree evaluation leading to an exponential compute time (de Casteljau ~O(dn²) per sample). In practice our controllers accelerate quad/cubic only and higher degrees will get flattened anyway, adding cost without quality gain. Fixed length bezier curves keeps streaming lightweight and predictable in my opinion.

Switching to general, nested messages increases payload size and decoder complexity, and would require all readers to implement flattening. I think keep quad/cubic as the primary wire format.

I agree, documenting expected behavior from a misaligned float does pose a risk, however I hesitate to depart from convention without a compelling reason.

For our current use case repeated float segments keep it as lightweight and predictable as possible, however I might be blind to the needs of the industry outside of our own implementation. I appreciate any feedback you may have for me.

Thank you.

Respectfully,
Sam Tanner

@SebastianDirks
Copy link
Contributor

Thank you both for your detailed feedback. I want to add details on the previous design choices for OVF that lead to the repeated float design for Polyline types, and add my toughts on the different proposals.

OVF perf: why unstructured repeated float?

@dyn-sdunn The choice of using unstructured repeated float in OVF is very deliberate and the reasoning is performance. Approx. 99% of the data volume in OVF is the coordinate data. Let me use the example of Polyline2d.
What we do in our C# library is reinterpreting the float data as an array of structs using the C# equivalent of memcast. This lets us use many different definitions of point structs without allocating again or copying the data (e.g. for different libraries, to use JIT intrinsics for SIMD and other algorithms). Zero copy in place is the minimal overhead possible of any solution. This general idea is also possible in C++. All geometry libraries I work with define 2d or 3d coordinate vectors as fixed size structs instead of dynamically sized types like List (C#) or Vector (C++).

wire format consequences

Encoding the Point structured in protobuf would lead to a different encoding "on the wire". Each message of a repeated embedded message field needs its own tag for the message (min 1 byte), length of the message (min 1 byte) and a tag for each field (1 byte for x, 1 byte for y). What we have now is a single tag and length for ALL floats, which is neglectable serialization overhead for typical block sizes. The repeated float field is "parsed" with a memcopy based on the length from the network buffer/file stream buffer to the proto object float array. The structured embedded message adds 4 bytes of overhead per 2 floats. This would increase file size by at least 50%. Even worse, each length and tag is a varint and has to be parsed with branch heavy code on a single byte level with bit masks instead of a bulk memcopy.
So the general OVF design idea is to document the form of structs on the lowest level and chose the more efficient definition.

vector blocks and machine controller capabilities

The Vector Block types serve as an extension point in OVF that represent controller capabilities. The intended design for OVF machine controllers is basically a switch statement on the vector block type to execute the desired exposure. It is not necessarily desirable to make the Vector Block design the most general possible, because as Mr. Tanner noted that also requires the machine controller to implement the most general form although it might never be used.

the pros and cons of generality

I would also prefer a more straightforward definition that can be memcast to common structs used in the geometry libraries. If the need for higher degree Bezier (or other Spline types) arises, this can simply be implemented with another vector block type, which is an optional capability. Converting complex and general definitions to primitive forms on the machine controller can be efficient if it reduces data volume and reduces discretization errors, but I think for Bezier it would just add a nested swich statement in the handling of the vector block type.
Two examples for complexity vs. performance:
Pro: Discretizing a circle or a Bezier spline to straight line segment micro vectors on the embedded machine controller (optimized for the hardware cycle time) is easy to calculate and saves data.
Con: Performing polygon offsets to calculate hatch fillings on the machine controller is not a good idea because the operation is expensive (CPU time) and requires careful handling of float rounding errors and edge cases.

representation of initial segments and strided memory access

I also want to elaborate on the initial segments comment by Samuel.
On the one hand this is a valid point because this deviates from the other Vector Block types. The repeated float fields are intended to be treated as an array of structs. Partial structs are not intended and their handling is undefined by the format, our C# library and format checker throws an error in that case.
Per the previous discussion, in case of the spline types what makes them a spline is that 2 adjacent segments have a common start/end point. This could also be represented with the BezierHatches definition, but that not only stores duplicate data but also gives rise to wired edge cases where parts of a hatch vector block form a spline that is then interrupted.
My idea to enforce this on the format level was to memcast the spline definition into the same 6 float segments (for 2d quadratic) with a “stride” of 2 floats, which is an overlap between the adjacent structs. . I know the “stride” concept from GPU vertex buffers in graphics pipelines.
But it might be clearer and easier to define the struct as having 4 floats, and 2 floats as extra fields. A stride requires pointer arithmetic in the indexer to access the “array of structs”. This is possible in C++ of course and in C# with Span, but it is uncommon and would at least need more comprehensive documentation. The “struct of 4” + 2 extra floats is closer to what we already use.

useful feedback to move forward

@Samtanner12 as far as I understood your software uses the full struct (with start and end point) for splines. The "array of structs with stride" pattern can also be used with the definition proposed by Samuel, if the 2 floats from the extra fields are appended to the Vector after protobuf has parsed the Vector Block Message. I would then propose to define the last point (end point) instead of the start point in extra fields. This avoids a memmove of the whole backing array of the Vector because we have to insert at the front. This would give e.g. layout (x0, y0, cx0, cy0) for quadratic Bezier struct. Would that work for you, or would you prefer the current proposal with more comprehensive documentation?

@dyn-sdunn @dyndrite-gabriel is Dyndrite currently already internally using any fixed size structs for quadratic or cubic Bezier spline segments, or uses libraries that define such structs? If yes, what is their memory layout? Is it directly compatible with one of the proposed definitions or would require transformations?

@Samtanner12
Copy link
Author

@SebastianDirks I appreciate your comprehensive breakdown of the various proposals.

My current implementation and firmware limitations does require me to use a full struct format.

I had not considered the cost of the front insert, and I understand the value of your proposed change to an endpoint definition. I see no reason to not move forward with the proposed definition as far as I am concerned. I look forward to answering any other questions that might be relevant. Thank you.

Implemented proposed behavior
@Samtanner12
Copy link
Author

Samtanner12 commented Dec 2, 2025

Hi @SebastianDirks @dyndrite-gabriel @dyndrite-gabriel I’ve pushed the proposed updates:

Hatches: Quadratic/Cubic (stride 6/8); 3D variants (9/12).

Splines: start+control(s) with tail end point; 3D variants included.

Kept packed repeated float convention;

Let me know if you have any questions or concerns. thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants