2015-02-14 23:32:58 +01:00
|
|
|
/** \file
|
|
|
|
* Generate an OpenSCAD with connectors for each face.
|
|
|
|
*
|
2015-05-03 19:59:18 +02:00
|
|
|
* This imports the original STL file and then slices the corners
|
|
|
|
* off from it.
|
2015-02-22 23:32:06 +01:00
|
|
|
* Options are inside only (with face flush on outside)
|
|
|
|
* or with a slot for the face (like a corner cap)
|
2015-02-14 23:32:58 +01:00
|
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <unistd.h>
|
2015-05-03 19:59:18 +02:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2015-02-14 23:32:58 +01:00
|
|
|
#include <math.h>
|
|
|
|
#include <err.h>
|
|
|
|
#include <assert.h>
|
2015-05-05 03:23:45 +02:00
|
|
|
#include <getopt.h>
|
2015-02-14 23:32:58 +01:00
|
|
|
#include "v3.h"
|
2015-02-14 23:59:08 +01:00
|
|
|
#include "stl_3d.h"
|
2015-02-14 23:32:58 +01:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
static FILE * output;
|
|
|
|
static int verbose;
|
2015-02-14 23:32:58 +01:00
|
|
|
|
2015-02-22 23:32:06 +01:00
|
|
|
|
|
|
|
static void
|
|
|
|
print_multmatrix(
|
|
|
|
const refframe_t * const ref,
|
|
|
|
const int transpose
|
|
|
|
)
|
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "multmatrix(m=["
|
2015-02-22 23:32:06 +01:00
|
|
|
"[%f,%f,%f,0],"
|
|
|
|
"[%f,%f,%f,0],"
|
|
|
|
"[%f,%f,%f,0],"
|
|
|
|
"[ 0, 0, 0,1]])\n",
|
|
|
|
transpose ? ref->x.p[0] : ref->x.p[0],
|
|
|
|
transpose ? ref->x.p[1] : ref->y.p[0],
|
|
|
|
transpose ? ref->x.p[2] : ref->z.p[0],
|
|
|
|
transpose ? ref->y.p[0] : ref->x.p[1],
|
|
|
|
transpose ? ref->y.p[1] : ref->y.p[1],
|
|
|
|
transpose ? ref->y.p[2] : ref->z.p[1],
|
|
|
|
transpose ? ref->z.p[0] : ref->x.p[2],
|
|
|
|
transpose ? ref->z.p[1] : ref->y.p[2],
|
|
|
|
transpose ? ref->z.p[2] : ref->z.p[2]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-03-07 23:45:25 +01:00
|
|
|
|
2015-02-15 22:57:37 +01:00
|
|
|
static void
|
2015-05-03 21:12:32 +02:00
|
|
|
print_normal(
|
2015-05-04 01:21:05 +02:00
|
|
|
const v3_t * normal,
|
2015-05-05 03:23:45 +02:00
|
|
|
int show_model
|
2015-05-03 21:12:32 +02:00
|
|
|
)
|
|
|
|
{
|
|
|
|
const float x = normal->p[0];
|
|
|
|
const float y = normal->p[1];
|
|
|
|
const float z = normal->p[2];
|
|
|
|
const double length = sqrt(x*x+y*y+z*z);
|
|
|
|
const double b = acos(z / length);
|
|
|
|
const double c = x == 0 ? sign(y)*90 : atan2(y,x);
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
if (!show_model)
|
2015-05-04 01:21:05 +02:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "rotate([0,%f,0])", -b*180/M_PI);
|
|
|
|
fprintf(output, "rotate([0,0,%f])", -c*180/M_PI);
|
2015-05-04 01:21:05 +02:00
|
|
|
} else {
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "rotate([%f,%f,%f])\n", 0.0, b * 180 / M_PI, c * 180 / M_PI);
|
2015-05-04 01:21:05 +02:00
|
|
|
}
|
2015-05-03 21:12:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
find_normal(
|
2015-02-15 22:57:37 +01:00
|
|
|
const stl_3d_t * const stl,
|
|
|
|
const stl_vertex_t * const v,
|
2015-05-05 03:23:45 +02:00
|
|
|
const float inset_distance,
|
2015-05-03 21:12:32 +02:00
|
|
|
v3_t * const avg
|
2015-02-15 22:57:37 +01:00
|
|
|
)
|
|
|
|
{
|
2015-05-03 21:12:32 +02:00
|
|
|
int * const face_used
|
|
|
|
= calloc(sizeof(*face_used), stl->num_face);
|
2015-02-15 22:57:37 +01:00
|
|
|
|
|
|
|
// generate all of the coplanar polygons at this vertex
|
2015-05-03 21:12:32 +02:00
|
|
|
const stl_vertex_t ** const vertex_list
|
|
|
|
= calloc(sizeof(**vertex_list), stl->num_vertex);
|
2015-02-15 22:57:37 +01:00
|
|
|
|
|
|
|
for (int j = 0 ; j < v->num_face; j++)
|
|
|
|
{
|
|
|
|
// generate the polygon face for this vertex
|
|
|
|
const stl_face_t * const f = v->face[j];
|
|
|
|
if (face_used[f - stl->face])
|
|
|
|
continue;
|
|
|
|
|
2015-05-03 23:14:20 +02:00
|
|
|
//ref.origin.p[0] = 0;
|
|
|
|
//ref.origin.p[1] = 0;
|
|
|
|
//ref.origin.p[2] = 0;
|
|
|
|
|
2015-02-15 22:57:37 +01:00
|
|
|
const int start_vertex = v->face_num[j];
|
|
|
|
const int vertex_count = stl_trace_face(
|
|
|
|
stl,
|
|
|
|
f,
|
|
|
|
vertex_list,
|
|
|
|
face_used,
|
|
|
|
start_vertex
|
|
|
|
);
|
|
|
|
|
2015-05-03 21:12:32 +02:00
|
|
|
// find this vertex in the vertex list
|
|
|
|
// and compute the vector that subdivides the
|
|
|
|
// two outbound edges
|
|
|
|
for (int k = 0 ; k < vertex_count ; k++)
|
|
|
|
{
|
|
|
|
if (vertex_list[k] != v)
|
|
|
|
continue;
|
2015-02-15 22:57:37 +01:00
|
|
|
|
2015-05-03 21:12:32 +02:00
|
|
|
v3_t p1 = vertex_list[(k+vertex_count-1) % vertex_count]->p;
|
|
|
|
v3_t p2 = vertex_list[k % vertex_count]->p;
|
2015-05-03 23:14:20 +02:00
|
|
|
v3_t p3 = vertex_list[(k+1) % vertex_count]->p;
|
|
|
|
|
|
|
|
refframe_t ref;
|
|
|
|
refframe_init(
|
|
|
|
&ref,
|
|
|
|
p2,
|
|
|
|
p3,
|
|
|
|
p1
|
|
|
|
);
|
|
|
|
|
|
|
|
double x, y;
|
2015-05-05 03:23:45 +02:00
|
|
|
refframe_inset(&ref, inset_distance, &x, &y, p1, p2, p3);
|
2015-05-03 23:14:20 +02:00
|
|
|
|
|
|
|
v3_t hole = refframe_project(&ref, (v3_t){{x,y,0}});
|
|
|
|
//hole = refframe_project(&ref, (v3_t){{10,0,0}});
|
|
|
|
//hole.p[0] = 10*ref.x.p[0]; // + ref.origin.p[0];
|
|
|
|
//hole.p[1] = 10*ref.x.p[1]; // + ref.origin.p[1];
|
|
|
|
//hole.p[2] = 10*ref.x.p[2]; // + ref.origin.p[2];
|
|
|
|
fprintf(stderr, "**** %p [%f,%f]=>%f,%f,%f\n", v, x, y,
|
|
|
|
hole.p[0],
|
|
|
|
hole.p[1],
|
|
|
|
hole.p[2]
|
|
|
|
);
|
2015-02-22 23:32:06 +01:00
|
|
|
|
2015-05-03 23:46:51 +02:00
|
|
|
//*avg = v3_add(*avg, ref.z);
|
|
|
|
*avg = v3_add(*avg, v3_norm(v3_sub(ref.origin, hole)));
|
|
|
|
//*avg = v3_add(*avg, (v3_sub(hole, ref.origin)));
|
2015-05-03 21:12:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
free(face_used);
|
|
|
|
free(vertex_list);
|
|
|
|
}
|
2015-02-15 22:57:37 +01:00
|
|
|
|
2015-03-07 23:45:25 +01:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
static struct option long_options[] =
|
|
|
|
{
|
|
|
|
{ "verbose", no_argument, 0, 'v' },
|
|
|
|
{ "model", no_argument, 0, 'm' },
|
|
|
|
{ "inset", required_argument, 0, 'i' },
|
|
|
|
{ "radius", required_argument, 0, 'r' },
|
|
|
|
{ "input", required_argument, 0, 'I' },
|
|
|
|
{ "output", required_argument, 0, 'O' },
|
|
|
|
{ 0, 0, 0, 0 },
|
|
|
|
};
|
2015-02-15 22:57:37 +01:00
|
|
|
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
static void
|
|
|
|
usage(
|
|
|
|
FILE * const out
|
|
|
|
)
|
|
|
|
{
|
|
|
|
fprintf(out,
|
|
|
|
"Usage: corners [options] -I stl-binary.stl > corners.scad\n"
|
|
|
|
"Options:\n"
|
|
|
|
" -v | --verbose Enable verbosity\n"
|
|
|
|
" -i | --inset N Inset mm\n"
|
|
|
|
" -r | --radius N Hole radius mm\n"
|
|
|
|
" -I | --input file Read binary STL from file\n"
|
|
|
|
" -O | --output file Write SVG to file\n"
|
|
|
|
" -m | --model Generate a 3D model instead of corners\n"
|
|
|
|
"\n"
|
|
|
|
);
|
2015-02-15 22:57:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
|
2015-02-14 23:32:58 +01:00
|
|
|
int
|
2015-05-03 19:59:18 +02:00
|
|
|
main(
|
|
|
|
int argc,
|
|
|
|
char ** argv
|
|
|
|
)
|
2015-02-14 23:32:58 +01:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
double inset_distance = 5;
|
|
|
|
double hole_radius = 1.15;
|
|
|
|
const char * input_file = NULL;
|
|
|
|
const char * output_file = NULL;
|
|
|
|
int show_model = 0;
|
|
|
|
int option_index = 0;
|
|
|
|
|
|
|
|
while (1)
|
2015-05-03 19:59:18 +02:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
const int c = getopt_long(
|
|
|
|
argc,
|
|
|
|
argv,
|
|
|
|
"vmI:r:i:O:",
|
|
|
|
long_options,
|
|
|
|
&option_index
|
|
|
|
);
|
|
|
|
if (c == -1)
|
|
|
|
break;
|
|
|
|
switch(c)
|
|
|
|
{
|
|
|
|
case 'm': show_model = 1; break;
|
|
|
|
case 'v': verbose++; break;
|
|
|
|
case 'i': inset_distance = atof(optarg); break;
|
|
|
|
case 'r': hole_radius = atof(optarg); break;
|
|
|
|
case 'I': input_file = optarg; break;
|
|
|
|
case 'O': output_file = optarg; break;
|
|
|
|
case 'h': case '?':
|
|
|
|
usage(stdout);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
usage(stderr);
|
|
|
|
return -1;
|
|
|
|
}
|
2015-05-03 19:59:18 +02:00
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
int input_fd;
|
|
|
|
if (!input_file)
|
2015-05-03 19:59:18 +02:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(stderr, "Input STL must be specified\n");
|
2015-05-03 19:59:18 +02:00
|
|
|
return -1;
|
2015-05-05 03:23:45 +02:00
|
|
|
} else {
|
|
|
|
input_fd = open(input_file, O_RDONLY);
|
|
|
|
if (input_fd < 0)
|
|
|
|
{
|
|
|
|
perror(input_file);
|
|
|
|
return -1;
|
|
|
|
}
|
2015-05-03 19:59:18 +02:00
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
if (!output_file)
|
|
|
|
{
|
|
|
|
output_file = "stdout";
|
|
|
|
output = stdout;
|
|
|
|
} else {
|
|
|
|
output = fopen(output_file, "w");
|
|
|
|
if (!output)
|
|
|
|
{
|
|
|
|
perror(output_file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stl_3d_t * const stl = stl_3d_parse(input_fd);
|
2015-02-14 23:59:08 +01:00
|
|
|
if (!stl)
|
2015-05-05 03:23:45 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: Unable to parse STL\n", input_file);
|
2015-02-14 23:32:58 +01:00
|
|
|
return EXIT_FAILURE;
|
2015-05-05 03:23:45 +02:00
|
|
|
}
|
|
|
|
close(input_fd);
|
|
|
|
|
2015-05-03 19:59:18 +02:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
if (verbose)
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: %d faces, %d vertex\n",
|
|
|
|
input_file,
|
|
|
|
stl->num_face,
|
|
|
|
stl->num_vertex
|
|
|
|
);
|
|
|
|
|
|
|
|
fprintf(output, "module model() {\n"
|
2015-05-03 19:59:18 +02:00
|
|
|
"render() difference() {\n"
|
|
|
|
"import(\"%s\");\n",
|
2015-05-05 03:23:45 +02:00
|
|
|
input_file
|
2015-05-03 19:59:18 +02:00
|
|
|
);
|
|
|
|
//printf("%%model();\n");
|
|
|
|
|
|
|
|
int * const face_used
|
|
|
|
= calloc(sizeof(*face_used), stl->num_face);
|
|
|
|
const stl_vertex_t ** const vertex_list
|
|
|
|
= calloc(sizeof(*vertex_list), stl->num_vertex);
|
|
|
|
|
2015-05-03 21:12:32 +02:00
|
|
|
// for face, generate the set of coplanar points that go with it
|
|
|
|
// and "drill" holes in the model for those corners.
|
2015-05-03 19:59:18 +02:00
|
|
|
for (int i = 0 ; i < stl->num_face ; i++)
|
|
|
|
{
|
|
|
|
if (face_used[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const stl_face_t * const f = &stl->face[i];
|
|
|
|
const int vertex_count = stl_trace_face(
|
|
|
|
stl,
|
|
|
|
f,
|
|
|
|
vertex_list,
|
|
|
|
face_used,
|
|
|
|
0
|
|
|
|
);
|
2015-02-15 20:55:43 +01:00
|
|
|
|
2015-05-03 19:59:18 +02:00
|
|
|
refframe_t ref;
|
|
|
|
refframe_init(
|
|
|
|
&ref,
|
|
|
|
f->vertex[0]->p,
|
|
|
|
f->vertex[1]->p,
|
|
|
|
f->vertex[2]->p
|
|
|
|
);
|
|
|
|
|
|
|
|
// replace the origin with the actual origin
|
|
|
|
//ref.origin.p[0] = 0;
|
|
|
|
//ref.origin.p[1] = 0;
|
|
|
|
//ref.origin.p[2] = 0;
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "translate([%f,%f,%f])",
|
2015-05-03 19:59:18 +02:00
|
|
|
f->vertex[0]->p.p[0],
|
|
|
|
f->vertex[0]->p.p[1],
|
|
|
|
f->vertex[0]->p.p[2]
|
|
|
|
);
|
|
|
|
|
|
|
|
print_multmatrix(&ref, 0);
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "{\n");
|
2015-05-03 19:59:18 +02:00
|
|
|
|
|
|
|
// generate a bolt hole for each non-copolanar corner
|
|
|
|
for (int j = 0 ; j < vertex_count ; j++)
|
|
|
|
{
|
|
|
|
double x, y;
|
|
|
|
refframe_inset(
|
|
|
|
&ref,
|
2015-05-05 03:23:45 +02:00
|
|
|
inset_distance,
|
2015-05-03 19:59:18 +02:00
|
|
|
&x,
|
|
|
|
&y,
|
|
|
|
vertex_list[(j+0) % vertex_count]->p,
|
|
|
|
vertex_list[(j+1) % vertex_count]->p,
|
|
|
|
vertex_list[(j+2) % vertex_count]->p
|
|
|
|
);
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "translate([%f,%f,0]) cylinder(r=%f, h=%f, center=true);\n",
|
2015-05-03 19:59:18 +02:00
|
|
|
x,
|
|
|
|
y,
|
2015-05-05 03:23:45 +02:00
|
|
|
hole_radius,
|
2015-05-03 19:59:18 +02:00
|
|
|
10.0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "}\n");
|
2015-05-03 19:59:18 +02:00
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "}\n}\n");
|
2015-05-03 19:59:18 +02:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
if (show_model)
|
|
|
|
fprintf(output, "model();\n");
|
2015-05-03 21:12:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
// For each vertex, extract a small region around the corner
|
2015-05-03 23:46:51 +02:00
|
|
|
const int div = sqrt(stl->num_vertex);
|
|
|
|
const double spacing = 32;
|
|
|
|
|
2015-02-22 23:32:06 +01:00
|
|
|
for(int i = 0 ; i < stl->num_vertex ; i++)
|
2015-02-14 23:59:08 +01:00
|
|
|
{
|
|
|
|
const stl_vertex_t * const v = &stl->vertex[i];
|
2015-02-15 01:53:31 +01:00
|
|
|
const v3_t origin = v->p;
|
2015-02-14 23:32:58 +01:00
|
|
|
|
2015-05-03 23:14:20 +02:00
|
|
|
v3_t avg = {{ 0, 0, 0}};
|
2015-05-05 03:23:45 +02:00
|
|
|
find_normal(stl, v, inset_distance, &avg);
|
2015-02-22 23:32:06 +01:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
if (!show_model)
|
2015-05-04 01:21:05 +02:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "translate([%f,%f,20])", (i/div)*spacing, (i%div)*spacing);
|
|
|
|
fprintf(output, "render() intersection()");
|
2015-05-04 01:21:05 +02:00
|
|
|
}
|
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "{\n");
|
2015-02-15 20:55:43 +01:00
|
|
|
|
2015-05-03 23:46:51 +02:00
|
|
|
//printf("%%\n");
|
2015-05-05 03:23:45 +02:00
|
|
|
if (!show_model)
|
2015-05-04 01:21:05 +02:00
|
|
|
{
|
2015-05-05 03:23:45 +02:00
|
|
|
print_normal(&avg, show_model);
|
|
|
|
fprintf(output, "translate([%f,%f,%f])",
|
2015-05-04 01:21:05 +02:00
|
|
|
-origin.p[0], -origin.p[1], -origin.p[2]);
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "model();\n");
|
|
|
|
fprintf(output, "translate([0,0,-20]) cylinder(r=15,h=20);\n");
|
2015-05-04 01:21:05 +02:00
|
|
|
} else {
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "translate([%f,%f,%f])",
|
2015-05-04 01:21:05 +02:00
|
|
|
origin.p[0], origin.p[1], origin.p[2]);
|
2015-05-05 03:23:45 +02:00
|
|
|
print_normal(&avg, show_model);
|
|
|
|
fprintf(output, "%%translate([0,0,-20]) cylinder(r=15,h=20);\n");
|
2015-05-04 01:21:05 +02:00
|
|
|
}
|
2015-03-07 19:20:12 +01:00
|
|
|
|
2015-05-03 23:46:51 +02:00
|
|
|
//avg = v3_norm(avg);
|
2015-03-07 19:20:12 +01:00
|
|
|
|
2015-05-05 03:23:45 +02:00
|
|
|
fprintf(output, "}\n");
|
2015-02-14 23:32:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|