-
Notifications
You must be signed in to change notification settings - Fork 2
D. Examples
This is the shape from Solidworks Model Mania 2020. I had to make some adjustments to the model to make it work. The largest difference is that the cutout in each arm does end 1 mm before reaching the cylindrical part in the middle.
The model files can be found here: https://blogs.solidworks.com/tech/wp-content/uploads/sites/4/Model-Mania-2020-Phase1-drawing.png https://blogs.solidworks.com/tech/wp-content/uploads/sites/4/Model-Mania-2020-Phase2-drawing.png
As usual the fillets posed the biggest challenge. I solved this by rounding the cutout first before subtracting it from the main shape. Furthermore I cut the holes after filleting the shape to avoid the need to select the edges explicitly.
The code below contains some failed fillet experiments. The red shape shows the part that I used to create the cutouts in each arm. The green box shows a selection box that I tried to select only a few edges. Strangely enough this failed, whereas filleting the whole shape and then cutting the holes that should not be rounded succeeded. Note that I still had to restrict the rounding within a box (using the e.InBox
method to avoid rounding the bottom of the shape.
// Three arm as created for Model Mania 2020
// https://blogs.solidworks.com/tech/wp-content/uploads/sites/4/Model-Mania-2020-Phase1-drawing.png
// https://blogs.solidworks.com/tech/wp-content/uploads/sites/4/Model-Mania-2020-Phase2-drawing.png
// free interpretation as not all fillets were possible in Replicad
// and cutout up to central cylinder posed a problem
function main({Sketcher,
sketchCircle,Lever,
makeCylinder,
makeBaseBox},{})
{
let r1 = 11;
let r2 = 6;
let d = 35;
let t = 3;
let h = 22;
let fl = 30;
let bb = 16/2;
let sb = 4.5/2;
let cb = 8/2;
// function to create a lever consisting of two circles connected with tangent lines
//
// radius1 = radius of circle that is located around the origin
// radius2 = radius of circle that is located at distance D along x-axis
// distance = distance between the two circles
// leverheight = distance over which lever is extruded in z-direction
//
// note that this function creates a closed shape. If you want holes you have to create two cylinders at
// the correct position, extrude these a bit more than the leverheight and subtract these from the shape.
function Lever(radius1, radius2, distance, leverHeight)
{
let sinus_angle = (radius1 - radius2) / distance
let angle = Math.asin(sinus_angle);
// points of outer contour of the lever
let p1 = [radius1 * Math.sin(angle), radius1 * Math.cos(angle)];
let p2 = [distance + radius2 * Math.sin(angle), radius2 * Math.cos(angle)];
let p3 = [distance + radius2, 0];
let p4 = [distance + radius2 * Math.sin(angle), - radius2 * Math.cos(angle)];
let p5 = [radius1 * Math.sin(angle), - radius1 * Math.cos(angle)];
let p6 = [- radius1, 0 ];
let sketchLever = new Sketcher("XY").movePointerTo(p1)
.lineTo(p2)
.threePointsArcTo(p4,p3)
.lineTo(p5)
.threePointsArcTo(p1,p6)
.close();
let leverBody = sketchLever.extrude(leverHeight);
return leverBody
}
// function to create lever with holes with standard wallthickness around the holes
// radii refer to outer radii, the holes will be radius - wallThickness
// uses the function lever to create the basic shape
function leverHoles(radius1,radius2,distance,leverHeight,wallThickness)
{
let leverBody = Lever(radius1,radius2,distance,leverHeight);
let orig_hole = sketchCircle(radius1-wallThickness).extrude(leverHeight + 10);
let dist_hole = sketchCircle(radius2-wallThickness).extrude(leverHeight + 10).translate([distance,0,0]);
let lever = leverBody.cut(orig_hole)
lever = lever.cut(dist_hole);
return lever
}
// function to cut part out of lever to make it lighter
// generally the size of the radii is equal to the size of the holes in the lever
function cutLever(r1,r2,d,h,ts,th)
{
let cLever = Lever(r1-ts,r2,d,h).translate([0,0,th]);
cLever=cLever.cut(makeCylinder(r1+ts,h+2*th,[0,0,0],[0,0,1]));
cLever=cLever.cut(makeCylinder(r2+ts,h+2*th,[d,0,0],[0,0,1]));
return cLever
}
// create three arms, fuse them together and round the edges in z-direction
let arm1 = Lever(r1,r2,d,h);
let arm2 = Lever(r1,r2,d,h).rotate(120,[0,0,0],[0,0,1])
let arm3 = Lever(r1,r2,d,h).rotate(240,[0,0,0],[0,0,1])
let threeArm = arm1.fuse(arm2);
threeArm = threeArm.fuse(arm3).fillet(fl,(e)=>e.inDirection("Z"));
// cut the three arms so that they slope with 22 degrees towards the end
let side = new Sketcher("XZ").movePointerTo([41,6])
.lineTo([50,6]).lineTo([50,30]).lineTo([0,30])
.lineTo([0,22]).lineTo([11,22]).lineTo([11,6+(30*Math.sin(22*Math.PI/180))])
.close()
let sideCutter = side.revolve()
// NOTE: sideCutter is rotated to avoid edge over first arm!!!
sideCutter = sideCutter.rotate(60,[0,0,0],[0,0,1]);
threeArm = threeArm.cut(sideCutter,false,false)
// fillet the top edges, leaving out the central axle
threeArm = threeArm.fillet(1,(e)=>e.inBox([50,50,2],[-50,-50,20]));
// Phase 2: make arms lighter, note that fillet is applied in this stage already
let cutLever1 = cutLever(bb+1,cb,d,h,3,4).fillet(1);
let cutLever2 = cutLever(bb+1,cb,d,h,3,4).rotate(120,[0,0,0],[0,0,1]).fillet(1);
let cutLever3 = cutLever(bb+1,cb,d,h,3,4).rotate(240,[0,0,0],[0,0,1]).fillet(1);
threeArm = threeArm.cut(cutLever1);
threeArm = threeArm.cut(cutLever2);
threeArm = threeArm.cut(cutLever3);
let selBox = makeBaseBox(5,20,12).translate([22.5,0,9])
// experiments to round edges
//threeArm = threeArm.fillet(0.4,(e)=>e.inBox([20,10,3],[25,0,15]));
//threeArm = threeArm.fillet(0.7,(e)=>e.inDirection("Z"));
//threeArm = threeArm.fillet(0.7,(e)=>e.inPlane("XY"));
threeArm = threeArm.fillet(1,(e)=>e.inBox([50,50,2],[-50,-50,21]))
let smallBore1 = makeCylinder(sb,h,[0,0,0],[0,0,1]).translate([35,0,-5]).rotate(120,[0,0,0],[0,0,1])
let smallBore2 = makeCylinder(sb,h,[0,0,0],[0,0,1]).translate([35,0,-5]).rotate(240,[0,0,0],[0,0,1])
let smallBore3 = makeCylinder(sb,h,[0,0,0],[0,0,1]).translate([35,0,-5]).rotate(360,[0,0,0],[0,0,1])
threeArm = threeArm.cut(smallBore1)
threeArm = threeArm.cut(smallBore2)
threeArm = threeArm.cut(smallBore3)
let counterBore1 = makeCylinder(cb,h,[0,0,0],[0,0,1]).translate([35,0,4])
let counterBore2 = makeCylinder(cb,h,[0,0,0],[0,0,1]).translate([35,0,4]).rotate(120,[0,0,0],[0,0,1])
let counterBore3 = makeCylinder(cb,h,[0,0,0],[0,0,1]).translate([35,0,4]).rotate(240,[0,0,0],[0,0,1])
threeArm = threeArm.cut(counterBore1)
threeArm = threeArm.cut(counterBore2)
threeArm = threeArm.cut(counterBore3)
// create holes for axles
let bigBore = sketchCircle(8).extrude(40).translate([0,0,-10]);
threeArm = threeArm.cut(bigBore);
let shapeArray =[{shape: threeArm, color: "steelblue"}
//,{shape: sideCutter, color:"grey", opacity:0.5}
,{shape: cutLever1, color:"red", opacity:0.5}
,{shape: selBox, color:"green", opacity:0.5}
]
return shapeArray;
}
I created the angled bracket defined for the SolidWorks Model Mania 2001 challenge. It was indeed a challenge to model this with Replicad. I started out with combining shapes like cylinders and boxes, but finally returned to an approach with drawings (sketches). As Solidworks supports constrained-based modelling the dimensions provided in the drawing https://blogs.solidworks.com/tech/wp-content/uploads/sites/4/Model-Mania-2001-Phase-1.jpg are not suited to create a drawing. Therefore I created three functions that allow to define points using angles and distances, either the polar distance or one of the X or Y distances. Using this function I defined points around the contour of each drawing.
The following drawing created in SolveSpace illustrates the drawing that I needed to recreate in Replicad. The part is called "flange" in the code below.
I did not solve the following issues:
- The rib, shown in the drawing below, is defined by a height of 26 mm, following the curve of the flange and then a curve with radius 55 mm to join towards the base of the shape. I used the
tangentArc
for this curve, but this does not allow to define a radius. I eyeballed the length of the straight segment between the two curves and used this to define the point where thetangentArc
starts. I plan to define some functions to take the tedious math out of these problems.
- After many attempts I was not able to create the fillets between the base block, the flange and the rib. The site https://github.com/alexneufeld/FreeCAD_modelmania/tree/main/2001 shows that it should be possible with FreeCad (based on OpenCascade), so I must be doing something wrong.
Here is my end-result:
And here is the code:
const {draw,makeCylinder,makeBaseBox} = replicad
// Number of functions to make drawing easier
function Polar(currentPoint,distance,angleDegToX)
{
let newPoint = [];
let angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + distance * Math.cos(angleRad);
newPoint[1] = currentPoint[1] + distance * Math.sin(angleRad);
return newPoint
}
function PolarX(currentPoint,xdistance,angleDegToX)
{
let newPoint = [];
let angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + xdistance;
newPoint[1] = currentPoint[1] + xdistance * Math.tan(angleRad);
return newPoint
}
function PolarY(currentPoint,ydistance,angleDegToX)
{
let newPoint = [];
let angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + ydistance/Math.tan(angleRad);
newPoint[1] = currentPoint[1] + ydistance;
return newPoint
}
function main()
{
// Model Mania 2001 part 1
// break part apart into several components
// cylinder
let startPoint = [-146,0,64];
let cylinderAngleDeg = 45
let cylinderAngle = cylinderAngleDeg*Math.PI/180;
let cylinderDirection = [Math.cos(cylinderAngle),0,Math.sin(cylinderAngle)]
let cylinderHeight = 32;
let cylinderOuterRadius = 52/2;
let cylinderInnerRadius = 32/2
let cylinderOuter = makeCylinder(cylinderOuterRadius,
cylinderHeight+1,startPoint,cylinderDirection)
// add millimeter to intrude into the flange
let cylinderInner = makeCylinder(cylinderInnerRadius,
cylinderHeight*2,startPoint,cylinderDirection)
cylinderOuter = cylinderOuter.cut(cylinderInner.clone())
// clone the inner cylinder as we want to re-use it to cut the flange
// base
let baseHeight = 20;
let baseWidth = 96;
let baseLength = 64;
let baseBlock = makeBaseBox(baseHeight,baseWidth,baseLength)
.translate([-baseHeight/2,0,0])
// flange
let flangeWidth = 64;
let flangeRoundingTop = 64/2;
let flangeThickness = 12;
let flangeRounding = 15;
let flangeLength = 146
let pStart = [startPoint[0],startPoint[2]] // 2D representation
let p1 = Polar(pStart,cylinderHeight,cylinderAngleDeg) // middle of cylinder
let p2 = Polar(p1,flangeRoundingTop,cylinderAngleDeg+90) // clockwise around
let p3 = Polar(p2,flangeThickness,cylinderAngleDeg)
let p4 = PolarY(p3,-p3[1]+baseLength,cylinderAngleDeg-90) // note that Dy is negative!
let p5 = [-10,baseLength]
let p6 = [-10,baseLength-flangeThickness]
let p7 = PolarY(p1,-p1[1]+(baseLength-flangeThickness),cylinderAngleDeg-90)
let flange = draw(p1)
.lineTo(p2)
.lineTo(p3)
.lineTo(p4)
.customCorner(flangeRounding)
.lineTo(p5)
.lineTo(p6)
.lineTo(p7)
.customCorner(flangeRounding+flangeThickness)
.close()
.sketchOnPlane("XZ")
.extrude(flangeWidth)
.translate([0,flangeWidth/2,0])
.fillet(31.99,(e)=>e.atAngleWith("X",45).ofLength(flangeThickness))
// rib
let ribHeight = 26;
let ribRounding = 55;
let r1 = Polar(pStart,cylinderHeight-ribHeight,45)
let r2 = PolarY(r1,-r1[1]+(64-12-26),-45)
let r8 = Polar(r1,ribHeight,45)
let r3 = [r2[0]+25,r2[1]]
let r4 = [-20,0]
let r5 = [-10,0]
let r6 = [-10,52]
let r7 = PolarY(r8,+52-r8[1],-45)
let rib = draw(r1)
.lineTo(r2)
.customCorner(53)
.lineTo(r3)
.tangentArcTo(r4) // not possible to enter a ribRounding, depends on r3[1]
.lineTo(r5)
.lineTo(r6)
.lineTo(r7)
.customCorner(27)
.lineTo(r8)
.close()
.sketchOnPlane("XZ")
.extrude(12)
.translate([0,6,0])
// M6 outer 11,20mm depth 6mm dia 6.8 mm
let holeSpacing = 60;
let holeMarginSide = 18;
let holeRadius = 6/2;
let holeOuterRadius = 10/2;
let holeMarginFront = 32;
let holeCutter = makeCylinder(holeRadius,baseHeight*4,[0,-holeSpacing/2,holeMarginFront],[-1,0,0])
let holeCutter2 = holeCutter.clone().translate([0,holeSpacing,0])
baseBlock = baseBlock.cut(holeCutter).cut(holeCutter2)
let counterCutter = makeCylinder(holeOuterRadius,baseHeight*4,[-14,-holeSpacing/2,holeMarginFront],[-1,0,0])
let counterCutter2 = counterCutter.clone().translate([0,holeSpacing,0])
baseBlock = baseBlock.cut(counterCutter).cut(counterCutter2)
baseBlock = baseBlock.fuse(flange).fuse(rib).fuse(cylinderOuter)
baseBlock = baseBlock.cut(cylinderInner)
.fillet(2,(e)=>e.inDirection("X"))
//.fillet(2,(e)=>e.inDirection("Y"))
.fillet(2,(e)=>e.atAngleWith("X",45))
//.fillet(1,(e)=>e.inBox([0,-10,50],[-40,10,60]))
.rotate(90,[0,0,0],[0,1,0])
return [{shape:baseBlock, color: "steelblue"}]
}
I created this "Plunge" water carafe for plants designed by Robert Bronwasser (see https://www.robertbronwasser.com/project/spring/) in Replicad. It is not an exact copy but demonstrates the capabilities and partly also the limitations of the library. The model consists of three main parts which I called the "body", the "filler" and the "spout". The cone-like body is created as a body of revolution. The filler is built as a loft between three wires, where the middle wire coincides with the top of the body. The spout is a small cylinder at an angle. The shapes are combined in a boolean fuse operation and then filleted. I tried to create a hollow shape by identifying the filler opening and the spout opening, but I could not get that to work. So in the end I created a shell by removing only the opening of the spout. The filler was then opened using a "cutter".
The code and some remarks regarding kernel errors are shown below"
// Model of the Plunge watering carafe designed by Robert Bronwasser
function main({
Sketcher,
sketchCircle,
sketchFaceOffset,
makeCylinder,
sketchRectangle,
FaceFinder,
})
{
// side profile of the bottom of the carafe
let p0 = [0,0]
let p1 = [20,0]
let p2 = [30,5]
let p3 = [30,8]
let p4 = [8,100] // radius of the top at 100 mm is 8 mm
let p5 = [0,100]
let sideview = new Sketcher("XZ")
.lineTo(p1)
.lineTo(p2)
.lineTo(p3)
.lineTo(p4)
.lineTo(p5)
.close()
// sketch is created on XZ plane, revolve is per default around z-axis
let body = sideview.revolve()
// create cross sections of the filler for the carafe
// used a workaround to rotate and translate the sketch to the required position
let fillHole = sketchCircle(12).face().rotate(-20,[0,0,0],[0,1,0]).translate([-35,0,135])
fillHole = sketchFaceOffset(fillHole,0);
let topBody = sketchCircle(8).face().translate([0,0,100]); // radius 8 at 100 mm
topBody = sketchFaceOffset(topBody,0);
let fillBottom = sketchCircle(9).face().rotate(20,[0,0,0],[0,1,0]).translate([0,0,80]);
fillBottom = sketchFaceOffset(fillBottom,0);
// filler shape is created as a loft between the three wires
let filler = fillHole.loftWith([topBody,fillBottom],{ruled: false});
// create spout, a cylinder with radius 5, length "lengthSpout"
let angleSpout = 45
let lengthSpout = 70
let spout = makeCylinder(5,lengthSpout,[0,0,0],[0,0,1]).rotate(angleSpout,[0,0,0],[0,1,0]).translate([0,0,100])
// // union the main body with the filler and fillet the junction with radius 30
let plunge = body.fuse(filler);
plunge = plunge.fillet(30,(e)=>e.inPlane("XY",100))
// union the shape with the sprout and fillet the junction with radius 10
plunge = plunge.fuse(spout).fillet(10,(e)=>e.inBox([20,20,100],[-20,-20,120]));
// Create a shell
let pointFiller = [-35,0,135]
let spoutOpening = []
spoutOpening[0] = Math.cos(angleSpout*Math.PI/180)*lengthSpout
spoutOpening[1] = 0
spoutOpening[2] = 100 + Math.sin(angleSpout*Math.PI/180)*lengthSpout
const orifices = new FaceFinder().either([
(f) => f.containsPoint(pointFiller),
(f) => f.containsPoint(spoutOpening)]);
// plunge = plunge.shell(-0.5,(f)=>f.inBox([20,20,130],[[-20,-20,155]])); // Kernel Error
// plunge = plunge.shell(-0.5,(f)=>f.containsPoint(pointFiller)); // works!
// plunge = plunge.shell(-0.5,orifices); // Kernel Error
plunge = plunge.shell(-1,(f)=>f.containsPoint(spoutOpening)); // works!
// create cutter boxes
// approach to open up another face by subtracting a box from the shape
let cutterFiller = sketchRectangle(40,30).extrude(20).rotate(-20,[0,0,0],[0,1,0]).translate([-30,0,135])
// let cutterSpout = sketchRectangle(40,30).extrude(20).rotate(10,[0,0,0],[0,1,0]).translate([48,0,145])
plunge = plunge.cut(cutterFiller)
// plunge = plunge.cut(cutterSpout) // resulted in Kernel error without the shell command
// let plungeInner = makeOffset(plunge,-1) // resulted in Kernel error
// let hollowPlunge = plunge.cut(plungeInner) // not possible as makeOffset failed
let shapeArray = [
{shape: plunge, name: "plunge", color:"steelblue"},
// {shape: fillHole, color:"black"}, // note that after the loft these sketches are deleted?
// {shape: topBody, color:"black"},
// {shape: fillBottom, color: "black"},
// {shape: filler, color: "yellow"},
// {shape: sprout, color: "blue"},
// {shape: cutterFiller},
// {shape:cutterSpout},
// {shape: test}
]
return shapeArray
}