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.
- data/.gitignore +25 -0
- data/Gemfile +4 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/ext/_rcad/_rcad.cpp +795 -0
- data/ext/_rcad/extconf.rb +22 -0
- data/lib/rcad.rb +384 -0
- data/lib/rcad/gears.rb +144 -0
- data/lib/rcad/nuts.rb +87 -0
- data/lib/rcad/version.rb +3 -0
- data/pedestal.rb +13 -0
- data/rcad.gemspec +27 -0
- metadata +122 -0
data/.gitignore
ADDED
@@ -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
data/README.md
ADDED
@@ -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
|
+
```
|
data/Rakefile
ADDED
data/ext/_rcad/_rcad.cpp
ADDED
@@ -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
|
+
}
|