sgenoud/replicad

Can't fuse a circle on the edge of another circle?

lf94 opened this issue · 9 comments

lf94 commented

1656378390

Basically I'm trying to lay one circle on the edge of another and fuse them, but I get this error - any ideas?

const main = ({ drawCircle, Plane }) => {
  const top  = drawCircle(44.0 / 2).sketchOnPlane(new Plane("XY")).extrude(3.0);
  const base = drawCircle(38.0 / 2).sketchOnPlane(new Plane("XY"))
    .loftWith(
      drawCircle(37.0 / 2)
      .fuse(drawCircle(3.0).translate([37.0 / 2 - 1.5, 0]))
      .sketchOnPlane(new Plane([0, 0, -10.0]))
    );
  const baseWithTop = top.fuse(base);
  const withCut = baseWithTop.cut(
    drawCircle(38.0 / 2 - 2.0).sketchOnPlane(new Plane([0, 0, -10.0])).extrude(13.0)
  );
  return [withCut];
};
lf94 commented

For what it's worth I did its 3D analog way:

1656382413

This would've been a lot easier though if I could've fused circles and lofted 🙂

const main = ({ sketchCircle, Sketcher, Plane }) => {
  const top  = sketchCircle(44.0 / 2).extrude(3.0);
  const base = sketchCircle(38.0 / 2)
    .loftWith(sketchCircle(37.0 / 2, new Plane([0, 0, -10.0])));
  const baseWithTop = top.fuse(base);
  const withCut = baseWithTop.cut(
    sketchCircle(38.0 / 2 - 2.0, new Plane([0, 0, -10.0])).extrude(13.0)
  );

  const parentRadii = { start: 38.0 / 2, end: 37.0 / 2 };
  const childRadii = { start: 1.0 / 2, end: 1.0 / 2 };

  const grips = [0, 90, 180, 270].map(angle =>
    modelGrip({ sketchCircle, Plane })({ angle, height: 10.0, parentRadii, childRadii })
  );
  return [withCut, ...grips];
};

const toDegrees = radians => (radians / (Math.PI * 2)) * 360;
const toRadians = angle => (angle / 360) * (Math.PI * 2);

const modelGrip = ({ sketchCircle, Plane }) => ({ angle, height, parentRadii, childRadii }) => {
  const radiusDiff = parentRadii.start - parentRadii.end;

  const parallelAngle = (90 - toDegrees(Math.atan(height / radiusDiff)));
  const grip = sketchCircle(childRadii.start)
  .loftWith(sketchCircle(childRadii.end, new Plane([0, 0, -height])))
  .rotate(parallelAngle, [0,0,0], [Math.cos(toRadians(angle + 90)),Math.sin(toRadians(angle + 90)),0])
  .translate([
    parentRadii.start * Math.cos(toRadians(angle)),
    parentRadii.start * Math.sin(toRadians(angle)),
    0
  ]);
  return grip;
}

The problem was with the translation - you need to pass to variables instead of a point - I should improve a bit on the error message which was misleading.

Also I am not 100% sure that you use the Plane constructor as you want to. I would advise you to to use the makePlane helper function - it takes as first argument the direction of the plane (as either its normal or a named plane XZ for instance) and as second argument the origin (i.e. the point which will be [0, 0] in the plane.). In the case of a named plane you can also pass just a number - it translate the plane along its normal. For instance makePlane("XZ", 5) will be the plane parallel to [0, 1, 0] which goes through [0, 5, 0].

Note that you can also now easily more around your planes to make the same plane you could makePlane("XZ").translateY(5).

lf94 commented

Yep, the Plane constructor seems to be working as I expect it to: it's creating an XY positioned plane at position P, which is really nice!

Thank you for explaining the rest :D I'll close it then. Why not make it accept a point instead to be closer to the API of other functions?...

Another approach to model this shape could have been a revolve of the cross-section? This way you could avoid booleans which require more calculations.

lf94 commented

@raydeleu I can't visualize it 😆

lf94 commented

@raydeleu ah you mean for the overall larger shape - yes! 100% it could have. But notice the little cylinders along the edges - that was actually the "hard part", especially since they are all rotated at an angle so the piece can easily fit snug into a hole in my floor

Another approach to model this shape could have been a revolve of the cross-section? This way you could avoid booleans which require more calculations.

Part of the advantage of 2D boolean operations is that they should be faster than the 3D ones (this is an hypothesis, I have not tested it to be honest). So, in theory the approach of doing boolean stuff in 2D and then extruding should be the most efficient way of building things.

Something else that came to my mind: the value of the Replicad concept is that a user can take functions, prepared by a different user to enhance his/her model. The "grip" idea used in this model has certain elements in it that - when generalized - are very useful to other users. For example, the function to create a radial array is interesting and can be used for all kinds of patterns. Another is the idea to create a small protrusion to create a tight fit.

In this example I would think that the solution envisaged by @lf94 using lofts would already be more generally applicable. It would also work on rectangular plugs, not only circular ones. I assume that it would also solve the difficult calculation of the rotation angle for the grip-cylinders. And using a standard loop instead of a mapping function to create a circular array where you can adapt the number of grips would be wider applicable and easier from a user perspective.

The questions then are:

  • how we can persuade a user to create a separate function instead of a tailor-made solution,
  • how can we make the function more understandable,
  • what are guidelines to make a function more generic, so applicable to a wider range of models
  • when a user has built such a useful function, how is this visible to other users of the library. Would it be possible to create something like a search tool where you type "array" or "grip" and you are lead to a boilerplate for a function to create this?
lf94 commented

Could you turn your comment into a discussion? I'll think about this throughout the day!