openjscad-aurora-webapp
422 строки · 15.5 Кб
1// title: Lamp Shade
2// author: Joost Nieuwenhuijse
3// license: MIT License
4
5function main(params)6{
7CSG.defaultResolution2D = (params.quality == "DRAFT")? 8:32;8
9var bottomradius = params.bottomdiameter/2;10var topradius = params.topdiameter/2;11var height = params.height;12var numfaces = params.numfaces;13var thickness = params.thickness;14var topholeradius = params.topholediameter/2;15var cutterRadius = params.cutterdiameter / 2;16
17var solid = CSG.cube({radius: [1000, 1000, height/2]});18
19var plane = CSG.Plane.fromPoints([bottomradius, 0, -height/2], [bottomradius, 10, -height/2], [topradius, 0, height/2]);20for(var i = 0; i < numfaces; i++)21{22solid = solid.cutByPlane(plane.rotateZ(i * 360 / numfaces));23}24
25var plates = solidToOuterShellPlates(solid, thickness);26plates = removePlateWithNormal(plates, [0,0,-1]);27plates = removePlateWithNormal(plates, [0,0,1]);28
29for(var j = 1; j < numfaces; j++)30{31plates[j] = plates[0].rotateZ(j * 360 / numfaces);32}33
34var topplate = getStockPlate(1000,1000,thickness)35.subtract(CSG.cylinder({start: [0,0,-thickness], end:[ 0,0,thickness], radius: topholeradius}))36.translate([0,0,height/2-thickness/2-10]);37topplate = topplate.intersect(solid);38topplate = fixPlate(topplate, thickness);39
40var fingerjointoptions = {41margin: 0, cutterRadius: cutterRadius, fingerWidth: 2542};43plates = fingerJoint(plates,fingerjointoptions);44plates = fingerJointAdd(plates, topplate, fingerjointoptions);45
46if(params.type == "TOPPLATE")47{48return plateCSGToCAG(plates[numfaces]);49}50else51{52var plate2d = plateCSGToCAG(plates[0]);53plate2d = addRandomHoles(plate2d);54if(params.type == "SIDEPLATE")55{56return plate2d;57}58else59{60for(var k = 0; k < numfaces; k++)61{62var plate3d = plateCAGToCSG(plate2d, plates[k].properties.platebasis, thickness);63plates[k] = plate3d;64}65var result = new CSG().union(plates);66result = result.rotateX(90);67return result;68}69}70}
71
72function addRandomHoles(plate)73{
74var distancefromedge = 8;75var distancebetweenholes = 10;76var mindiameter = 10;77var maxdiameter = 25;78// maskarea: the 'forbidden' area for holes:79var maskarea = plate.contract(distancefromedge, 4);80var bounds = maskarea.getBounds();81maskarea = maskarea.flipped();82var holes = [];83var existingholecenters = [];84var existingholeradii = [];85for(var i = 0; i < 10; i++)86{87for(var tryindex = 0; tryindex < 10; tryindex++)88{89var holeradius = (mindiameter + Math.random() * (maxdiameter - mindiameter))/2;90var x = bounds[0].x + holeradius + (bounds[1].x - bounds[0].x - holeradius*2) * Math.random();91var y = bounds[0].y + holeradius + (bounds[1].y - bounds[0].y - holeradius*2) * Math.random();92var holecenter = new CSG.Vector2D(x,y);93var valid = true;94// check if the hole is too close to one of the existing holes:95var numexistingholes = existingholecenters.length;96for(var i2 = 0; i2 < numexistingholes; i2++)97{98var d = holecenter.minus(existingholecenters[i2]).length();99if(d < holeradius+existingholeradii[i2] + distancebetweenholes)100{101valid = false;102break;103}104}105if(valid)106{107// check if the hole is not too close to the edges:108var hole = CAG.circle({radius: holeradius, center: holecenter});109var testarea = maskarea.intersect(hole);110if(testarea.sides.length !== 0) valid = false;111if(valid)112{113existingholeradii.push(holeradius);114existingholecenters.push(holecenter);115holes.push(hole);116break;117}118}119}120}121return plate.subtract(holes);122}
123
124function plateCSGToCAG(plate)125{
126if(!("platebasis" in plate.properties))127{128throw new Error("Plates should be created using getStockPlate()");129}130var plate2d = plate.projectToOrthoNormalBasis(plate.properties.platebasis);131return plate2d;132}
133
134function plateCAGToCSG(plate2d, platebasis, thickness)135{
136var basisinversematrix = platebasis.getInverseProjectionMatrix();137var plate_reprojected = plate2d.extrude({offset: [0,0,thickness]}).translate([0,0,-thickness/2]);138plate_reprojected = plate_reprojected.transform(basisinversematrix);139plate_reprojected.properties.platebasis = platebasis;140return plate_reprojected;141}
142
143function fixPlate(plate, thickness)144{
145return plateCAGToCSG(plateCSGToCAG(plate), plate.properties.platebasis, thickness);146}
147
148function removePlateWithNormal(plates, normalvector)149{
150normalvector = new CSG.Vector3D(normalvector);151var result = [];152plates.map(function(plate){153if(!("platebasis" in plate.properties))154{155throw new Error("Plates should be created using getStockPlate()");156}157if(plate.properties.platebasis.plane.normal.dot(normalvector) < 0.9999)158{159result.push(plate);160}161});162return result;163}
164
165function getStockPlate(width, height, thickness)166{
167var result = CSG.cube({radius: [width/2, height/2, thickness/2]});168result.properties.platebasis = CSG.OrthoNormalBasis.Z0Plane();169return result;170}
171
172function fingerJointAdd(plates, newplate, options)173{
174var result = plates.slice(0);175var numplates = plates.length;176for(var plateindex1 = 0; plateindex1 < numplates; plateindex1++)177{178var joined = fingerJointTwo(result[plateindex1], newplate, options);179result[plateindex1] = joined[0];180newplate = joined[1];181}182result.push(newplate);183return result;184}
185
186// Finger joint between multiple plates:
187function fingerJoint(plates, options)188{
189var result = plates.slice(0);190var numplates = plates.length;191var maxdelta = Math.floor(numplates/2);192for(var delta=1; delta <= maxdelta; delta++)193{194for(var plateindex1 = 0; plateindex1 < numplates; plateindex1++)195{196var plateindex2 = plateindex1 + delta;197if(plateindex2 >= numplates) plateindex2 -= numplates;198
199var joined = fingerJointTwo(result[plateindex1], result[plateindex2], options);200result[plateindex1] = joined[0];201result[plateindex2] = joined[1];202if(delta*2 >= numplates)203{204// numplates is even205if(plateindex1*2 >= numplates)206{207// and we've done the first half: we're done208break;209}210}211}212}213return result;214}
215
216function fingerJointTwo(plate1, plate2, options)217{
218if(!options) options = {};219if(!("platebasis" in plate1.properties))220{221throw new Error("Plates should be created using getStockPlate()");222}223if(!("platebasis" in plate2.properties))224{225throw new Error("Plates should be created using getStockPlate()");226}227// get the intersection solid of the 2 plates:228var intersection = plate1.intersect(plate2);229if(intersection.polygons.length === 0)230{231// plates do not intersect. Return unmodified:232return [plate1, plate2];233}234else235{236var plane1 = plate1.properties.platebasis.plane;237var plane2 = plate2.properties.platebasis.plane;238// get the intersection line of the 2 center planes:239var jointline = plane1.intersectWithPlane(plane2);240// Now we need to find the two endpoints on jointline (the points at the edges of intersection):241// construct a plane perpendicular to jointline:242plane1 = CSG.Plane.fromNormalAndPoint(jointline.direction, jointline.point);243// make the plane into an orthonormal basis:244var basis1 = new CSG.OrthoNormalBasis(plane1);245// get the projection matrix for the orthobasis:246var matrix = basis1.getProjectionMatrix();247// now transform the intersection solid:248var intersection_transformed = intersection.transform(matrix);249var bounds = intersection_transformed.getBounds();250// now we know the two edge points. The joint line runs from jointline_origin, in the251// direction jointline_direction and has a length jointline_length (jointline_length >= 0)252var jointline_origin = jointline.point.plus(jointline.direction.times(bounds[0].z));253var jointline_direction = jointline.direction;254var jointline_length = bounds[1].z - bounds[0].z;255
256var fingerwidth = options.fingerWidth || (jointline_length / 4);257var numfingers=Math.round(jointline_length / fingerwidth);258if(numfingers < 2) numfingers=2;259fingerwidth = jointline_length / numfingers;260
261var margin = options.margin || 0;262var cutterRadius = options.cutterRadius || 0;263var results = [];264for(var plateindex = 0; plateindex < 2; plateindex++)265{266var thisplate = (plateindex == 1)? plate2:plate1;267// var otherplate = (plateindex == 1)? plate1:plate2;268// create a new orthonormal basis for this plate, such that the joint line runs in the positive x direction:269var platebasis = new CSG.OrthoNormalBasis(thisplate.properties.platebasis.plane, jointline_direction);270// get the 2d shape of our plate:271var plate2d = thisplate.projectToOrthoNormalBasis(platebasis);272var jointline_origin_2d = platebasis.to2D(jointline_origin);273matrix = platebasis.getProjectionMatrix();274intersection_transformed = intersection.transform(matrix);275bounds = intersection_transformed.getBounds();276var maxz = bounds[1].z;277var minz = bounds[0].z;278var maxy = bounds[1].y + margin/2;279var miny = bounds[0].y - margin/2;280
281var cutouts2d = [];282for(var fingerindex = 0; fingerindex < numfingers; fingerindex++)283{284if( (plateindex === 0) && ((fingerindex & 1)===0) ) continue;285if( (plateindex == 1) && ((fingerindex & 1)!==0) ) continue;286var minx = jointline_origin_2d.x + fingerindex * fingerwidth - margin/2;287var maxx = minx + fingerwidth + margin;288var cutout = createRectCutoutWithCutterRadius(minx, miny, maxx, maxy, cutterRadius, plate2d);289cutouts2d.push(cutout);290}291var cutout2d = new CAG().union(cutouts2d);292var cutout3d = cutout2d.extrude({offset: [0,0,maxz-minz]}).translate([0,0,minz]);293cutout3d = cutout3d.transform(platebasis.getInverseProjectionMatrix());294var thisplate_modified = thisplate.subtract(cutout3d);295results[plateindex] = thisplate_modified;296}297return results;298}299}
300
301// Create a rectangular cutout in 2D
302// minx, miny, maxx, maxy: boundaries of the rectangle
303// cutterRadius: if > 0, add extra cutting margin at the corners of the rectangle
304// plate2d is the 2d shape from which the cutout will be subtracted
305// it is tested at the corners of the cutout rectangle, to see if do need to add the extra margin at that corner
306function createRectCutoutWithCutterRadius(minx, miny, maxx, maxy, cutterRadius, plate2d)307{
308var deltax = maxx-minx;309var deltay = maxy-miny;310var cutout = CAG.rectangle({radius: [(maxx-minx)/2, (maxy-miny)/2], center: [(maxx+minx)/2, (maxy+miny)/2]});311var cornercutouts = [];312if(cutterRadius > 0)313{314var extracutout = cutterRadius * 0.2;315var hypcutterradius = cutterRadius / Math.sqrt(2.0);316var halfcutterradius = 0.5 * cutterRadius;317var dcx, dcy;318if(deltax > 3*deltay)319{320dcx = cutterRadius + extracutout/2;321dcy = extracutout / 2;322}323else if(deltay > 3*deltax)324{325dcx = extracutout / 2;326dcy = cutterRadius + extracutout/2;327}328else329{330dcx = hypcutterradius-extracutout/2;331dcy = hypcutterradius-extracutout/2;332}333for(var corner = 0; corner < 4; corner++)334{335var cutoutcenterx = (corner & 2)? (maxx-dcx):(minx+dcx);336var cutoutcentery = (corner & 1)? (maxy-dcy):(miny+dcy);337var cornercutout = CAG.rectangle({radius: [cutterRadius+extracutout/2, cutterRadius+extracutout/2], center: [cutoutcenterx, cutoutcentery]});338var testrectacenterx = (corner & 2)? (maxx-halfcutterradius):(minx+halfcutterradius);339var testrectbcenterx = (corner & 2)? (maxx+halfcutterradius):(minx-halfcutterradius);340var testrectacentery = (corner & 1)? (maxy+halfcutterradius):(miny-halfcutterradius);341var testrectbcentery = (corner & 1)? (maxy-halfcutterradius):(miny+halfcutterradius);342var testrecta = CAG.rectangle({radius: [halfcutterradius, halfcutterradius], center: [testrectacenterx, testrectacentery]});343var testrectb = CAG.rectangle({radius: [halfcutterradius, halfcutterradius], center: [testrectbcenterx, testrectbcentery]});344if( (plate2d.intersect(testrecta).sides.length > 0) &&345(plate2d.intersect(testrectb).sides.length > 0) )346{347cornercutouts.push(cornercutout);348}349}350}351if(cornercutouts.length > 0)352{353cutout = cutout.union(cornercutouts);354}355return cutout;356}
357
358function solidToOuterShellPlates(csg, thickness)359{
360csg = csg.canonicalized();361var bounds = csg.getBounds();362var csgcenter = bounds[1].plus(bounds[0]).times(0.5);363var csgradius = bounds[1].minus(bounds[0]).length();364var plane2polygons = {};365csg.polygons.map(function(polygon){366var planetag = polygon.plane.getTag();367if(!(planetag in plane2polygons))368{369plane2polygons[planetag] = [];370}371plane2polygons[planetag].push(polygon);372});373var plates = [];374for(var planetag in plane2polygons)375{376var polygons = plane2polygons[planetag];377var plane = polygons[0].plane;378var shellcenterplane = new CSG.Plane(plane.normal, plane.w - thickness/2);379var basis = new CSG.OrthoNormalBasis(shellcenterplane);380var inversebasisprojection = basis.getInverseProjectionMatrix();381var csgcenter_projected = basis.to2D(csgcenter);382var plate = getStockPlate(csgradius, csgradius, thickness).translate([csgcenter_projected.x, csgcenter_projected.y, 0]);383plate = plate.transform(inversebasisprojection);384plate = plate.intersect(csg);385plates.push(plate);386}387return plates;388}
389
390function getParameterDefinitions()391{
392return [393{name: 'topdiameter', type: 'float', initial: 160, caption: "Top diameter:"},394{name: 'bottomdiameter', type: 'float', initial: 300, caption: "Bottom diameter:"},395{name: 'height', type: 'float', initial: 170, caption: "Height:"},396{name: 'numfaces', type: 'int', initial: 5, caption: "Number of faces:"},397{name: 'thickness', type: 'float', initial: 4, caption: "Thickness of stock material:"},398{name: 'topholediameter', type: 'float', initial: 42, caption: "Diameter of top hole:"},399{name: 'cutterdiameter', type: 'float', initial: 3.2, caption: "Diameter of CNC cutter / laser beam:"},400
401{402name: 'type',403type: 'choice',404values: ["ASSEMBLED", "TOPPLATE", "SIDEPLATE"], // these are the values that will be supplied to your script405captions: ["Assembled", "Top plate (DXF output)", "Side plate (DXF output)"], // optional, these values are shown in the listbox406// if omitted, the items in the 'values' array are used407caption: 'Show:', // optional, displayed left of the input field408initial: "ASSEMBLED" // optional, default selected value409// if omitted, the first item is selected by default410},411{412name: 'quality',413type: 'choice',414values: ["DRAFT", "HIGH"], // these are the values that will be supplied to your script415captions: ["Draft", "High"], // optional, these values are shown in the listbox416// if omitted, the items in the 'values' array are used417caption: 'Quality:', // optional, displayed left of the input field418initial: "DRAFT" // optional, default selected value419// if omitted, the first item is selected by default420}421];422}
423
424