openjscad-aurora-webapp

Форк
0
422 строки · 15.5 Кб
1
// title: Lamp Shade
2
// author: Joost Nieuwenhuijse
3
// license: MIT License
4

5
function main(params)
6
{
7
  CSG.defaultResolution2D = (params.quality == "DRAFT")? 8:32;
8

9
  var bottomradius = params.bottomdiameter/2;
10
  var topradius = params.topdiameter/2;
11
  var height = params.height;
12
  var numfaces = params.numfaces;
13
  var thickness = params.thickness;
14
  var topholeradius = params.topholediameter/2;
15
  var cutterRadius = params.cutterdiameter / 2;
16
  
17
  var solid = CSG.cube({radius: [1000, 1000, height/2]});
18
  
19
  var plane = CSG.Plane.fromPoints([bottomradius, 0, -height/2], [bottomradius, 10, -height/2], [topradius, 0, height/2]);
20
  for(var i = 0; i < numfaces; i++)
21
  {
22
    solid = solid.cutByPlane(plane.rotateZ(i * 360 / numfaces));
23
  }
24

25
  var plates = solidToOuterShellPlates(solid, thickness);
26
  plates = removePlateWithNormal(plates, [0,0,-1]);
27
  plates = removePlateWithNormal(plates, [0,0,1]);
28

29
  for(var j = 1; j < numfaces; j++)
30
  {
31
    plates[j] = plates[0].rotateZ(j * 360 / numfaces);
32
  }
33
  
34
  var 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]);
37
  topplate = topplate.intersect(solid);
38
  topplate = fixPlate(topplate, thickness);
39

40
  var fingerjointoptions = {
41
    margin: 0, cutterRadius: cutterRadius, fingerWidth: 25
42
  };
43
  plates = fingerJoint(plates,fingerjointoptions);
44
  plates = fingerJointAdd(plates, topplate, fingerjointoptions);
45

46
  if(params.type == "TOPPLATE")
47
  {
48
    return plateCSGToCAG(plates[numfaces]);
49
  }
50
  else
51
  {  
52
    var plate2d = plateCSGToCAG(plates[0]);
53
    plate2d = addRandomHoles(plate2d); 
54
    if(params.type == "SIDEPLATE")
55
    {
56
      return plate2d;
57
    }
58
    else
59
    {
60
      for(var k = 0; k < numfaces; k++)
61
      {
62
        var plate3d =  plateCAGToCSG(plate2d, plates[k].properties.platebasis, thickness);
63
        plates[k] = plate3d;
64
      }
65
      var result = new CSG().union(plates);
66
      result = result.rotateX(90);
67
      return result;
68
    }
69
  }
70
}
71

72
function addRandomHoles(plate)
73
{
74
  var distancefromedge = 8;
75
  var distancebetweenholes = 10;
76
  var mindiameter = 10;
77
  var maxdiameter = 25; 
78
  // maskarea: the 'forbidden' area for holes:
79
  var maskarea = plate.contract(distancefromedge, 4);
80
  var bounds = maskarea.getBounds();
81
  maskarea = maskarea.flipped();  
82
  var holes = [];
83
  var existingholecenters = [];
84
  var existingholeradii = [];
85
  for(var i = 0; i < 10; i++)
86
  {
87
    for(var tryindex = 0; tryindex < 10; tryindex++)
88
    {
89
      var holeradius = (mindiameter + Math.random() * (maxdiameter - mindiameter))/2;
90
      var x = bounds[0].x + holeradius + (bounds[1].x - bounds[0].x - holeradius*2) * Math.random(); 
91
      var y = bounds[0].y + holeradius + (bounds[1].y - bounds[0].y - holeradius*2) * Math.random();
92
      var holecenter = new CSG.Vector2D(x,y);
93
      var valid = true;
94
      // check if the hole is too close to one of the existing holes:
95
      var numexistingholes = existingholecenters.length;
96
      for(var i2 = 0; i2 < numexistingholes; i2++)
97
      {
98
        var d = holecenter.minus(existingholecenters[i2]).length();
99
        if(d < holeradius+existingholeradii[i2] + distancebetweenholes)
100
        {        
101
          valid = false;
102
          break;
103
        } 
104
      }
105
      if(valid)
106
      {
107
        // check if the hole is not too close to the edges:
108
        var hole = CAG.circle({radius: holeradius, center: holecenter});
109
        var testarea = maskarea.intersect(hole);
110
        if(testarea.sides.length !== 0) valid = false;
111
        if(valid)
112
        {
113
          existingholeradii.push(holeradius);
114
          existingholecenters.push(holecenter);
115
          holes.push(hole);
116
          break;
117
        }
118
      } 
119
    }
120
  }
121
  return plate.subtract(holes);
122
}
123

124
function plateCSGToCAG(plate)
125
{
126
  if(!("platebasis" in plate.properties))
127
  {
128
    throw new Error("Plates should be created using getStockPlate()");
129
  }
130
  var plate2d = plate.projectToOrthoNormalBasis(plate.properties.platebasis);
131
  return plate2d;
132
}
133

134
function plateCAGToCSG(plate2d, platebasis, thickness)
135
{
136
  var basisinversematrix = platebasis.getInverseProjectionMatrix();
137
  var plate_reprojected = plate2d.extrude({offset: [0,0,thickness]}).translate([0,0,-thickness/2]);
138
  plate_reprojected = plate_reprojected.transform(basisinversematrix);
139
  plate_reprojected.properties.platebasis = platebasis;
140
  return plate_reprojected;
141
}
142

143
function fixPlate(plate, thickness)
144
{
145
  return plateCAGToCSG(plateCSGToCAG(plate), plate.properties.platebasis, thickness); 
146
}
147

148
function removePlateWithNormal(plates, normalvector)
149
{
150
  normalvector = new CSG.Vector3D(normalvector);
151
  var result = [];
152
  plates.map(function(plate){
153
    if(!("platebasis" in plate.properties))
154
    {
155
      throw new Error("Plates should be created using getStockPlate()");
156
    } 
157
    if(plate.properties.platebasis.plane.normal.dot(normalvector) < 0.9999)
158
    {
159
      result.push(plate);
160
    } 
161
  });
162
  return result;
163
}
164

165
function getStockPlate(width, height, thickness)
166
{
167
  var result = CSG.cube({radius: [width/2, height/2, thickness/2]});
168
  result.properties.platebasis = CSG.OrthoNormalBasis.Z0Plane();
169
  return result;
170
}
171

172
function fingerJointAdd(plates, newplate, options)
173
{
174
  var result = plates.slice(0);
175
  var numplates = plates.length;
176
  for(var plateindex1 = 0; plateindex1 < numplates; plateindex1++)
177
  {
178
    var joined = fingerJointTwo(result[plateindex1], newplate, options);
179
    result[plateindex1] = joined[0];
180
    newplate = joined[1];
181
  }
182
  result.push(newplate);
183
  return result;
184
}
185

186
// Finger joint between multiple plates:
187
function fingerJoint(plates, options)
188
{
189
  var result = plates.slice(0);
190
  var numplates = plates.length;
191
  var maxdelta = Math.floor(numplates/2);
192
  for(var delta=1; delta <= maxdelta; delta++)
193
  { 
194
    for(var plateindex1 = 0; plateindex1 < numplates; plateindex1++)
195
    {
196
      var plateindex2 = plateindex1 + delta;
197
      if(plateindex2 >= numplates) plateindex2 -= numplates; 
198
      
199
      var joined = fingerJointTwo(result[plateindex1], result[plateindex2], options);
200
      result[plateindex1] = joined[0];
201
      result[plateindex2] = joined[1];
202
      if(delta*2 >= numplates)
203
      {
204
        // numplates is even
205
        if(plateindex1*2 >= numplates)
206
        {
207
          // and we've done the first half: we're done
208
          break;
209
        }
210
      }
211
    }
212
  }
213
  return result;
214
}
215

216
function fingerJointTwo(plate1, plate2, options)
217
{
218
  if(!options) options = {};
219
  if(!("platebasis" in plate1.properties))
220
  {
221
    throw new Error("Plates should be created using getStockPlate()");
222
  } 
223
  if(!("platebasis" in plate2.properties))
224
  {
225
    throw new Error("Plates should be created using getStockPlate()");
226
  } 
227
  // get the intersection solid of the 2 plates:
228
  var intersection = plate1.intersect(plate2);
229
  if(intersection.polygons.length === 0)
230
  {
231
    // plates do not intersect. Return unmodified:
232
    return [plate1, plate2];
233
  }
234
  else
235
  {
236
    var plane1 = plate1.properties.platebasis.plane; 
237
    var plane2 = plate2.properties.platebasis.plane;
238
    // get the intersection line of the 2 center planes:
239
    var 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:
242
    plane1 = CSG.Plane.fromNormalAndPoint(jointline.direction, jointline.point);
243
    // make the plane into an orthonormal basis:
244
    var basis1 = new CSG.OrthoNormalBasis(plane1);
245
    // get the projection matrix for the orthobasis:
246
    var matrix = basis1.getProjectionMatrix();
247
    // now transform the intersection solid:
248
    var intersection_transformed = intersection.transform(matrix);
249
    var bounds = intersection_transformed.getBounds();
250
    // now we know the two edge points. The joint line runs from jointline_origin, in the 
251
    // direction jointline_direction and has a length jointline_length (jointline_length >= 0) 
252
    var jointline_origin = jointline.point.plus(jointline.direction.times(bounds[0].z));
253
    var jointline_direction = jointline.direction;
254
    var jointline_length = bounds[1].z - bounds[0].z;
255
  
256
    var fingerwidth = options.fingerWidth || (jointline_length / 4); 
257
    var numfingers=Math.round(jointline_length / fingerwidth);
258
    if(numfingers < 2) numfingers=2;
259
    fingerwidth = jointline_length / numfingers;
260
    
261
    var margin = options.margin || 0;
262
    var cutterRadius = options.cutterRadius || 0; 
263
    var results = [];
264
    for(var plateindex = 0; plateindex < 2; plateindex++)
265
    {
266
      var 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:
269
      var platebasis = new CSG.OrthoNormalBasis(thisplate.properties.platebasis.plane, jointline_direction);
270
      // get the 2d shape of our plate:
271
      var plate2d = thisplate.projectToOrthoNormalBasis(platebasis);
272
      var jointline_origin_2d = platebasis.to2D(jointline_origin);
273
      matrix = platebasis.getProjectionMatrix();
274
      intersection_transformed = intersection.transform(matrix);
275
      bounds = intersection_transformed.getBounds();
276
      var maxz = bounds[1].z;
277
      var minz = bounds[0].z;
278
      var maxy = bounds[1].y + margin/2; 
279
      var miny = bounds[0].y - margin/2;
280
      
281
      var cutouts2d = [];
282
      for(var fingerindex = 0; fingerindex < numfingers; fingerindex++)
283
      {
284
        if( (plateindex === 0) && ((fingerindex & 1)===0) ) continue;
285
        if( (plateindex  == 1) && ((fingerindex & 1)!==0) ) continue;
286
        var minx = jointline_origin_2d.x + fingerindex * fingerwidth - margin/2;
287
        var maxx = minx + fingerwidth + margin;
288
        var cutout = createRectCutoutWithCutterRadius(minx, miny, maxx, maxy, cutterRadius, plate2d);
289
        cutouts2d.push(cutout);
290
      }
291
      var cutout2d = new CAG().union(cutouts2d);
292
      var cutout3d = cutout2d.extrude({offset: [0,0,maxz-minz]}).translate([0,0,minz]);
293
      cutout3d = cutout3d.transform(platebasis.getInverseProjectionMatrix());
294
      var thisplate_modified = thisplate.subtract(cutout3d);
295
      results[plateindex] = thisplate_modified;  
296
    } 
297
    return 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
306
function createRectCutoutWithCutterRadius(minx, miny, maxx, maxy, cutterRadius, plate2d)
307
{
308
  var deltax = maxx-minx;
309
  var deltay = maxy-miny;  
310
  var cutout = CAG.rectangle({radius: [(maxx-minx)/2, (maxy-miny)/2], center: [(maxx+minx)/2, (maxy+miny)/2]});
311
  var cornercutouts = [];
312
  if(cutterRadius > 0)
313
  {
314
    var extracutout = cutterRadius * 0.2;
315
    var hypcutterradius = cutterRadius / Math.sqrt(2.0);
316
    var halfcutterradius = 0.5 * cutterRadius;
317
    var dcx, dcy;
318
    if(deltax > 3*deltay)
319
    {
320
      dcx = cutterRadius + extracutout/2;
321
      dcy = extracutout / 2;
322
    }
323
    else if(deltay > 3*deltax)
324
    {
325
      dcx = extracutout / 2;
326
      dcy = cutterRadius + extracutout/2;
327
    }
328
    else
329
    {
330
      dcx = hypcutterradius-extracutout/2;
331
      dcy = hypcutterradius-extracutout/2;
332
    }
333
    for(var corner = 0; corner < 4; corner++)
334
    {
335
      var cutoutcenterx = (corner & 2)? (maxx-dcx):(minx+dcx);
336
      var cutoutcentery = (corner & 1)? (maxy-dcy):(miny+dcy);
337
      var cornercutout = CAG.rectangle({radius: [cutterRadius+extracutout/2, cutterRadius+extracutout/2], center: [cutoutcenterx, cutoutcentery]});
338
      var testrectacenterx = (corner & 2)? (maxx-halfcutterradius):(minx+halfcutterradius);
339
      var testrectbcenterx = (corner & 2)? (maxx+halfcutterradius):(minx-halfcutterradius);
340
      var testrectacentery = (corner & 1)? (maxy+halfcutterradius):(miny-halfcutterradius);
341
      var testrectbcentery = (corner & 1)? (maxy-halfcutterradius):(miny+halfcutterradius);
342
      var testrecta = CAG.rectangle({radius: [halfcutterradius, halfcutterradius], center: [testrectacenterx, testrectacentery]}); 
343
      var testrectb = CAG.rectangle({radius: [halfcutterradius, halfcutterradius], center: [testrectbcenterx, testrectbcentery]});
344
      if( (plate2d.intersect(testrecta).sides.length > 0)  &&
345
       (plate2d.intersect(testrectb).sides.length > 0) )
346
      {
347
        cornercutouts.push(cornercutout);
348
      }
349
    }
350
  }
351
  if(cornercutouts.length > 0)
352
  {
353
    cutout = cutout.union(cornercutouts);
354
  } 
355
  return cutout;
356
}
357

358
function solidToOuterShellPlates(csg, thickness)
359
{
360
  csg = csg.canonicalized();
361
  var bounds = csg.getBounds();
362
  var csgcenter = bounds[1].plus(bounds[0]).times(0.5);
363
  var csgradius = bounds[1].minus(bounds[0]).length();
364
  var plane2polygons = {};
365
  csg.polygons.map(function(polygon){
366
    var planetag = polygon.plane.getTag();
367
    if(!(planetag in plane2polygons))
368
    {
369
      plane2polygons[planetag] = [];
370
    }
371
    plane2polygons[planetag].push(polygon);
372
  });
373
  var plates = [];
374
  for(var planetag in plane2polygons)
375
  {
376
    var polygons = plane2polygons[planetag];
377
    var plane = polygons[0].plane;
378
    var shellcenterplane = new CSG.Plane(plane.normal, plane.w - thickness/2);    
379
    var basis = new CSG.OrthoNormalBasis(shellcenterplane);
380
    var inversebasisprojection = basis.getInverseProjectionMatrix(); 
381
    var csgcenter_projected = basis.to2D(csgcenter);
382
    var plate = getStockPlate(csgradius, csgradius, thickness).translate([csgcenter_projected.x, csgcenter_projected.y, 0]);
383
    plate = plate.transform(inversebasisprojection);
384
    plate = plate.intersect(csg);
385
    plates.push(plate);
386
  }
387
  return plates;
388
}
389

390
function getParameterDefinitions()
391
{
392
  return [
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
    {
402
      name: 'type',
403
      type: 'choice',
404
      values: ["ASSEMBLED", "TOPPLATE", "SIDEPLATE"],               // these are the values that will be supplied to your script
405
      captions: ["Assembled", "Top plate (DXF output)", "Side plate (DXF output)"],  // optional, these values are shown in the listbox
406
                                                   // if omitted, the items in the 'values' array are used
407
      caption: 'Show:',                           // optional, displayed left of the input field
408
      initial: "ASSEMBLED"                              // optional, default selected value
409
                                                   // if omitted, the first item is selected by default
410
    },
411
    {
412
      name: 'quality',
413
      type: 'choice',
414
      values: ["DRAFT", "HIGH"],               // these are the values that will be supplied to your script
415
      captions: ["Draft", "High"],  // optional, these values are shown in the listbox
416
                                                   // if omitted, the items in the 'values' array are used
417
      caption: 'Quality:',                           // optional, displayed left of the input field
418
      initial: "DRAFT"                              // optional, default selected value
419
                                                   // if omitted, the first item is selected by default
420
    }
421
  ];
422
}
423

424

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.