rcad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }