rcad 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+
21
+ *.o
22
+ *.so
23
+ *.log
24
+ *.stl
25
+ Makefile
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rcad.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ rcad
2
+ ====
3
+
4
+ Solid CAD programming with Ruby
5
+
6
+ Example:
7
+
8
+ ```ruby
9
+ require 'rcad'
10
+ require 'gears'
11
+
12
+ # overloaded ~ operator adds stuff to the shape
13
+ # you're working on
14
+ ~sub do
15
+ ~SpurGear.new(1.cm, 4.8.cm)
16
+
17
+ # make a hole for an M3 screw
18
+ ~cylinder(1.cm, 3.mm)
19
+ end
20
+
21
+ # STL is written automatically on exit
22
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+
4
+ Rake::ExtensionTask.new "_rcad" do |ext|
5
+ ext.lib_dir = "lib/rcad"
6
+ end
@@ -0,0 +1,795 @@
1
+ #include <gp_Pnt2d.hxx>
2
+ #include <gp_Pnt.hxx>
3
+ #include <gp_Vec.hxx>
4
+ #include <gp_Circ.hxx>
5
+ #include <TColgp_Array2OfPnt.hxx>
6
+ #include <Poly_Triangulation.hxx>
7
+ #include <Geom_BezierSurface.hxx>
8
+ #include <GCE2d_MakeSegment.hxx>
9
+ #include <TopoDS.hxx>
10
+ #include <BRepPrimAPI_MakeBox.hxx>
11
+ #include <BRepPrimAPI_MakeCylinder.hxx>
12
+ #include <BRepPrimAPI_MakeSphere.hxx>
13
+ #include <BRepPrimAPI_MakeTorus.hxx>
14
+ #include <BRepPrimAPI_MakePrism.hxx>
15
+ #include <BRepPrimAPI_MakeRevol.hxx>
16
+ #include <BRepOffsetAPI_MakePipeShell.hxx>
17
+ #include <BRepAlgoAPI_Fuse.hxx>
18
+ #include <BRepAlgoAPI_Cut.hxx>
19
+ #include <BRepAlgoAPI_Common.hxx>
20
+ #include <BRepAlgoAPI_Common.hxx>
21
+ #include <BRepBuilderAPI_MakeEdge.hxx>
22
+ #include <BRepBuilderAPI_MakeWire.hxx>
23
+ #include <BRepBuilderAPI_MakePolygon.hxx>
24
+ #include <BRepBuilderAPI_MakeFace.hxx>
25
+ #include <BRepBuilderAPI_MakeSolid.hxx>
26
+ #include <BRepBuilderAPI_Transform.hxx>
27
+ #include <BRepBuilderAPI_GTransform.hxx>
28
+ #include <BRepBuilderAPI_Sewing.hxx>
29
+ #include <BRepClass3d_SolidClassifier.hxx>
30
+ #include <BRepTopAdaptor_FClass2d.hxx>
31
+ #include <BRepMesh_IncrementalMesh.hxx>
32
+ #include <StlAPI_Reader.hxx>
33
+ #include <StlAPI_Writer.hxx>
34
+ #include <Standard_Failure.hxx>
35
+ #include <rice/Class.hpp>
36
+ #include <rice/Exception.hpp>
37
+ #include <rice/Array.hpp>
38
+ #include <rice/global_function.hpp>
39
+
40
+ extern "C" {
41
+ #include <qhull/qhull_a.h>
42
+ }
43
+
44
+
45
+ using namespace Rice;
46
+
47
+
48
+ Data_Type<Standard_Failure> rb_cOCEError;
49
+ Data_Type<TopoDS_Shape> rb_cRenderedShape;
50
+ Class rb_cShape;
51
+
52
+
53
+ // for debugging
54
+ std::ostream &operator <<(std::ostream &lhs, gp_Pnt rhs)
55
+ {
56
+ return lhs << "(" << rhs.X() << "," << rhs.Y() << "," << rhs.Z() << ")";
57
+ }
58
+
59
+ std::ostream &operator <<(std::ostream &lhs, gp_Vec rhs)
60
+ {
61
+ return lhs << "(" << rhs.X() << "," << rhs.Y() << "," << rhs.Z() << ")";
62
+ }
63
+
64
+
65
+ void translate_oce_exception(const Standard_Failure &e)
66
+ {
67
+ //Data_Object<Standard_Failure> e_obj(
68
+ // new Standard_Failure(e), rb_cOCEError);
69
+ throw Exception(rb_cOCEError, "%s", e.GetMessageString());
70
+ }
71
+
72
+
73
+ template<>
74
+ gp_Pnt2d from_ruby<gp_Pnt2d>(Object obj)
75
+ {
76
+ Array ary(obj);
77
+
78
+ if (ary.size() != 2) {
79
+ throw Exception(rb_eArgError,
80
+ "2D points must be arrays with 2 numbers each");
81
+ }
82
+
83
+ return gp_Pnt2d(
84
+ from_ruby<Standard_Real>(ary[0]),
85
+ from_ruby<Standard_Real>(ary[1]));
86
+ }
87
+
88
+ template<>
89
+ gp_Pnt from_ruby<gp_Pnt>(Object obj)
90
+ {
91
+ Array ary(obj);
92
+
93
+ if (ary.size() == 2) {
94
+ return gp_Pnt(
95
+ from_ruby<Standard_Real>(ary[0]),
96
+ from_ruby<Standard_Real>(ary[1]),
97
+ 0);
98
+ } else if (ary.size() == 3) {
99
+ return gp_Pnt(
100
+ from_ruby<Standard_Real>(ary[0]),
101
+ from_ruby<Standard_Real>(ary[1]),
102
+ from_ruby<Standard_Real>(ary[2]));
103
+ } else {
104
+ throw Exception(rb_eArgError,
105
+ "3D points must be arrays with 2 or 3 numbers each");
106
+ }
107
+ }
108
+
109
+ template<>
110
+ gp_Vec from_ruby<gp_Vec>(Object obj)
111
+ {
112
+ Array ary(obj);
113
+
114
+ if (ary.size() != 3) {
115
+ throw Exception(rb_eArgError,
116
+ "Vectors must be arrays with 3 numbers each");
117
+ }
118
+
119
+ return gp_Vec(
120
+ from_ruby<Standard_Real>(ary[0]),
121
+ from_ruby<Standard_Real>(ary[1]),
122
+ from_ruby<Standard_Real>(ary[2]));
123
+ }
124
+
125
+ template<>
126
+ gp_Dir from_ruby<gp_Dir>(Object obj)
127
+ {
128
+ return gp_Dir(from_ruby<gp_Vec>(obj));
129
+ }
130
+
131
+
132
+ static Data_Object<TopoDS_Shape> render_shape(Object shape)
133
+ {
134
+ if (shape.is_a(rb_cRenderedShape)) {
135
+ return shape;
136
+ }
137
+
138
+ if (!shape.is_a(rb_cShape)) {
139
+ String shape_str = shape.to_s();
140
+ throw Exception(rb_eArgError,
141
+ "attempt to render %s which is not a Shape",
142
+ shape_str.c_str());
143
+ }
144
+
145
+ while (shape.is_a(rb_cShape)) {
146
+ shape = shape.call("render");
147
+ }
148
+
149
+ if (!shape.is_a(rb_cRenderedShape)) {
150
+ String shape_str = shape.to_s();
151
+ throw Exception(rb_eArgError,
152
+ "render returned %s instead of a rendered shape",
153
+ shape_str.c_str());
154
+ }
155
+
156
+ return shape;
157
+ }
158
+
159
+ static Object wrap_rendered_shape(const TopoDS_Shape &shape)
160
+ {
161
+ Object shape_obj = rb_cShape.call("new");
162
+ shape_obj.iv_set("@shape", to_ruby(shape));
163
+ return shape_obj;
164
+ }
165
+
166
+ static Object shape_transform(Object self, const gp_Trsf &transform)
167
+ {
168
+ Data_Object<TopoDS_Shape> rendered = render_shape(self);
169
+ return wrap_rendered_shape(
170
+ BRepBuilderAPI_Transform(*rendered, transform, Standard_True).Shape());
171
+ }
172
+
173
+ Object shape_move(Object self, Standard_Real x, Standard_Real y,
174
+ Standard_Real z)
175
+ {
176
+ gp_Trsf transform;
177
+ transform.SetTranslation(gp_Vec(x, y, z));
178
+ return shape_transform(self, transform);
179
+ }
180
+
181
+ Object shape_rotate(Object self, Standard_Real angle, gp_Dir axis)
182
+ {
183
+ gp_Trsf transform;
184
+ transform.SetRotation(gp_Ax1(gp_Pnt(), axis), angle);
185
+ return shape_transform(self, transform);
186
+ }
187
+
188
+ Object shape_scale(Object self, Standard_Real x, Standard_Real y,
189
+ Standard_Real z)
190
+ {
191
+ gp_GTrsf transform;
192
+ transform.SetVectorialPart(
193
+ gp_Mat(x, 0, 0,
194
+ 0, y, 0,
195
+ 0, 0, z));
196
+
197
+ Data_Object<TopoDS_Shape> rendered = render_shape(self);
198
+ return wrap_rendered_shape(
199
+ BRepBuilderAPI_GTransform(*rendered, transform, Standard_True).Shape());
200
+ }
201
+
202
+ void shape_write_stl(Object self, String path)
203
+ {
204
+ Data_Object<TopoDS_Shape> shape = render_shape(self);
205
+
206
+ StlAPI_Writer writer;
207
+ writer.ASCIIMode() = false;
208
+ writer.RelativeMode() = false;
209
+ writer.SetDeflection(0.05); // TODO: deflection param
210
+ writer.Write(*shape, path.c_str());
211
+ }
212
+
213
+
214
+ Object shape_from_stl(String path)
215
+ {
216
+ TopoDS_Shape shape;
217
+ StlAPI_Reader reader;
218
+ reader.Read(shape, path.c_str());
219
+ return wrap_rendered_shape(shape);
220
+ }
221
+
222
+
223
+ static TopoDS_Wire make_wire_from_path(Array points, Array path)
224
+ {
225
+ BRepBuilderAPI_MakeWire wire_maker;
226
+
227
+ for (size_t i = 0; i < path.size(); ++i) {
228
+ const size_t j = (i + 1) % path.size();
229
+
230
+ const size_t p1_idx = from_ruby<size_t>(path[i]);
231
+ const size_t p2_idx = from_ruby<size_t>(path[j]);
232
+
233
+ gp_Pnt gp_p1(from_ruby<gp_Pnt>(points[p1_idx]));
234
+ gp_Pnt gp_p2(from_ruby<gp_Pnt>(points[p2_idx]));
235
+
236
+ wire_maker.Add(BRepBuilderAPI_MakeEdge(gp_p1, gp_p2).Edge());
237
+ }
238
+
239
+ return wire_maker.Wire();
240
+ }
241
+
242
+ Object polygon_render(Object self)
243
+ {
244
+ const Array points = self.iv_get("@points");
245
+ const Array paths = self.iv_get("@paths");
246
+
247
+ if (paths.size() == 0) {
248
+ throw Exception(rb_eArgError,
249
+ "Polygon must have at least 1 path!");
250
+ }
251
+
252
+ BRepBuilderAPI_MakeFace face_maker(
253
+ make_wire_from_path(points, paths[0]));
254
+ for (size_t i = 1; i < paths.size(); ++i) {
255
+ TopoDS_Wire wire = make_wire_from_path(points, paths[i]);
256
+
257
+ // all paths except the first are inner loops,
258
+ // so they should be reversed
259
+ face_maker.Add(TopoDS::Wire(wire.Oriented(TopAbs_REVERSED)));
260
+ }
261
+
262
+ return wrap_rendered_shape(face_maker.Shape());
263
+ }
264
+
265
+ Object circle_render(Object self)
266
+ {
267
+ Standard_Real dia = from_ruby<Standard_Real>(self.iv_get("@dia"));
268
+ gp_Circ circ(gp_Ax2(), dia / 2.0);
269
+ TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circ).Edge();
270
+ TopoDS_Wire wire = BRepBuilderAPI_MakeWire(edge).Wire();
271
+ return wrap_rendered_shape(BRepBuilderAPI_MakeFace(wire).Shape());
272
+ }
273
+
274
+
275
+ Object box_render(Object self)
276
+ {
277
+ Standard_Real xsize = from_ruby<Standard_Real>(self.iv_get("@xsize"));
278
+ Standard_Real ysize = from_ruby<Standard_Real>(self.iv_get("@ysize"));
279
+ Standard_Real zsize = from_ruby<Standard_Real>(self.iv_get("@zsize"));
280
+
281
+ return to_ruby(BRepPrimAPI_MakeBox(xsize, ysize, zsize).Shape());
282
+ }
283
+
284
+
285
+ Object cylinder_render(Object self)
286
+ {
287
+ Standard_Real height = from_ruby<Standard_Real>(self.iv_get("@height"));
288
+ Standard_Real dia = from_ruby<Standard_Real>(self.iv_get("@dia"));
289
+ return to_ruby(BRepPrimAPI_MakeCylinder(dia / 2.0, height).Shape());
290
+ }
291
+
292
+
293
+ Object sphere_render(Object self)
294
+ {
295
+ Standard_Real dia = from_ruby<Standard_Real>(self.iv_get("@dia"));
296
+ return to_ruby(BRepPrimAPI_MakeSphere(dia / 2.0).Shape());
297
+ }
298
+
299
+
300
+ // check if shape is inside-out and fix it if it is
301
+ static void fix_inside_out_solid(TopoDS_Solid &solid)
302
+ {
303
+ BRepClass3d_SolidClassifier classifier(solid);
304
+ classifier.PerformInfinitePoint(Precision::Confusion());
305
+ if (classifier.State() == TopAbs_IN) {
306
+ solid = TopoDS::Solid(solid.Oriented(TopAbs_REVERSED));
307
+ }
308
+ }
309
+
310
+ static Object polyhedron_render_internal(const Array points, const Array faces)
311
+ {
312
+ if (faces.size() < 4) {
313
+ throw Exception(rb_eArgError,
314
+ "Polyhedron must have at least 4 faces!");
315
+ }
316
+
317
+ BRepBuilderAPI_Sewing sewing;
318
+
319
+ for (size_t i = 0; i < faces.size(); ++i) {
320
+ TopoDS_Wire wire = make_wire_from_path(points, faces[i]);
321
+
322
+ sewing.Add(BRepBuilderAPI_MakeFace(wire).Face());
323
+ }
324
+
325
+ sewing.Perform();
326
+
327
+ TopoDS_Shell shell = TopoDS::Shell(sewing.SewedShape());
328
+ // TODO: check for free/multiple edges and problems from sewing object
329
+
330
+ TopoDS_Solid solid = BRepBuilderAPI_MakeSolid(shell).Solid();
331
+
332
+ fix_inside_out_solid(solid);
333
+
334
+ return wrap_rendered_shape(solid);
335
+ }
336
+
337
+ Object polyhedron_render(Object self)
338
+ {
339
+ const Array points = self.iv_get("@points");
340
+ const Array faces = self.iv_get("@faces");
341
+ return polyhedron_render_internal(points, faces);
342
+ }
343
+
344
+
345
+ Object torus_render(Object self)
346
+ {
347
+ Standard_Real inner_dia = from_ruby<Standard_Real>(
348
+ self.iv_get("@inner_dia"));
349
+
350
+ Standard_Real outer_dia = from_ruby<Standard_Real>(
351
+ self.iv_get("@outer_dia"));
352
+
353
+ Standard_Real r1 = inner_dia / 2.0;
354
+ Standard_Real r2 = outer_dia / 2.0;
355
+
356
+ Object angle = self.iv_get("@angle");
357
+ if (angle.is_nil()) {
358
+ return to_ruby(BRepPrimAPI_MakeTorus(r1, r2).Shape());
359
+ } else {
360
+ Standard_Real angle_num = from_ruby<Standard_Real>(angle);
361
+ return to_ruby(BRepPrimAPI_MakeTorus(r1, r2, angle_num).Shape());
362
+ }
363
+ }
364
+
365
+
366
+ void combination_initialize(Object self, Object a, Object b)
367
+ {
368
+ self.iv_set("@a", a);
369
+ self.iv_set("@b", b);
370
+ }
371
+
372
+
373
+ Object union_render(Object self)
374
+ {
375
+ Data_Object<TopoDS_Shape> shape_a = render_shape(self.iv_get("@a"));
376
+ Data_Object<TopoDS_Shape> shape_b = render_shape(self.iv_get("@b"));
377
+ return to_ruby(
378
+ BRepAlgoAPI_Fuse(*shape_a, *shape_b).Shape());
379
+ }
380
+
381
+
382
+ Object difference_render(Object self)
383
+ {
384
+ Data_Object<TopoDS_Shape> shape_a = render_shape(self.iv_get("@a"));
385
+ Data_Object<TopoDS_Shape> shape_b = render_shape(self.iv_get("@b"));
386
+ return to_ruby(
387
+ BRepAlgoAPI_Cut(*shape_a, *shape_b).Shape());
388
+ }
389
+
390
+
391
+ Object intersection_render(Object self)
392
+ {
393
+ Data_Object<TopoDS_Shape> shape_a = render_shape(self.iv_get("@a"));
394
+ Data_Object<TopoDS_Shape> shape_b = render_shape(self.iv_get("@b"));
395
+ return to_ruby(
396
+ BRepAlgoAPI_Common(*shape_a, *shape_b).Shape());
397
+ }
398
+
399
+
400
+ static bool is_inner_wire_of_face(TopoDS_Wire wire, TopoDS_Face face)
401
+ {
402
+ // recipe from http://opencascade.wikidot.com/recipes
403
+ TopoDS_Face newface = TopoDS::Face(
404
+ face.EmptyCopied().Oriented(TopAbs_FORWARD));
405
+
406
+ BRep_Builder builder;
407
+ builder.Add(newface, wire);
408
+
409
+ BRepTopAdaptor_FClass2d fclass2D(newface, Precision::PConfusion());
410
+ return (fclass2D.PerformInfinitePoint() != TopAbs_OUT);
411
+ }
412
+
413
+ static TopoDS_Shape twist_extrude_wire(TopoDS_Wire wire, Standard_Real height,
414
+ Standard_Real twist)
415
+ {
416
+ // split height into segments. each segment will twist no more than
417
+ // 90 degrees.
418
+ const int num_twist_segments = (int)(fabs(twist) / M_PI_2 + 1);
419
+ // note that height and twist are doubles so division is not integer
420
+ // division
421
+ const Standard_Real seg_height = height / num_twist_segments;
422
+ const Standard_Real seg_twist = twist / num_twist_segments;
423
+
424
+ Handle_Geom_BezierSurface surf_hnd(
425
+ new Geom_BezierSurface(
426
+ TColgp_Array2OfPnt(
427
+ 1, num_twist_segments + 1,
428
+ 1, 2)));
429
+
430
+ for (int i = 1; i <= num_twist_segments + 1; ++i) {
431
+ const Standard_Real z = seg_height * (i - 1);
432
+ const Standard_Real angle = seg_twist * (i - 1);
433
+ surf_hnd->SetPole(i, 1, gp_Pnt(0, 0, z));
434
+ surf_hnd->SetPole(i, 2, gp_Pnt(cos(angle), sin(angle), z));
435
+ }
436
+
437
+ TopoDS_Face spine_support =
438
+ // TODO: tolerance
439
+ BRepBuilderAPI_MakeFace(surf_hnd, 0, 1, 0, 1,
440
+ Precision::Confusion());
441
+
442
+ Handle_Geom2d_Curve uv_curve_hnd =
443
+ GCE2d_MakeSegment(gp_Pnt2d(0, 0), gp_Pnt2d(1, 0));
444
+ TopoDS_Edge spine = BRepBuilderAPI_MakeEdge(uv_curve_hnd, surf_hnd);
445
+ TopoDS_Wire spine_wire = BRepBuilderAPI_MakeWire(spine);
446
+
447
+
448
+ BRepOffsetAPI_MakePipeShell pipe_maker(spine_wire);
449
+
450
+ if (!pipe_maker.SetMode(spine_support)) {
451
+ throw Exception(rb_cOCEError,
452
+ "failed setting twisted surface-normal for PipeShell");
453
+ }
454
+
455
+ pipe_maker.Add(wire);
456
+ pipe_maker.SetTolerance(0.05, 0.05); // TODO: tolerance
457
+ pipe_maker.Build();
458
+
459
+ if (!pipe_maker.MakeSolid()) {
460
+ throw Exception(rb_cOCEError, "failed making extrusion solid");
461
+ }
462
+
463
+ return pipe_maker.Shape();
464
+ }
465
+
466
+ static TopoDS_Shape twist_extrude_face(TopoDS_Face face, Standard_Real height,
467
+ Standard_Real twist)
468
+ {
469
+ // extrude outer and inner wires separately, then subtract the inner
470
+ // shapes from the outer shape. there should be only one outer shape,
471
+ // and zero or more inner shapes.
472
+
473
+ TopoDS_Shape outer;
474
+
475
+ TopoDS_Compound inner;
476
+ BRep_Builder builder;
477
+ builder.MakeCompound(inner);
478
+
479
+ TopExp_Explorer texp;
480
+
481
+ TopoDS_Face orface = TopoDS::Face(face.Oriented(TopAbs_FORWARD));
482
+ for (texp.Init(orface, TopAbs_WIRE); texp.More(); texp.Next()) {
483
+ TopoDS_Wire wire = TopoDS::Wire(texp.Current());
484
+ TopoDS_Shape ext_wire = twist_extrude_wire(wire, height, twist);
485
+
486
+ if (is_inner_wire_of_face(wire, orface)) {
487
+ builder.Add(inner, ext_wire);
488
+ } else {
489
+ outer = ext_wire;
490
+ }
491
+ }
492
+
493
+ return BRepAlgoAPI_Cut(outer, inner).Shape();
494
+ }
495
+
496
+ // initialize is defined in Ruby code
497
+ Object linear_extrusion_render(Object self)
498
+ {
499
+ Object profile = self.iv_get("@profile");
500
+ Standard_Real height = from_ruby<Standard_Real>(self.iv_get("@height"));
501
+ Standard_Real twist = from_ruby<Standard_Real>(self.iv_get("@twist"));
502
+
503
+ Data_Object<TopoDS_Shape> shape = render_shape(profile);
504
+
505
+ if (0 == twist) {
506
+ return to_ruby(
507
+ BRepPrimAPI_MakePrism(*shape, gp_Vec(0, 0, height),
508
+ Standard_True).Shape());
509
+ } else {
510
+ BRep_Builder builder;
511
+ TopoDS_Compound compound;
512
+ builder.MakeCompound(compound);
513
+
514
+ TopExp_Explorer texp;
515
+ for (texp.Init(*shape, TopAbs_FACE); texp.More(); texp.Next()) {
516
+ builder.Add(compound,
517
+ twist_extrude_face(
518
+ TopoDS::Face(texp.Current()), height, twist));
519
+ }
520
+
521
+ return wrap_rendered_shape(compound);
522
+ }
523
+ }
524
+
525
+
526
+ // initialize is defined in Ruby code
527
+ Object revolution_render(Object self)
528
+ {
529
+ Object profile = self.iv_get("@profile");
530
+ Data_Object<TopoDS_Shape> shape = render_shape(profile);
531
+
532
+ Object angle = self.iv_get("@angle");
533
+ if (angle.is_nil()) {
534
+ return to_ruby(
535
+ BRepPrimAPI_MakeRevol(*shape, gp::OY(), Standard_True).Shape());
536
+ } else {
537
+ Standard_Real angle_num = from_ruby<Standard_Real>(angle);
538
+ return to_ruby(
539
+ BRepPrimAPI_MakeRevol(*shape, gp::OY(), angle_num,
540
+ Standard_True).Shape());
541
+ }
542
+ }
543
+
544
+
545
+ static void get_points_from_shape(const TopoDS_Shape &shape,
546
+ std::vector<gp_Pnt> &points)
547
+ {
548
+ TopExp_Explorer ex(shape, TopAbs_FACE);
549
+ for (; ex.More(); ex.Next()) {
550
+ const TopoDS_Face& face = TopoDS::Face(ex.Current());
551
+
552
+ TopLoc_Location loc;
553
+ Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(face, loc);
554
+
555
+ if (tri.IsNull()) {
556
+ throw Exception(rb_cOCEError, "No triangulation");
557
+ }
558
+
559
+ const Standard_Integer numNodes = tri->NbNodes();
560
+ const TColgp_Array1OfPnt& nodes = tri->Nodes();
561
+ for (Standard_Integer i = 1; i <= numNodes; i++) {
562
+ points.push_back(
563
+ loc.IsIdentity()
564
+ ? nodes(i)
565
+ : nodes(i).Transformed(loc));
566
+ }
567
+ }
568
+ }
569
+
570
+ static std::vector<gp_Pnt> get_points_from_shapes(Array shapes)
571
+ {
572
+ std::vector<gp_Pnt> points;
573
+
574
+ for (size_t i = 0; i < shapes.size(); ++i) {
575
+ Data_Object<TopoDS_Shape> shape_obj = render_shape(shapes[i]);
576
+ const TopoDS_Shape &shape = *shape_obj;
577
+
578
+ BRepMesh_IncrementalMesh(shape, 0.05); // TODO: tolerance
579
+
580
+ get_points_from_shape(shape, points);
581
+ }
582
+
583
+ return points;
584
+ }
585
+
586
+
587
+ // Sort function object to sort polygon vertices
588
+ // Algorithm from here:
589
+ // http://stackoverflow.com/a/15104911/2758814
590
+ class PolygonVertexSortComparator
591
+ {
592
+ public:
593
+ PolygonVertexSortComparator(std::vector<gp_Pnt> vertices)
594
+ : vertices(vertices)
595
+ {
596
+ if (vertices.size() == 0) {
597
+ throw std::exception();
598
+ }
599
+
600
+ // get center point
601
+ for (size_t i = 0; i < vertices.size(); ++i) {
602
+ center.Translate(gp::Origin(), vertices[i]);
603
+ }
604
+ center.Scale(gp::Origin(), 1.0/vertices.size());
605
+
606
+ center_to_first_vert = gp_Vec(center, vertices[0]);
607
+
608
+ // TODO: ensure that normals are all in consistent direction
609
+ // by specifying a point that should be on the front or back side of
610
+ // the polygon
611
+
612
+ // get a normal with one of the other vectors
613
+ // (must find a non-zero normal, i.e. other vector must not be
614
+ // parallel to first vector)
615
+ for (size_t i = 1; i < vertices.size(); ++i) {
616
+ gp_Vec another_vec = gp_Vec(center, vertices[i]);
617
+ normal = center_to_first_vert ^ another_vec;
618
+ if (normal.Magnitude() > Precision::Confusion()) {
619
+ break;
620
+ }
621
+ }
622
+
623
+ if (normal.Magnitude() <= Precision::Confusion()) {
624
+ // couldn't find a non-zero normal. normal still zero
625
+ throw std::exception();
626
+ }
627
+ }
628
+
629
+ bool operator() (gp_Pnt a, gp_Pnt b) {
630
+ double angle1 = get_angle_to_vec(a);
631
+ double angle2 = get_angle_to_vec(b);
632
+ return angle1 < angle2;
633
+ }
634
+
635
+ private:
636
+ double get_angle_to_vec(gp_Pnt vertex) {
637
+ gp_Vec center_to_vertex(center, vertex);
638
+
639
+ // use normal as reference to define positive rotation angle
640
+ return center_to_first_vert.AngleWithRef(center_to_vertex, normal);
641
+ }
642
+
643
+ std::vector<gp_Pnt> vertices;
644
+ gp_Pnt center;
645
+ gp_Vec center_to_first_vert;
646
+ gp_Vec normal;
647
+ };
648
+
649
+ static TopoDS_Solid make_solid_from_qhull()
650
+ {
651
+ BRepBuilderAPI_Sewing sewing;
652
+
653
+ facetT *facet;
654
+ FORALLfacets {
655
+ // get vertices into an std::vector
656
+ std::vector<gp_Pnt> vertices;
657
+ vertices.reserve(qh_setsize(facet->vertices));
658
+
659
+ vertexT *vertex, **vertexp;
660
+ FOREACHvertex_(facet->vertices) {
661
+ vertices.push_back(gp_Pnt(vertex->point[0], vertex->point[1],
662
+ vertex->point[2]));
663
+ }
664
+
665
+ sort(vertices.begin(), vertices.end(),
666
+ PolygonVertexSortComparator(vertices));
667
+
668
+ BRepBuilderAPI_MakePolygon poly_maker;
669
+ for (size_t i = 0; i < vertices.size(); ++i) {
670
+ poly_maker.Add(vertices[i]);
671
+ }
672
+ poly_maker.Close();
673
+
674
+ sewing.Add(BRepBuilderAPI_MakeFace(poly_maker.Wire()).Face());
675
+ }
676
+
677
+ sewing.Perform();
678
+
679
+ TopoDS_Shell shell = TopoDS::Shell(sewing.SewedShape());
680
+ // TODO: check for free/multiple edges and problems from sewing object
681
+
682
+ TopoDS_Solid solid = BRepBuilderAPI_MakeSolid(shell).Solid();
683
+
684
+ fix_inside_out_solid(solid);
685
+
686
+ return solid;
687
+ }
688
+
689
+ static Object _hull(Array shapes)
690
+ {
691
+ try {
692
+ std::vector<gp_Pnt> points = get_points_from_shapes(shapes);
693
+
694
+ char flags[128];
695
+ strcpy(flags, "qhull Qt");
696
+ int err = qh_new_qhull(3, points.size(),
697
+ // each point contains a gp_XYZ which contains X,Y,Z as Standard_Reals
698
+ reinterpret_cast<Standard_Real*>(points.data()),
699
+ false, flags, NULL, stderr);
700
+ if (err) {
701
+ throw Exception(rb_cOCEError, "Error running qhull");
702
+ }
703
+
704
+ TopoDS_Solid solid = make_solid_from_qhull();
705
+
706
+ qh_freeqhull(!qh_ALL);
707
+ int curlong, totlong;
708
+ qh_memfreeshort(&curlong, &totlong);
709
+ if (curlong || totlong) {
710
+ throw Exception(rb_cOCEError,
711
+ "did not free %d bytes of long memory (%d pieces)",
712
+ totlong, curlong);
713
+ }
714
+
715
+ return wrap_rendered_shape(solid);
716
+ } catch (const Standard_Failure &e) {
717
+ // this throws an exception, so return won't be reached
718
+ translate_oce_exception(e);
719
+ return Object(Qnil);
720
+ }
721
+ }
722
+
723
+
724
+ extern "C"
725
+ void Init__rcad()
726
+ {
727
+ Data_Type<TopoDS_Shape> rb_cRenderedShape =
728
+ define_class<TopoDS_Shape>("RenderedShape");
729
+
730
+ Data_Type<Standard_Failure> rb_cOCEError =
731
+ define_class("OCEError", rb_eRuntimeError);
732
+
733
+ rb_cShape = define_class("Shape")
734
+ .add_handler<Standard_Failure>(translate_oce_exception)
735
+ .define_method("move", &shape_move)
736
+ .define_method("rotate", &shape_rotate)
737
+ .define_method("scale", &shape_scale)
738
+ .define_method("write_stl", &shape_write_stl)
739
+ .define_singleton_method("from_stl", &shape_from_stl);
740
+
741
+ Class rb_cPolygon = define_class("Polygon", rb_cShape)
742
+ .add_handler<Standard_Failure>(translate_oce_exception)
743
+ .define_method("render", &polygon_render);
744
+
745
+ Class rb_cCircle = define_class("Circle", rb_cShape)
746
+ .add_handler<Standard_Failure>(translate_oce_exception)
747
+ .define_method("render", &circle_render);
748
+
749
+
750
+ Class rb_cBox = define_class("Box", rb_cShape)
751
+ .add_handler<Standard_Failure>(translate_oce_exception)
752
+ .define_method("render", &box_render);
753
+
754
+ Class rb_cCylinder = define_class("Cylinder", rb_cShape)
755
+ .add_handler<Standard_Failure>(translate_oce_exception)
756
+ .define_method("render", &cylinder_render);
757
+
758
+ Class rb_cSphere = define_class("Sphere", rb_cShape)
759
+ .add_handler<Standard_Failure>(translate_oce_exception)
760
+ .define_method("render", &sphere_render);
761
+
762
+ Class rb_cPolyhedron = define_class("Polyhedron", rb_cShape)
763
+ .add_handler<Standard_Failure>(translate_oce_exception)
764
+ .define_method("render", &polyhedron_render);
765
+
766
+ Class rb_cTorus = define_class("Torus", rb_cShape)
767
+ .add_handler<Standard_Failure>(translate_oce_exception)
768
+ .define_method("render", &torus_render);
769
+
770
+ Class rb_cCombination = define_class("Combination", rb_cShape)
771
+ .add_handler<Standard_Failure>(translate_oce_exception)
772
+ .define_method("initialize", &combination_initialize);
773
+
774
+ Class rb_cUnion = define_class("Union", rb_cCombination)
775
+ .add_handler<Standard_Failure>(translate_oce_exception)
776
+ .define_method("render", &union_render);
777
+
778
+ Class rb_cDifference = define_class("Difference", rb_cCombination)
779
+ .add_handler<Standard_Failure>(translate_oce_exception)
780
+ .define_method("render", &difference_render);
781
+
782
+ Class rb_cIntersection = define_class("Intersection", rb_cCombination)
783
+ .add_handler<Standard_Failure>(translate_oce_exception)
784
+ .define_method("render", &intersection_render);
785
+
786
+ Class rb_cLinearExtrusion = define_class("LinearExtrusion", rb_cShape)
787
+ .add_handler<Standard_Failure>(translate_oce_exception)
788
+ .define_method("render", &linear_extrusion_render);
789
+
790
+ Class rb_cRevolution = define_class("Revolution", rb_cShape)
791
+ .add_handler<Standard_Failure>(translate_oce_exception)
792
+ .define_method("render", &revolution_render);
793
+
794
+ define_global_function("_hull", &_hull);
795
+ }