solidify/face.c |
1 | | /* |
2 | | * face.c - Data structure and handling of one face of a part |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 by Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <stdio.h> |
16 | | #include <unistd.h> |
17 | | #include <string.h> |
18 | | #include <math.h> |
19 | | #include <fcntl.h> |
20 | | #include <sys/wait.h> |
21 | | |
22 | | #include "util.h" |
23 | | #include "array.h" |
24 | | #include "histo.h" |
25 | | #include "face.h" |
26 | | |
27 | | |
28 | | #define CACHE_DIR ".cache" |
29 | | #define DEFAULT_STEP 1 /* 1 mm */ |
30 | | #define MIN_STEP 0.005 /* 5 um */ |
31 | | |
32 | | |
33 | | struct coord { |
34 | | float x, y, z; |
35 | | }; |
36 | | |
37 | | |
38 | | static struct coord *load_file(const char *name) |
39 | | { |
40 | | FILE *file; |
41 | | struct coord *v, *n; |
42 | | int s; |
43 | | |
44 | | if (!strcmp(name, "-")) { |
45 | | file = stdin; |
46 | | } else { |
47 | | int len; |
48 | | |
49 | | len = strlen(name); |
50 | | if (len > 4 && !strcmp(name+len-4, ".bz2")) { |
51 | | char tmp[1000]; /* @@@ enough */ |
52 | | |
53 | | sprintf(tmp, "bzcat \"%s\"", name); |
54 | | file = popen(tmp, "r"); |
55 | | if (!file) { |
56 | | perror(tmp); |
57 | | exit(1); |
58 | | } |
59 | | } else { |
60 | | file = fopen(name, "r"); |
61 | | if (!file) { |
62 | | perror(name); |
63 | | exit(1); |
64 | | } |
65 | | } |
66 | | } |
67 | | |
68 | | v = n = alloc_type(struct coord); |
69 | | s = 1; |
70 | | |
71 | | while (fscanf(file, "%f,%f,%f\r\n", &n->x, &n->y, &n->z) == 3) { |
72 | | n++; |
73 | | if (n-v == s) { |
74 | | struct coord *tmp; |
75 | | |
76 | | s += s; |
77 | | tmp = realloc(v, sizeof(struct coord)*s); |
78 | | if (!tmp) { |
79 | | perror("realloc"); |
80 | | exit(1); |
81 | | } |
82 | | n = n-v+tmp; |
83 | | v = tmp; |
84 | | } |
85 | | } |
86 | | if (file != stdin) |
87 | | (void) fclose(file); |
88 | | |
89 | | n->x = n->y = n->z = 0; |
90 | | return v; |
91 | | } |
92 | | |
93 | | |
94 | | static void adjust_step(double *step, double delta) |
95 | | { |
96 | | double n = round(delta/MIN_STEP); |
97 | | double s = n*MIN_STEP; |
98 | | |
99 | | if (n && s < *step) |
100 | | *step = s; |
101 | | } |
102 | | |
103 | | |
104 | | static struct face *read_file(const char *name) |
105 | | { |
106 | | struct coord *v, *p; |
107 | | struct face *f; |
108 | | struct histo *h; |
109 | | int xi, yi, zi; |
110 | | |
111 | | v = load_file(name); |
112 | | |
113 | | f = alloc_type(struct face); |
114 | | f->a = new_array(); |
115 | | |
116 | | /* |
117 | | * Hack: the MDX-15 measures bumps along the x axis with 25 um |
118 | | * resolution, so we just ignore the x resultion we find and use the |
119 | | * y resolution instead. |
120 | | */ |
121 | | f->x_step = f->y_step =f->z_step = DEFAULT_STEP; |
122 | | for (p = v; p[1].x || p[1].y || p[1].z; p++) { |
123 | | adjust_step(&f->y_step, fabs(p[0].y-p[1].y)); |
124 | | adjust_step(&f->z_step, fabs(p[0].z-p[1].z)); |
125 | | } |
126 | | f->x_step = f->y_step; |
127 | | |
128 | | for (p = v; p->x || p->y || p->z; p++) { |
129 | | xi = round(p->x/f->x_step); |
130 | | yi = round(p->y/f->y_step); |
131 | | zi = round(p->z/f->z_step); |
132 | | set(f->a, xi, yi, zi); |
133 | | } |
134 | | |
135 | | free(v); |
136 | | |
137 | | f->sx = f->a->max_x-f->a->min_x+1; |
138 | | f->sy = f->a->max_y-f->a->min_y+1; |
139 | | |
140 | | f->cx = (f->a->min_x+f->a->max_x)/2; |
141 | | f->cy = (f->a->min_y+f->a->max_y)/2; |
142 | | |
143 | | h = make_histo(f->a); |
144 | | f->z_ref = f->a->min_z+median(h); |
145 | | free_histo(h); |
146 | | f->fx = f->fy = 0; |
147 | | |
148 | | f->m.a[0][0] = f->m.a[1][1] = 1; |
149 | | f->m.a[0][1] = f->m.a[1][0] = 0; |
150 | | f->m.b[0] = f->m.b[1] = 0; |
151 | | |
152 | | fprintf(stderr, "%g %g %g\n", f->x_step, f->y_step, f->z_step); |
153 | | fprintf(stderr, "%d-%d / %d-%d / %d-%d\n", |
154 | | f->a->min_x, f->a->max_x, f->a->min_y, f->a->max_y, |
155 | | f->a->min_z, f->a->max_z); |
156 | | |
157 | | return f; |
158 | | } |
159 | | |
160 | | |
161 | | struct face *read_face(const char *name) |
162 | | { |
163 | | const char *p; |
164 | | int cwd; |
165 | | struct face *face; |
166 | | |
167 | | if (strncmp(name, "http:", 5) && strncmp(name, "https:", 6)) |
168 | | return read_file(name); |
169 | | p = strrchr(name, '/'); |
170 | | if (!p || !p[1]) { |
171 | | fprintf(stderr, "malformed URL: \"%s\"\n", name); |
172 | | exit(1); |
173 | | } |
174 | | cwd = open(".", O_RDONLY); |
175 | | if (cwd < 0) { |
176 | | perror("."); |
177 | | exit(1); |
178 | | } |
179 | | if (chdir(CACHE_DIR) < 0) { |
180 | | perror(CACHE_DIR); |
181 | | exit(1); |
182 | | } |
183 | | if (access(p+1, R_OK) < 0) { |
184 | | char tmp[1000]; /* @@@ enough */ |
185 | | int res; |
186 | | |
187 | | sprintf(tmp, "wget '%s'", name); |
188 | | res = system(tmp); |
189 | | if (res < 0) { |
190 | | perror("system"); |
191 | | exit(1); |
192 | | } |
193 | | if (!WIFEXITED(res) || WEXITSTATUS(res)) { |
194 | | fprintf(stderr, "%s: status %d\n", tmp, res); |
195 | | exit(1); |
196 | | } |
197 | | } |
198 | | face = read_file(p+1); |
199 | | if (fchdir(cwd) < 0) { |
200 | | perror("fchdir"); |
201 | | exit(1); |
202 | | } |
203 | | return face; |
204 | | } |
solidify/level.c |
1 | | /* |
2 | | * level.c - Interactively align a nearly horizontal plane with a face |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 by Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <stdio.h> |
16 | | #include <math.h> |
17 | | #include <gtk/gtk.h> |
18 | | |
19 | | #include "util.h" |
20 | | #include "array.h" |
21 | | #include "face.h" |
22 | | #include "style.h" |
23 | | #include "level.h" |
24 | | |
25 | | |
26 | | #define NEAR 1 |
27 | | |
28 | | |
29 | | static int has_osd; |
30 | | |
31 | | |
32 | | static double r_center(const struct face *f) |
33 | | { |
34 | | return hypot(f->sx, f->sy)/LEVEL_CENTER_DIV; |
35 | | } |
36 | | |
37 | | |
38 | | static void draw_map(GtkWidget *widget, struct face *f) |
39 | | { |
40 | | int x, y, z; |
41 | | double z0; |
42 | | guchar *rgbbuf, *p; |
43 | | |
44 | | rgbbuf = p = calloc(f->sx*f->sy, 3); |
45 | | if (!rgbbuf) { |
46 | | perror("calloc"); |
47 | | exit(1); |
48 | | } |
49 | | for (y = f->sy-1; y >= 0; y--) |
50 | | for (x = 0; x != f->sx ; x++) { |
51 | | z = get(f->a, x+f->a->min_x, y+f->a->min_y); |
52 | | if (z == UNDEF) { |
53 | | p += 3; |
54 | | continue; |
55 | | } |
56 | | z0 = face_z0(f, x, y); |
57 | | if (fabs(z-z0) < NEAR) { |
58 | | *p++ = 255*fabs(z-z0); |
59 | | *p++ = 255*fabs(z-z0); |
60 | | *p++ = 255; |
61 | | continue; |
62 | | } |
63 | | if (z < z0) { |
64 | | z = z > z0-2*NEAR ? 255*(z-z0)/NEAR : |
65 | | 255.0*(z-z0)/(z0-f->a->min_z); |
66 | | *p++ = 255; |
67 | | *p++ = z; |
68 | | *p++ = z; |
69 | | } else { |
70 | | z = z < z0+2*NEAR ? 255*(z0-z)/NEAR : |
71 | | 255.0*(z0-z)/(f->a->max_z-z0); |
72 | | *p++ = z; |
73 | | *p++ = 255; |
74 | | *p++ = z; |
75 | | } |
76 | | } |
77 | | gdk_draw_rgb_image(widget->window, |
78 | | widget->style->fg_gc[GTK_STATE_NORMAL], |
79 | | 0, 0, f->sx, f->sy, GDK_RGB_DITHER_MAX, rgbbuf, f->sx*3); |
80 | | free(rgbbuf); |
81 | | } |
82 | | |
83 | | |
84 | | static void draw_image(GtkWidget *widget, struct face *f, int osd) |
85 | | { |
86 | | draw_map(widget, f); |
87 | | has_osd = osd; |
88 | | if (osd) |
89 | | draw_circle(widget->window, gc_osd, |
90 | | f->sx/2, f->sy/2, r_center(f)); |
91 | | } |
92 | | |
93 | | |
94 | | static void scroll_z(GtkWidget *darea, struct face *f, int up, int osd) |
95 | | { |
96 | | if (up) { |
97 | | if (f->z_ref < f->a->max_z) |
98 | | f->z_ref++; |
99 | | } else { |
100 | | if (f->z_ref > f->a->min_z) |
101 | | f->z_ref--; |
102 | | } |
103 | | draw_image(darea, f, osd); |
104 | | } |
105 | | |
106 | | |
107 | | static void scroll_xy(GtkWidget *darea, struct face *f, int dx, int dy, int up, |
108 | | int osd) |
109 | | { |
110 | | double d; |
111 | | |
112 | | d = (double) (up ? 1 : -1)/(dx*dx+dy*dy)/2.0; |
113 | | f->fx += d*dx; |
114 | | f->fy += d*dy; |
115 | | draw_image(darea, f, osd); |
116 | | } |
117 | | |
118 | | |
119 | | static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, |
120 | | gpointer data) |
121 | | { |
122 | | GtkWidget *darea = gtk_bin_get_child(GTK_BIN(widget)); |
123 | | struct face *f = data; |
124 | | int dx = event->x-f->sx/2; |
125 | | int dy = event->y-f->sy/2; |
126 | | double r = hypot(dx, dy); |
127 | | double rc = r_center(f); |
128 | | int center = r < rc; |
129 | | int osd = fabs(r-rc) < OSD_PROXIMITY; |
130 | | |
131 | | switch (event->direction) { |
132 | | case GDK_SCROLL_UP: |
133 | | if (center) |
134 | | scroll_z(darea, f, 0, osd); |
135 | | else |
136 | | scroll_xy(darea, f, dx, dy, 1, osd); |
137 | | break; |
138 | | case GDK_SCROLL_DOWN: |
139 | | if (center) |
140 | | scroll_z(darea, f, 1, osd); |
141 | | else |
142 | | scroll_xy(darea, f, dx, dy, 0, osd); |
143 | | break; |
144 | | default: |
145 | | /* ignore */; |
146 | | } |
147 | | return TRUE; |
148 | | } |
149 | | |
150 | | |
151 | | static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, |
152 | | gpointer user_data) |
153 | | { |
154 | | draw_image(widget, user_data, has_osd); |
155 | | return TRUE; |
156 | | } |
157 | | |
158 | | |
159 | | static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, |
160 | | gpointer data) |
161 | | { |
162 | | struct face *f = data; |
163 | | int dx = event->x-f->sx/2; |
164 | | int dy = event->y-f->sy/2; |
165 | | double r = hypot(dx, dy); |
166 | | double rc = r_center(f); |
167 | | int osd = fabs(r-rc) < OSD_PROXIMITY; |
168 | | |
169 | | if (osd != has_osd) |
170 | | draw_image(widget, f, osd); |
171 | | return FALSE; |
172 | | } |
173 | | |
174 | | |
175 | | void level(GtkWidget *canvas, struct face *f) |
176 | | { |
177 | | GtkWidget *evbox, *darea; |
178 | | |
179 | | evbox = gtk_event_box_new(); |
180 | | darea = gtk_drawing_area_new(); |
181 | | |
182 | | gtk_widget_set_events(darea, |
183 | | GDK_EXPOSE | GDK_KEY_PRESS_MASK | |
184 | | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | |
185 | | GDK_SCROLL | |
186 | | GDK_POINTER_MOTION_MASK); |
187 | | |
188 | | gtk_widget_set_size_request(darea, f->sx, f->sy); |
189 | | gtk_container_add(GTK_CONTAINER(canvas), evbox); |
190 | | gtk_container_add(GTK_CONTAINER(evbox), darea); |
191 | | |
192 | | draw_image(darea, f, 0); |
193 | | |
194 | | g_signal_connect(G_OBJECT(evbox), "scroll-event", |
195 | | G_CALLBACK(scroll_event), f); |
196 | | g_signal_connect(G_OBJECT(darea), "expose-event", |
197 | | G_CALLBACK(expose_event), f); |
198 | | g_signal_connect(G_OBJECT(darea), "motion-notify-event", |
199 | | G_CALLBACK(motion_notify_event), f); |
200 | | } |
solidify/overlap.c |
1 | | /* |
2 | | * overlap.c - Overlap two parallel faces |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 by Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <stdio.h> |
16 | | #include <math.h> |
17 | | #include <limits.h> |
18 | | #include <gtk/gtk.h> |
19 | | |
20 | | #include "util.h" |
21 | | #include "face.h" |
22 | | #include "solid.h" |
23 | | #include "style.h" |
24 | | #include "overlap.h" |
25 | | |
26 | | |
27 | | #define UNDEF_F HUGE_VAL |
28 | | |
29 | | |
30 | | static int has_osd; |
31 | | |
32 | | |
33 | | static int sx(const struct solid *s) |
34 | | { |
35 | | return (s->a->sx > s->b->sx ? s->a->sx : s->b->sx)+2*OVERLAP_BORDER; |
36 | | } |
37 | | |
38 | | |
39 | | static int sy(const struct solid *s) |
40 | | { |
41 | | return (s->a->sy > s->b->sy ? s->a->sy : s->b->sy)+2*OVERLAP_BORDER; |
42 | | } |
43 | | |
44 | | |
45 | | static double r_center(const struct solid *s) |
46 | | { |
47 | | return hypot(sx(s), sy(s))/OVERLAP_CENTER_DIV; |
48 | | } |
49 | | |
50 | | |
51 | | static double ramp(int z0, double w0, int z1, double w1) |
52 | | { |
53 | | if (z0 != UNDEF && z1 != UNDEF) |
54 | | return z0*w0+z1*w1; |
55 | | if (z0 == UNDEF && z0 == UNDEF) |
56 | | return UNDEF_F; |
57 | | if (z0 == UNDEF && w0 < w1) |
58 | | return z1; |
59 | | if (z1 == UNDEF && w0 > w1) |
60 | | return z0; |
61 | | return UNDEF_F; |
62 | | } |
63 | | |
64 | | |
65 | | static double zmix(struct face *f, double x, double y) |
66 | | { |
67 | | int xa, xb, ya, yb; |
68 | | double zx0, zx1; |
69 | | |
70 | | xa = floor(x); |
71 | | xb = xa+1; |
72 | | ya = floor(y); |
73 | | yb = ya+1; |
74 | | |
75 | | zx0 = ramp( |
76 | | get_bounded(f->a, xa, ya), yb-y, |
77 | | get_bounded(f->a, xa, yb), y-ya); |
78 | | zx1 = ramp( |
79 | | get_bounded(f->a, xb, ya), yb-y, |
80 | | get_bounded(f->a, xb, yb), y-ya); |
81 | | |
82 | | return ramp(zx0, xb-x, zx1, x-xa); |
83 | | } |
84 | | |
85 | | |
86 | | /* |
87 | | * Coordinate transformations, on the example of the x coordinate: |
88 | | * |
89 | | * - the x coordinate runs from 0 to sx(s)-1 |
90 | | * - since we work relative to the screen center, this becomes x-sx(s)/2 |
91 | | * This is what we perform the coordinate transform on. |
92 | | * - our model runs from min_x to max_x. Its center is at cx. |
93 | | */ |
94 | | |
95 | | static void point(const struct solid *s, int x, int y, guchar *p, |
96 | | const struct matrix *ma, const struct matrix *mb) |
97 | | { |
98 | | double za, zb, z; |
99 | | double xaf, xbf, yaf, ybf; |
100 | | |
101 | | matrix_map(x, y, ma, &xaf, &yaf); |
102 | | matrix_map(x, y, mb, &xbf, &ybf); |
103 | | |
104 | | za = zmix(s->a, xaf, yaf); |
105 | | zb = zmix(s->b, xbf, ybf); |
106 | | |
107 | | if (za == UNDEF_F && zb == UNDEF_F) |
108 | | return; |
109 | | |
110 | | if (za == UNDEF_F) { |
111 | | z = 128.0*(zb-s->b->a->min_z)/(s->b->a->max_z-s->b->a->min_z); |
112 | | if (z < 0) |
113 | | z = 0; |
114 | | if (z > 255) |
115 | | z = 255; |
116 | | p[0] = 255; |
117 | | p[1] = z; |
118 | | p[2] = z; |
119 | | return; |
120 | | } |
121 | | if (zb == UNDEF_F) { |
122 | | z = 128.0*(za-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z); |
123 | | if (z < 0) |
124 | | z = 0; |
125 | | if (z > 255) |
126 | | z = 255; |
127 | | p[0] = z; |
128 | | p[1] = 255; |
129 | | p[2] = z; |
130 | | return; |
131 | | } |
132 | | |
133 | | z = za; |
134 | | za -= face_z0(s->a, xaf, yaf); |
135 | | zb -= face_z0(s->b, xbf, ybf); |
136 | | |
137 | | if (za+zb < -s->dist) { |
138 | | p[0] = 0; |
139 | | p[1] = 0; |
140 | | p[2] = 255; |
141 | | return; |
142 | | } |
143 | | |
144 | | z = 256.0*(z-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z); |
145 | | if (z < 0) |
146 | | z = 0; |
147 | | if (z > 255) |
148 | | z = 255; |
149 | | p[0] = z; |
150 | | p[1] = z; |
151 | | p[2] = z; |
152 | | } |
153 | | |
154 | | |
155 | | static void merge_matrix(struct matrix *m, const struct solid *s, |
156 | | const struct face *f) |
157 | | { |
158 | | double tm[2][2], tm2[2][2]; |
159 | | double tv[2]; |
160 | | double f_x, f_y; |
161 | | |
162 | | /* |
163 | | * Finally, we convert to model matrix coordinates. |
164 | | * |
165 | | * v' = v+c |
166 | | */ |
167 | | |
168 | | m->b[0] += f->cx; |
169 | | m->b[1] += f->cy; |
170 | | |
171 | | /* |
172 | | * Apply shrinkage caused by rotation out of z0. |
173 | | * We need to divide by x = cos a. We have f = tan a. |
174 | | * With sin^2 a + cos^2 a = 1, we get |
175 | | * |
176 | | * f = sqrt(1-cos^2 a)/cos a |
177 | | * = sqrt(1-x^2)/x |
178 | | * f^2 = 1/x^2-1 |
179 | | * 1/(f^2+1) = x^2 |
180 | | * cos a = sqrt(1/(f^2+1)) |
181 | | */ |
182 | | |
183 | | f_x = sqrt(f->fx*f->fx+1); |
184 | | f_y = sqrt(f->fy*f->fy+1); |
185 | | |
186 | | m->a[0][0] *= f_x; |
187 | | m->a[0][1] *= f_x; |
188 | | m->b[0] *= f_x; |
189 | | m->a[1][0] *= f_y; |
190 | | m->a[1][1] *= f_y; |
191 | | m->b[1] *= f_y; |
192 | | |
193 | | /* |
194 | | * The transformation matrix f->m describes a transformation of |
195 | | * (centered) model coordinates. We therefore have to reverse it: |
196 | | * |
197 | | * v = v'A+b |
198 | | * v-b = v'A |
199 | | * (v-b)A^-1 = v' |
200 | | * vA^-1-bA^-1 = v' |
201 | | */ |
202 | | |
203 | | matrix_invert(f->m.a, tm); |
204 | | matrix_multv(f->m.b, tm, tv); |
205 | | tv[0] = -tv[0]; |
206 | | tv[1] = -tv[1]; |
207 | | |
208 | | /* |
209 | | * Merge with the transformations we have so far: |
210 | | * |
211 | | * v' = vA1+b1 the transformation we have so far |
212 | | * v'' = v'A2+b2 the transformation we apply |
213 | | * |
214 | | * v'' = (vA1+b1)A2+b2 |
215 | | * v'' = vA1A2+b1A2+b2 |
216 | | */ |
217 | | |
218 | | /* |
219 | | * So far, the theory. To make it really work, we have to calculate |
220 | | * v'' = vA1A2+b1+b2 |
221 | | * duh ?!? |
222 | | */ |
223 | | |
224 | | matrix_mult(m->a, tm, tm2); /* A1A2 */ |
225 | | matrix_copy(tm2, m->a); |
226 | | // matrix_multv(m->b, tm, m->b); /* b1A2 */ |
227 | | m->b[0] += tv[0]; /* b2 */ |
228 | | m->b[1] += tv[1]; |
229 | | |
230 | | /* |
231 | | * Our input is a screen coordinate, its origin is in a corner so we |
232 | | * first have to make it center-based: |
233 | | * |
234 | | * v' = (v-s/2)A+b |
235 | | * v' = vA+(b-s/2*A) |
236 | | */ |
237 | | |
238 | | tv[0] = sx(s)/2; |
239 | | tv[1] = sy(s)/2; |
240 | | matrix_multv(tv, m->a, tv); |
241 | | m->b[0] -= tv[0]; |
242 | | m->b[1] -= tv[1]; |
243 | | } |
244 | | |
245 | | |
246 | | static void draw_map(GtkWidget *widget, struct solid *s) |
247 | | { |
248 | | guchar *rgbbuf, *p; |
249 | | int x, y; |
250 | | struct matrix ma = { |
251 | | .a = { { 1, 0 }, { 0, 1 } }, |
252 | | .b = { 0, 0 }, |
253 | | }; |
254 | | struct matrix mb = { |
255 | | .a = { { -1, 0 }, { 0, 1 } }, |
256 | | .b = { 0, 0 }, |
257 | | }; |
258 | | |
259 | | rgbbuf = p = calloc(sx(s)*sy(s), 3); |
260 | | if (!rgbbuf) { |
261 | | perror("calloc"); |
262 | | exit(1); |
263 | | } |
264 | | |
265 | | merge_matrix(&ma, s, s->a); |
266 | | merge_matrix(&mb, s, s->b); |
267 | | |
268 | | for (y = sy(s)-1; y >= 0; y--) |
269 | | for (x = 0; x != sx(s) ; x++) { |
270 | | point(s, x, y, p, &ma, &mb); |
271 | | p += 3; |
272 | | } |
273 | | gdk_draw_rgb_image(widget->window, |
274 | | widget->style->fg_gc[GTK_STATE_NORMAL], |
275 | | 0, 0, sx(s), sy(s), GDK_RGB_DITHER_MAX, rgbbuf, sx(s)*3); |
276 | | free(rgbbuf); |
277 | | } |
278 | | |
279 | | |
280 | | static void draw_image(GtkWidget *widget, struct solid *s, int osd) |
281 | | { |
282 | | int cx = sx(s)/2; |
283 | | int cy = sy(s)/2; |
284 | | int p; |
285 | | |
286 | | draw_map(widget, s); |
287 | | has_osd = osd; |
288 | | if (!osd) |
289 | | return; |
290 | | draw_circle(widget->window, gc_osd, cx, cy, r_center(s)); |
291 | | p = r_center(s)/sqrt(2); |
292 | | gdk_draw_line(widget->window, gc_osd, cx-p, cy-p, cx+p, cy+p); |
293 | | gdk_draw_line(widget->window, gc_osd, cx-p, cy+p, cx+p, cy-p); |
294 | | } |
295 | | |
296 | | |
297 | | /* |
298 | | * Rotate such that a point at distance "r" moves one unit. Rotate |
299 | | * counter-clockwise for r > 1, clockwise for r < 0. |
300 | | */ |
301 | | |
302 | | static void rotate(struct matrix *m, double r) |
303 | | { |
304 | | struct matrix t; |
305 | | double s, c; |
306 | | |
307 | | s = 1/r; |
308 | | c = sqrt(1-s*s); |
309 | | t.a[0][0] = m->a[0][0]*c-m->a[1][0]*s; |
310 | | t.a[0][1] = m->a[0][1]*c-m->a[1][1]*s; |
311 | | t.a[1][0] = m->a[1][0]*c+m->a[0][0]*s; |
312 | | t.a[1][1] = m->a[1][1]*c+m->a[0][1]*s; |
313 | | t.b[0] = m->b[0]*c-m->b[1]*s; |
314 | | t.b[1] = m->b[0]*s+m->b[1]*c; |
315 | | *m = t; |
316 | | } |
317 | | |
318 | | |
319 | | static void do_shift(struct matrix *m, double dx, double dy) |
320 | | { |
321 | | m->b[0] += dx; |
322 | | m->b[1] += dy; |
323 | | } |
324 | | |
325 | | |
326 | | static void shift(struct matrix *m, int dx, int dy, double dist) |
327 | | { |
328 | | /* |
329 | | * Wheeling "up" in each quadrant shifts in the respective direction, |
330 | | * wheeling "down" in the opposite direction. |
331 | | * |
332 | | * No rule without exception: we treat the "down" quadrant like the |
333 | | * "up" quadrant, because it would be extremely counter-intuitive to |
334 | | * wheel "up" to move "down". |
335 | | */ |
336 | | |
337 | | if (dx > 0 && dy < dx && dy > -dx) |
338 | | do_shift(m, dist, 0); |
339 | | if (dx < 0 && dy < -dx && dy > dx) |
340 | | do_shift(m, -dist, 0); |
341 | | if (dy > 0 && dx < dy && dx > -dy) |
342 | | do_shift(m, 0, dist); |
343 | | if (dy < 0 && dx < -dy && dx > dy) |
344 | | do_shift(m, 0, dist); /* exception ! */ |
345 | | } |
346 | | |
347 | | |
348 | | static int osd_proximity(const struct solid *s, int dx, int dy) |
349 | | { |
350 | | double r = hypot(dx, dy); |
351 | | double rc = r_center(s); |
352 | | |
353 | | if (fabs(r-rc) < OSD_PROXIMITY) |
354 | | return 1; |
355 | | if (r > rc) |
356 | | return 0; |
357 | | if (abs(abs(dx)-abs(dy)) < OSD_PROXIMITY) |
358 | | return 1; |
359 | | return 0; |
360 | | } |
361 | | |
362 | | |
363 | | static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, |
364 | | gpointer data) |
365 | | { |
366 | | GtkWidget *darea = gtk_bin_get_child(GTK_BIN(widget)); |
367 | | struct solid *s = data; |
368 | | int dx = event->x-sx(s)/2; |
369 | | int dy = event->y-sy(s)/2; |
370 | | double r = hypot(dx, dy); |
371 | | double rc = r_center(s); |
372 | | double rs, rot, dist; |
373 | | int center = r < rc; |
374 | | int osd = osd_proximity(s, dx, dy); |
375 | | |
376 | | if (r < 1) |
377 | | return TRUE; |
378 | | |
379 | | /* |
380 | | * rot goes exponentially from SLOWEST_ROT*rs to FASTEST_ROT for |
381 | | * r = rc to rs, with rs being half the canvas diagonal. |
382 | | * |
383 | | * The values are picked such that we achieve sufficient precision at |
384 | | * a reasonably large distance from the circle (for accidently entering |
385 | | * the circle would change the mode) but can also spin quickly, e.g., |
386 | | * when a 180 degrees rotation is needed. |
387 | | * |
388 | | * First, normalize to 0 ... 1 |
389 | | * Then, we start at exp(0) and end at |
390 | | * exp(ln(SLOWEST_ROT*rs/FASTEST_ROT))) |
391 | | */ |
392 | | rs = hypot(sx(s), sy(s))/2; |
393 | | rot = (r-rc)/(rs-rc); |
394 | | rot = SLOWEST_ROT*rs*exp(-rot*log(SLOWEST_ROT*rs/FASTEST_ROT)); |
395 | | |
396 | | /* |
397 | | * dist stays at 1 from 0...rc/DIST_STEPS, then linearly goes up to |
398 | | * DIST_STEPS from rc/DIST_STEPS...rc |
399 | | */ |
400 | | dist = r/rc*DIST_STEPS; |
401 | | if (dist < 0) |
402 | | dist = 1; |
403 | | |
404 | | switch (event->direction) { |
405 | | case GDK_SCROLL_UP: |
406 | | if (center) |
407 | | shift(&s->a->m, dx, dy, dist); |
408 | | else |
409 | | rotate(&s->a->m, dx > 0 ? rot : -rot); |
410 | | draw_image(darea, s, osd); |
411 | | break; |
412 | | case GDK_SCROLL_DOWN: |
413 | | if (center) |
414 | | shift(&s->a->m, dx, dy, -dist); |
415 | | else |
416 | | rotate(&s->a->m, dx > 0 ? -rot : rot); |
417 | | draw_image(darea, s, osd); |
418 | | break; |
419 | | default: |
420 | | /* ignore */; |
421 | | } |
422 | | return TRUE; |
423 | | } |
424 | | |
425 | | |
426 | | static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, |
427 | | gpointer user_data) |
428 | | { |
429 | | draw_image(widget, user_data, has_osd); |
430 | | return TRUE; |
431 | | } |
432 | | |
433 | | |
434 | | static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, |
435 | | gpointer data) |
436 | | { |
437 | | struct solid *s = data; |
438 | | int dx = event->x-sx(s)/2; |
439 | | int dy = event->y-sy(s)/2; |
440 | | int osd = osd_proximity(s, dx, dy); |
441 | | |
442 | | if (osd != has_osd) |
443 | | draw_image(widget, s, osd); |
444 | | return FALSE; |
445 | | } |
446 | | |
447 | | |
448 | | void overlap(GtkWidget *canvas, struct solid *s) |
449 | | { |
450 | | GtkWidget *evbox, *darea; |
451 | | |
452 | | evbox = gtk_event_box_new(); |
453 | | darea = gtk_drawing_area_new(); |
454 | | |
455 | | gtk_widget_set_events(darea, |
456 | | GDK_EXPOSE | GDK_KEY_PRESS_MASK | |
457 | | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | |
458 | | GDK_SCROLL | |
459 | | GDK_POINTER_MOTION_MASK); |
460 | | |
461 | | gtk_widget_set_size_request(darea, sx(s), sy(s)); |
462 | | gtk_container_add(GTK_CONTAINER(canvas), evbox); |
463 | | gtk_container_add(GTK_CONTAINER(evbox), darea); |
464 | | |
465 | | draw_image(darea, s, 0); |
466 | | |
467 | | g_signal_connect(G_OBJECT(evbox), "scroll-event", |
468 | | G_CALLBACK(scroll_event), s); |
469 | | g_signal_connect(G_OBJECT(darea), "expose-event", |
470 | | G_CALLBACK(expose_event), s); |
471 | | g_signal_connect(G_OBJECT(darea), "motion-notify-event", |
472 | | G_CALLBACK(motion_notify_event), s); |
473 | | |
474 | | if (0) { |
475 | | int i; |
476 | | long t0 = time(NULL); |
477 | | gtk_widget_show_all(canvas); |
478 | | for (i = 0; i != 1000; i++) { |
479 | | rotate(&s->a->m, 100); |
480 | | draw_image(darea, s, 0); |
481 | | while (gtk_events_pending()) |
482 | | gtk_main_iteration(); |
483 | | } |
484 | | fprintf(stderr, "%lu\n", time(NULL)-t0); |
485 | | } |
486 | | |
487 | | } |
solidify/povray.c |
1 | | /* |
2 | | * povray.c - Generate POV-Ray output |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 by Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdint.h> |
15 | | #include <stdlib.h> |
16 | | #include <stdio.h> |
17 | | |
18 | | #include "face.h" |
19 | | #include "solid.h" |
20 | | |
21 | | |
22 | | static void height_field(const char *name, const struct face *f, |
23 | | const struct matrix *m) |
24 | | { |
25 | | FILE *file; |
26 | | int x, y; |
27 | | int z; |
28 | | uint16_t g; |
29 | | uint8_t v[2]; |
30 | | |
31 | | file = fopen(name, "w"); |
32 | | if (!file) { |
33 | | perror(name); |
34 | | exit(1); |
35 | | } |
36 | | fprintf(file, "P5\n%d %d\n65535\n", f->sx, f->sy); |
37 | | for (y = 0; y != f->sy; y++) |
38 | | for (x = 0; x != f->sx; x++) { |
39 | | z = get(f->a, x+f->a->min_x, y+f->a->min_y); |
40 | | g = z == UNDEF ? 0 : |
41 | | 65535*(z-f->a->min_z)/(f->a->max_z-f->a->min_z); |
42 | | v[0] = g >> 8; |
43 | | v[1] = g; |
44 | | fwrite(v, 2, 1, file); |
45 | | } |
46 | | fclose(file); |
47 | | } |
48 | | |
49 | | |
50 | | static void sanitize(const char *s, char *res) |
51 | | { |
52 | | while (*s) { |
53 | | if ((*s >= 'A' && *s <= 'Z') || |
54 | | (*s >= 'a' && *s <= 'z') || |
55 | | (*s >= '0' && *s <= '9') || *s == '_') |
56 | | *res = *s; |
57 | | else |
58 | | *res = '_'; |
59 | | res++; |
60 | | s++; |
61 | | } |
62 | | *res = 0; |
63 | | } |
64 | | |
65 | | |
66 | | /* |
67 | | * For now, we put the part such that its x/y/z center is at the origin. |
68 | | * Later, we will also have to consider the changes the user made and the |
69 | | * relation with the opposing face. |
70 | | */ |
71 | | |
72 | | static void povray_face(const struct face *f, const char *side, |
73 | | const char *prefix, int flip, double dist) |
74 | | { |
75 | | int sz = f->a->max_z-f->a->min_z; |
76 | | |
77 | | /* |
78 | | * 1/65535 = 0.000015..., so we set the water level a bit lower, e.g., |
79 | | * to 0.0001 |
80 | | */ |
81 | | printf( |
82 | | "\theight_field {\n" |
83 | | "\t pgm \"%s-%s.pgm\"\n" |
84 | | "\t water_level 0.00001\n" |
85 | | "\t smooth\n" |
86 | | "\t scale <%g, %g, %g>\n" |
87 | | "\t rotate <90, 0, 0>\n" |
88 | | "\t translate <%g, %g, %g>\n" |
89 | | "\t translate <0, 0, %g>\n" |
90 | | "%s" /* flip bottom face */ |
91 | | "\t translate <0, 0, %g>\n" |
92 | | "\t}\n", prefix, side, |
93 | | f->sx*f->x_step, sz*f->z_step, f->sy*f->y_step, |
94 | | -f->sx*f->x_step/2, f->sy*f->y_step/2, f->a->min_z*f->z_step, |
95 | | -f->z_ref*f->z_step, |
96 | | flip ? "\t rotate <180, 0, 0>\n" : "", |
97 | | dist*f->z_step); |
98 | | } |
99 | | |
100 | | |
101 | | void povray(const char *name, const struct solid *s) |
102 | | { |
103 | | struct matrix m; |
104 | | char tmp[1000]; /* @@@ enough */ |
105 | | |
106 | | m.a[0][0] = m.a[1][1] = 1; |
107 | | m.a[0][1] = m.a[1][0] = 0; |
108 | | m.b[0] = m.b[1] = 0; |
109 | | |
110 | | sprintf(tmp, "%s-top.pgm", name); |
111 | | height_field(tmp, s->a, &m); |
112 | | sprintf(tmp, "%s-bot.pgm", name); |
113 | | height_field(tmp, s->b, &m); |
114 | | |
115 | | sanitize(name, tmp); |
116 | | printf("#declare Part_%s =\n intersection {\n", tmp); |
117 | | povray_face(s->a, "top", name, 0, s->dist/2); |
118 | | povray_face(s->b, "bot", name, 1, -s->dist/2); |
119 | | printf(" }\n"); |
120 | | } |
solidify/project.c |
1 | | /* |
2 | | * project.c - Load and save solidify project descriptions |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 by Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <stdio.h> |
16 | | #include <string.h> |
17 | | #include <errno.h> |
18 | | #include <math.h> |
19 | | |
20 | | #include "util.h" |
21 | | #include "face.h" |
22 | | #include "solid.h" |
23 | | #include "project.h" |
24 | | |
25 | | |
26 | | static struct project *make_project(const char *name, |
27 | | const char *top, const char *bottom, double dist_mm) |
28 | | { |
29 | | struct project *prj; |
30 | | |
31 | | prj = alloc_type(struct project); |
32 | | prj->name = stralloc(name); |
33 | | prj->top = stralloc(top); |
34 | | prj->bottom = stralloc(bottom); |
35 | | prj->s.a = read_face(top); |
36 | | prj->s.b = read_face(bottom); |
37 | | |
38 | | if (prj->s.a->x_step != prj->s.a->x_step || |
39 | | prj->s.a->y_step != prj->s.a->y_step || |
40 | | prj->s.a->z_step != prj->s.a->z_step) { |
41 | | fprintf(stderr, "both faces must have the same resolution\n"); |
42 | | exit(1); |
43 | | } |
44 | | |
45 | | prj->s.dist = dist_mm/prj->s.a->z_step; |
46 | | return prj; |
47 | | } |
48 | | |
49 | | |
50 | | struct project *new_project(const char *name, |
51 | | const char *top, const char *bottom, double dist_mm) |
52 | | { |
53 | | FILE *file; |
54 | | |
55 | | file = fopen(name, "r"); |
56 | | if (file) { |
57 | | fprintf(stderr, "%s: already exists\n", name); |
58 | | exit(1); |
59 | | } |
60 | | return make_project(name, top, bottom, dist_mm); |
61 | | } |
62 | | |
63 | | |
64 | | static void read_face_data(FILE *file, struct face *f) |
65 | | { |
66 | | float v; |
67 | | |
68 | | if (fscanf(file, "%f", &v) != 1) |
69 | | return; |
70 | | f->z_ref = v/f->z_step; |
71 | | |
72 | | if (fscanf(file, "%f", &v) != 1) |
73 | | return; |
74 | | f->fx = tan(v/180*M_PI); |
75 | | |
76 | | if (fscanf(file, "%f", &v) != 1) |
77 | | return; |
78 | | f->fy = tan(v/180*M_PI); |
79 | | |
80 | | if (fscanf(file, "%f", &v) != 1) |
81 | | return; |
82 | | v = v/180*M_PI; |
83 | | f->m.a[0][0] = cos(v); |
84 | | f->m.a[0][1] = -sin(v); |
85 | | f->m.a[1][0] = sin(v); |
86 | | f->m.a[1][1] = cos(v); |
87 | | |
88 | | if (fscanf(file, "%f", &v) != 1) |
89 | | return; |
90 | | f->m.b[0] = v/f->x_step; |
91 | | |
92 | | if (fscanf(file, "%f", &v) != 1) |
93 | | return; |
94 | | f->m.b[1] = v/f->y_step; |
95 | | } |
96 | | |
97 | | |
98 | | static void read_optional(FILE *file, struct project *prj) |
99 | | { |
100 | | float v; |
101 | | |
102 | | if (fscanf(file, "%f", &v) != 1) |
103 | | return; |
104 | | prj->s.dist = v/prj->s.a->z_step; |
105 | | |
106 | | read_face_data(file, prj->s.a); |
107 | | read_face_data(file, prj->s.b); |
108 | | } |
109 | | |
110 | | |
111 | | struct project *load_project(const char *name) |
112 | | { |
113 | | FILE *file; |
114 | | char top[1000], bottom[1000]; /* @@@ enough */ |
115 | | struct project *prj; |
116 | | |
117 | | file = fopen(name, "r"); |
118 | | if (!file) { |
119 | | perror(name); |
120 | | exit(1); |
121 | | } |
122 | | |
123 | | if (!fgets(top, sizeof(top), file)) { |
124 | | fprintf(stderr, "%s: can't read name of top face\n", name); |
125 | | exit(1); |
126 | | } |
127 | | if (strchr(top, '\n')) |
128 | | *strchr(top, '\n') = 0; |
129 | | |
130 | | if (!fgets(bottom, sizeof(bottom), file)) { |
131 | | fprintf(stderr, "%s: can't read name of bottom face\n", |
132 | | bottom); |
133 | | exit(1); |
134 | | } |
135 | | if (strchr(bottom, '\n')) |
136 | | *strchr(bottom, '\n') = 0; |
137 | | |
138 | | prj = make_project(name, top, bottom, 0); |
139 | | |
140 | | read_optional(file, prj); |
141 | | |
142 | | return prj; |
143 | | } |
144 | | |
145 | | |
146 | | static void save_face_data(FILE *file, const char *name, const struct face *f) |
147 | | { |
148 | | double a; |
149 | | |
150 | | a = asin(-f->m.a[0][1])/M_PI*180; |
151 | | if (f->m.a[0][0] < 0) |
152 | | a = 180-a; |
153 | | if (fprintf(file, "%g %g %g\n%g %g %g\n", |
154 | | f->z_ref*f->z_step, |
155 | | atan(f->fx)/M_PI*180, atan(f->fy)/M_PI*180, |
156 | | a, f->m.b[0]*f->x_step, f->m.b[1]*f->y_step) < 0) { |
157 | | perror(name); |
158 | | exit(1); |
159 | | } |
160 | | } |
161 | | |
162 | | |
163 | | void save_project(const struct project *prj) |
164 | | { |
165 | | char tmp[1000]; /* @@@ enough */ |
166 | | FILE *file; |
167 | | |
168 | | sprintf(tmp, "%s~", prj->name); |
169 | | file = fopen(tmp, "w"); |
170 | | if (!file) { |
171 | | perror(tmp); |
172 | | exit(1); |
173 | | } |
174 | | if (fprintf(file, |
175 | | "%s\n%s\n%g\n", prj->top, prj->bottom, |
176 | | prj->s.dist*prj->s.a->z_step) < 0) { |
177 | | perror(tmp); |
178 | | exit(1); |
179 | | } |
180 | | save_face_data(file, tmp, prj->s.a); |
181 | | save_face_data(file, tmp, prj->s.b); |
182 | | if (fclose(file) < 0) { |
183 | | perror(tmp); |
184 | | exit(1); |
185 | | } |
186 | | |
187 | | if (rename(tmp, prj->name) < 0) { |
188 | | fprintf(stderr, "rename %s to %s: %s\n", tmp, prj->name, |
189 | | strerror(errno)); |
190 | | exit(1); |
191 | | } |
192 | | } |
solidify/solidify.c |
1 | | /* |
2 | | * solidify.c - Merge two opposing faces of a part into a solid |
3 | | * |
4 | | * Written 2010 by Werner Almesberger |
5 | | * Copyright 2010 Werner Almesberger |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | */ |
12 | | |
13 | | |
14 | | #include <stdlib.h> |
15 | | #include <stdio.h> |
16 | | #include <unistd.h> |
17 | | #include <string.h> |
18 | | #include <locale.h> |
19 | | #include <gtk/gtk.h> |
20 | | |
21 | | #include "face.h" |
22 | | #include "solid.h" |
23 | | #include "project.h" |
24 | | #include "style.h" |
25 | | #include "level.h" |
26 | | #include "overlap.h" |
27 | | |
28 | | |
29 | | static struct project *prj; |
30 | | static const struct face *active; /* NULL if overlapping */ |
31 | | static GtkWidget *canvas; |
32 | | |
33 | | |
34 | | static void clicked(GtkButton *button, gpointer user_data) |
35 | | { |
36 | | struct face *face = user_data; |
37 | | |
38 | | if (active == face) |
39 | | return; |
40 | | |
41 | | gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(canvas))); |
42 | | |
43 | | if (face) |
44 | | level(canvas, face); |
45 | | else |
46 | | overlap(canvas, &prj->s); |
47 | | active = face; |
48 | | |
49 | | gtk_widget_show_all(canvas); |
50 | | } |
51 | | |
52 | | |
53 | | static GtkWidget *gui_buttons(void) |
54 | | { |
55 | | GtkWidget *vbox, *but; |
56 | | |
57 | | vbox = gtk_vbox_new(FALSE, 0); |
58 | | |
59 | | but = gtk_button_new_with_label("A"); |
60 | | gtk_box_pack_start(GTK_BOX(vbox), but, FALSE, FALSE, 0); |
61 | | g_signal_connect(G_OBJECT(but), "clicked", |
62 | | G_CALLBACK(clicked), prj->s.a); |
63 | | |
64 | | but = gtk_button_new_with_label("B"); |
65 | | gtk_box_pack_start(GTK_BOX(vbox), but, FALSE, FALSE, 0); |
66 | | g_signal_connect(G_OBJECT(but), "clicked", |
67 | | G_CALLBACK(clicked), prj->s.b); |
68 | | |
69 | | but = gtk_button_new_with_label("A+B"); |
70 | | gtk_box_pack_start(GTK_BOX(vbox), but, FALSE, FALSE, 0); |
71 | | g_signal_connect(G_OBJECT(but), "clicked", |
72 | | G_CALLBACK(clicked), NULL); |
73 | | |
74 | | return vbox; |
75 | | } |
76 | | |
77 | | |
78 | | static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, |
79 | | gpointer data) |
80 | | { |
81 | | if (event->keyval == 'q') |
82 | | gtk_main_quit(); |
83 | | return TRUE; |
84 | | } |
85 | | |
86 | | |
87 | | static void gui(void) |
88 | | { |
89 | | GtkWidget *root, *hbox, *buttons; |
90 | | |
91 | | root = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
92 | | gtk_window_set_position(GTK_WINDOW(root), GTK_WIN_POS_CENTER); |
93 | | |
94 | | hbox = gtk_hbox_new(FALSE, 0); |
95 | | gtk_container_add(GTK_CONTAINER(root), hbox); |
96 | | |
97 | | canvas = gtk_event_box_new(); |
98 | | gtk_box_pack_start(GTK_BOX(hbox), canvas, FALSE, FALSE, 0); |
99 | | |
100 | | /* initialize root->window */ |
101 | | gtk_widget_show_all(root); |
102 | | |
103 | | buttons = gui_buttons(); |
104 | | gtk_box_pack_start(GTK_BOX(hbox), buttons, FALSE, FALSE, 0); |
105 | | |
106 | | level(canvas, prj->s.a); |
107 | | active = prj->s.a; |
108 | | |
109 | | init_style(root->window); |
110 | | |
111 | | gtk_widget_show_all(root); |
112 | | |
113 | | g_signal_connect(G_OBJECT(root), "key-press-event", |
114 | | G_CALLBACK(key_press_event), NULL); |
115 | | g_signal_connect(G_OBJECT(root), "destroy", |
116 | | G_CALLBACK(gtk_main_quit), NULL); |
117 | | |
118 | | gtk_main(); |
119 | | } |
120 | | |
121 | | |
122 | | static void usage(const char *name) |
123 | | { |
124 | | fprintf(stderr, "usage: %s project [top bottom dist]\n", name); |
125 | | exit(1); |
126 | | } |
127 | | |
128 | | |
129 | | int main(int argc, char **argv) |
130 | | { |
131 | | double dist; |
132 | | |
133 | | gtk_init(&argc, &argv); |
134 | | setlocale(LC_ALL, "C"); /* damage control */ |
135 | | |
136 | | switch (argc) { |
137 | | case 2: |
138 | | prj = load_project(argv[1]); |
139 | | break; |
140 | | case 5: |
141 | | dist = atof(argv[4]); |
142 | | prj = new_project(argv[1], argv[2], argv[3], dist); |
143 | | break; |
144 | | default: |
145 | | usage(*argv); |
146 | | } |
147 | | |
148 | | gui(); |
149 | | |
150 | | save_project(prj); |
151 | | |
152 | | if (!isatty(1)) { |
153 | | const char *slash = strrchr(prj->name, '/'); |
154 | | char tmp[1000]; /* @@@ enough */ |
155 | | |
156 | | strcpy(tmp, slash ? slash+1 : prj->name); |
157 | | if (strchr(tmp, '.')) |
158 | | *strchr(tmp, '.') = 0; |
159 | | povray(tmp, &prj->s); |
160 | | } |
161 | | |
162 | | return 0; |
163 | | } |