#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <vector>
#include <map>
#define RASTER 100 // mil
#define MAX_ROUND (100/10) // allow 10% raster offset when aligning
#define MIN_OVERLAP (100/10) // require 10% overlap for merging
#define MIN(a, b) (((a)<(b))?(a):(b))
#define MAX(a, b) (((a)>(b))?(a):(b))
#define POW2(a) ((a)*(a))
#define LOG(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__);
typedef struct point_s {
unsigned x, y;
unsigned radius;
bool consumed;
bool operator==(const point_s& p) const {
return p.x == x && p.y == y;
}
bool operator!=(const point_s& p) const {
return p.x != x || p.y != y;
}
bool intersects(const point_s& p) const {
return (p.x >= x-radius-p.radius+MIN_OVERLAP && p.x <= x+radius+p.radius-MIN_OVERLAP && p.y >= y-radius-p.radius+MIN_OVERLAP && p.y <= y+radius+p.radius-MIN_OVERLAP);
}
unsigned sqdist(const point_s& p) const {
return POW2((x>p.x)? x-p.x: p.x-x) + POW2((y>p.y)? y-p.y: p.y-y);
}
unsigned dist(const point_s& p) const {
return (unsigned)round(sqrt(sqdist(p)));
}
} point_t;
typedef struct {
point_t a, b;
bool includes(const point_t& p) const {
if (a.x == b.x) {
if (p.x != a.x) return false;
if (a.y < b.y) return p.y >= a.y && p.y <= b.y;
return p.y >= b.y && p.y <= a.y;
} else {
assert(a.y == b.y);
if (p.y != a.y) return false;
if (a.x < b.x) return p.x >= a.x && p.x <= b.x;
return p.x >= b.x && p.x <= a.x;
}
}
} line_t;
static bool point_align(const point_t& raster, point_t& p, int& round_max) {
if (p.x < raster.x || p.y < raster.y) {
LOG("cannot align: %u/%u", p.x, p.y);
return false;
}
int off_x = ((int)p.x % RASTER) - (int)raster.x;
int off_y = ((int)p.y % RASTER) - (int)raster.y;
if (off_x || off_y) {
if (abs(off_x) > MAX_ROUND || abs(off_y) > MAX_ROUND) {
LOG("too much off to align: %u/%u", p.x, p.y);
return false;
}
p.x -= off_x;
p.y -= off_y;
if (abs(off_x) > abs(round_max)) round_max = off_x;
if (abs(off_y) > abs(round_max)) round_max = off_y;
}
return true;
}
static bool point2pad(point_t& p, std::vector<point_t>& pads) {
for (std::vector<point_t>::iterator it=pads.begin(); it!=pads.end(); ++it) {
if (p.intersects(*it)) {
p = *it;
it->consumed = true;
return true;
}
}
return false;
}
int main(int argc, char **argv) {
std::vector<point_t> pads;
std::vector<line_t> lines;
// parse input, check for unsupported features, and fill pads and lines
std::map<unsigned, unsigned> apertures;
point_t last_point = {};
bool last_point_set = false;
unsigned last_aperture = 0;
bool last_aperture_set = false;
static char line[256];
while (fgets(line, sizeof(line), stdin)) {
size_t len = strlen(line);
while (len && line[len-1] == '\n') {
line[--len] = '\0';
}
float a, b;
unsigned x, y;
unsigned cmd;
char s[sizeof(line)];
if (strncmp(line, "G04", 3) == 0 || strncmp(line, "%LN", 3) == 0 || strncmp(line, "M02*", 4) == 0 || strncmp(line, "D02*", 4) == 0 || strncmp(line, "G70*", 4) == 0 || strncmp(line, "G90*", 4) == 0) {
// comment, redundant, or unneeded
} else if (sscanf(line, "%%MO%[^*]*%%", s) == 1) {
if (strcmp(s, "IN") != 0) {
LOG("requiring inch units");
return 1;
}
} else if (sscanf(line, "%%AS%[^*]*%%", s) == 1) {
if (strcmp(s, "AXBY") != 0) {
LOG("no AX/BY axis");
return 1;
}
} else if (sscanf(line, "%%FS%[^X]X%uY%u*%%", s, &x, &y) == 3) {
if (strcmp(s, "LA") != 0) {
LOG("requiring absolute coordinates");
return 1;
}
if (x != 23 || y != 23) {
LOG("requiring 2.3 coordinate format");
return 1;
}
} else if (sscanf(line, "%%OFA%uB%u*%%", &x, &y) == 2) {
if (x != 0 || y != 0) {
LOG("coordinate offset given");
return 1;
}
} else if (sscanf(line, "%%SFA%fB%f*%%", &a, &b) == 2) {
if (a != 1.0 || b != 1.0) {
LOG("coordinate scale given");
return 1;
}
} else if (sscanf(line, "%%ADD%uC,%f*%%", &x, &a) == 2) {
if (apertures.find(x) != apertures.end()) {
LOG("aperture %u already defined", x);
return 1;
}
apertures.insert(std::pair<unsigned, unsigned>(x, a*1000));
} else if (sscanf(line, "%%ADD%uR,%fX%f*%%", &x, &a, &b) == 3) {
if (apertures.find(x) != apertures.end()) {
LOG("aperture %u already defined", x);
return 1;
}
if (a != b) {
LOG("irregular rectangular shape for aperture %u", x);
return 1;
}
apertures.insert(std::pair<unsigned, unsigned>(x, a*1000)); // sqrt(^2) instead?
} else if (sscanf(line, "G54D%u*", &x) == 1 || sscanf(line, "D%u*", &x) == 1) {
if (apertures.find(x) == apertures.end()) {
LOG("unknown aperture %u", x);
return 1;
}
last_aperture = apertures.find(x)->second;
last_aperture_set = true;
} else if (sscanf(line, "X%uY%uD%u*", &x, &y, &cmd) == 3) {
if (!last_aperture_set) {
LOG("no aperture set for: %s", line);
return 1;
} else if (cmd == 3) { // flash
pads.push_back((point_t){x, y, last_aperture/2, false});
last_point_set = false;
} else if (cmd == 2) { // goto while off
last_point.x = x;
last_point.y = y;
last_point.radius = last_aperture/2;
last_point.consumed = true;
last_point_set = true;
} else if (cmd == 1) { // goto while on
if (!last_point_set || last_aperture/2 != last_point.radius) {
LOG("unknown starting point for: %s", line);
return 1;
}
lines.push_back((line_t){(point_t){last_point.x, last_point.y, last_aperture/2, true}, (point_t){x, y, last_aperture/2, true}});
last_point.x = x;
last_point.y = y;
last_point_set = true;
} else {
LOG("unknown point suffix command: %s", line);
return 1;
}
} else {
LOG("ignoring unknown line: %s", line);
}
}
LOG("loaded input with %zu pads, %zu lines", pads.size(), lines.size());
// determine the global raster offset by majority of pads and line bends
point_t raster_start = {0, 0};
unsigned mods_num = 0;
unsigned mods_x[RASTER] = {};
unsigned mods_y[RASTER] = {};
for (std::vector<point_t>::const_iterator it=pads.begin(); it!=pads.end(); ++it) {
mods_num++;
mods_x[it->x % RASTER]++;
mods_y[it->y % RASTER]++;
}
for (std::vector<line_t>::const_iterator it1=lines.begin(); it1!=lines.end(); ++it1) {
for (std::vector<line_t>::const_iterator it2=lines.begin(); it2!=lines.end(); ++it2) {
if (it1-lines.begin() == it2-lines.begin()) continue;
if (it1->a == it2->a || it1->a == it2->b) {
mods_num++;
mods_x[it1->a.x % RASTER]++;
mods_y[it1->a.y % RASTER]++;
break;
} else if (it1->b == it2->a || it1->b == it2->b) {
mods_num++;
mods_x[it1->b.x % RASTER]++;
mods_y[it1->b.y % RASTER]++;
break;
}
}
}
for (unsigned i=0; i<RASTER; ++i) {
if (mods_x[i] > mods_x[raster_start.x]) {
raster_start.x = i;
}
if (mods_y[i] > mods_y[raster_start.y]) {
raster_start.y = i;
}
}
unsigned raster_x_ok = mods_x[((raster_start.x?:100)-1)%RASTER] + mods_x[raster_start.x] + mods_x[(raster_start.x+1)%RASTER];
unsigned raster_y_ok = mods_y[((raster_start.y?:100)-1)%RASTER] + mods_y[raster_start.y] + mods_y[(raster_start.y+1)%RASTER];
LOG("best %u mil raster offset: %ux%u mil", RASTER, raster_start.x, raster_start.y);
if (raster_x_ok < mods_num/2 || raster_y_ok < mods_num/2) {
LOG("too less matching offset samples (%u samples, %u/%u match)", mods_num, raster_x_ok, raster_y_ok);
return 1;
}
// align pads to global raster
int round_max = 0;
for (std::vector<point_t>::iterator it=pads.begin(); it!=pads.end(); ++it) { // we could go through mods, but we want to know the offending pad, if any
if (!point_align(raster_start, *it, round_max)) {
return 1;
}
}
LOG("aligned pads, max rounding error: %d mil", round_max);
// make sure pads are unique (can happen e.g. for different apertures)
unsigned dupes = 0;
for (std::vector<point_t>::iterator it1=pads.begin(); it1!=pads.end();) {
for (std::vector<point_t>::iterator it2=pads.begin(); it2!=pads.end();) {
if (it1-pads.begin() != it2-pads.begin()) {
if (*it1 == *it2) {
//LOG("pad %ux%u (%u,%u) given twice, ignoring", it1->x, it1->y, it1->radius, it2->radius);
dupes++;
if (it1->radius > it2->radius) {
it2 = pads.erase(it2);
continue;
} else {
it1 = pads.erase(it1);
goto cont2;
}
}
if (it1->intersects(*it2)) {
LOG("pad %ux%u intersects with %ux%u", it1->x, it1->y, it2->x, it2->y);
return 1;
}
}
++it2;
}
++it1;
cont2:;
}
LOG("ignored %u duplicate pads", dupes);
// align lines to global raster or snap them to the nearest pad
round_max = 0;
for (std::vector<line_t>::iterator it=lines.begin(); it!=lines.end(); ++it) {
if (!point2pad(it->a, pads) && !point_align(raster_start, it->a, round_max)) {
return 1;
}
if (!point2pad(it->b, pads) && !point_align(raster_start, it->b, round_max)) {
return 1;
}
if (it->a.x != it->b.x && it->a.y != it->b.y) {
LOG("skewed line: %ux%u -> %ux%u", it->a.x, it->a.y, it->b.x, it->b.y);
return 1;
}
if (it->a == it->b) {
LOG("zero length line: %ux%u", it->a.x, it->a.y); // maybe aperture intersected with the same pad for both points
return 1;
}
// let it start from the smaller point as convenience invariant
if (it->a.x > it->b.x || it->a.y > it->b.y) {
point_t tmp = it->a;
it->a = it->b;
it->b = tmp;
}
}
LOG("max. line rounding error: %d", round_max); // w/o pad connection
// compute result, i.e. borders for each line in raster/2
std::vector<line_t> result;
for (std::vector<line_t>::const_iterator it=lines.begin(); it!=lines.end(); ++it) {
line_t curr = *it;
// three corners for each point, acc. to general direction
static const unsigned N=1, S=2, W=4, E=8;
unsigned corner_a=N|S|W|E, corner_b=N|S|W|E;
if (curr.a.x < curr.b.x) { corner_a &= ~E; corner_b &= ~W; }
else if (curr.a.y < curr.b.y) { corner_a &= ~N; corner_b &= ~S; }
else assert(false);
// find overlappings/tees/crossings and delete corners accordingly
for (std::vector<line_t>::const_iterator ait=lines.begin(); ait!=lines.end(); ++ait) {
if (it-lines.begin() == ait-lines.begin()) continue;
line_t adj = *ait;
if (adj.a == curr.a || adj.b == curr.b) {
// ok
} else if (adj.a == curr.b || adj.b == curr.a) {
adj.a = ait->b;
adj.b = ait->a;
} else {
continue;
}
unsigned adir=0, bdir=0;
if (adj.a.x < adj.b.x) { adir = E; bdir = W; }
else if (adj.a.x > adj.b.x) { adir = W; bdir = E; }
else if (adj.a.y < adj.b.y) { adir = N; bdir = S; }
else if (adj.a.y > adj.b.y) { adir = S; bdir = N; }
else assert(false);
if (adj.a == curr.a) {
corner_a &= ~adir;
} else if (adj.b == curr.b) {
corner_b &= ~bdir;
} else assert(false);
}
// points needed for raster/2 result
point_t a_tl = {curr.a.x-(RASTER/2), curr.a.y+(RASTER/2), 1, false};
point_t a_tr = {curr.a.x+(RASTER/2), curr.a.y+(RASTER/2), 1, false};
point_t a_bl = {curr.a.x-(RASTER/2), curr.a.y-(RASTER/2), 1, false};
point_t a_br = {curr.a.x+(RASTER/2), curr.a.y-(RASTER/2), 1, false};
point_t b_tl = {curr.b.x-(RASTER/2), curr.b.y+(RASTER/2), 1, false};
point_t b_tr = {curr.b.x+(RASTER/2), curr.b.y+(RASTER/2), 1, false};
point_t b_bl = {curr.b.x-(RASTER/2), curr.b.y-(RASTER/2), 1, false};
point_t b_br = {curr.b.x+(RASTER/2), curr.b.y-(RASTER/2), 1, false};
// draw both side lines (assume there are no T junctions w/o a stop)
if (curr.a.x == curr.b.x) { // N/S
assert(curr.a.y < curr.b.y);
result.push_back((line_t){a_tl, b_bl});
result.push_back((line_t){a_tr, b_br});
} else { // W/E
assert(curr.a.x < curr.b.x);
result.push_back((line_t){a_tr, b_tl});
result.push_back((line_t){a_br, b_bl});
}
// and corners. note that everything will be coalesced afterwards
if (corner_a & N) result.push_back((line_t){a_tl, a_tr});
if (corner_a & S) result.push_back((line_t){a_bl, a_br});
if (corner_a & W) result.push_back((line_t){a_bl, a_tl});
if (corner_a & E) result.push_back((line_t){a_br, a_tr});
if (corner_b & N) result.push_back((line_t){b_tl, b_tr});
if (corner_b & S) result.push_back((line_t){b_bl, b_br});
if (corner_b & W) result.push_back((line_t){b_bl, b_tl});
if (corner_b & E) result.push_back((line_t){b_br, b_tr});
}
LOG("converted to %zu rectangular border lines", result.size());
// add unconsumed pads
for (std::vector<point_t>::iterator it=pads.begin(); it!=pads.end(); ++it) {
if (it->consumed) continue;
point_t tl = {it->x-(RASTER/2), it->y+(RASTER/2), 1, false};
point_t tr = {it->x+(RASTER/2), it->y+(RASTER/2), 1, false};
point_t bl = {it->x-(RASTER/2), it->y-(RASTER/2), 1, false};
point_t br = {it->x+(RASTER/2), it->y-(RASTER/2), 1, false};
result.push_back((line_t){tl, tr});
result.push_back((line_t){tr, br});
result.push_back((line_t){br, bl});
result.push_back((line_t){bl, tl});
}
LOG("added standalone pads, now %zu lines", result.size());
// postprocessing, sanity checks, add global borders
point_t min = {(unsigned)-1, (unsigned)-1};
point_t max = {0, 0};
for (std::vector<line_t>::iterator it=result.begin(); it!=result.end();) {
if (it->a == it->b) {
it = result.erase(it);
continue;
}
if ((it->a.x == it->b.x) == (it->a.y == it->b.y)) {
LOG("skewed line: %ux%u -> %ux%u", it->a.x, it->a.y, it->b.x, it->b.y);
return 1;
}
// let it start from the smaller point as convenience invariant, again
if (it->a.x > it->b.x || it->a.y > it->b.y) {
point_t tmp = it->a;
it->a = it->b;
it->b = tmp;
}
if (it->a.x < min.x) min.x = it->a.x;
if (it->a.y < min.y) min.y = it->a.y;
if (it->b.x > max.x) max.x = it->b.x;
if (it->b.y > max.y) max.y = it->b.y;
it++;
}
point_t tl = {min.x, max.y};
point_t br = {max.x, min.y};
result.push_back((line_t){min, tl});
result.push_back((line_t){tl, max});
result.push_back((line_t){max, br});
result.push_back((line_t){br, min});
// coalesce straight lines
again:;
for (std::vector<line_t>::iterator it1=result.begin(); it1!=result.end(); ++it1) {
for (std::vector<line_t>::iterator it2=result.begin(); it2!=result.end(); ++it2) {
if (it1-lines.begin() == it2-lines.begin()) continue;
if ((it1->a.x == it1->b.x) == (it2->a.x == it2->b.x)) { // same general direction
if (it1->includes(it2->a) || it1->includes(it2->b)) { // share a point, overlap, or inclusion
line_t tmp = *it1;
it1->a.x = MIN(MIN(tmp.a.x, tmp.b.x), MIN(it2->a.x, it2->b.x));
it1->a.y = MIN(MIN(tmp.a.y, tmp.b.y), MIN(it2->a.y, it2->b.y));
it1->b.x = MAX(MAX(tmp.a.x, tmp.b.x), MAX(it2->a.x, it2->b.x));
it1->b.y = MAX(MAX(tmp.a.y, tmp.b.y), MAX(it2->a.y, it2->b.y));
assert(it1->a != it1->b);
result.erase(it2);
goto again;
}
}
}
}
LOG("postprocessing: coalesced to %zu lines", result.size());
// print header
printf("%%ASAXBY*%%\n");
printf("%%FSLAX23Y23*%%\n");
printf("%%MOIN*%%\n");
printf("%%OFA0B0*%%\n");
printf("%%SFA1.0B1.0*%%\n");
printf("%%ADD10C,0.010000*%%\n");
printf("%%LNCOPPER0*%%\n");
// start at 0x0
printf("G54D10*\n");
printf("X0Y0D02*\n");
point_t curr = {0, 0};
// order acc. to consecutive corners or acc. to smallest noop drive
unsigned mill_len=0;
unsigned move_len=0;
unsigned moves = 0;
unsigned idle_moves = 0;
while (!result.empty()) {
std::vector<line_t>::iterator next;
unsigned next_dst = (unsigned)-1;
bool next_rev = false;
for (std::vector<line_t>::iterator nit=result.begin(); nit!=result.end(); ++nit) {
unsigned dst_a = curr.sqdist(nit->a);
unsigned dst_b = curr.sqdist(nit->b);
if (dst_a <= dst_b) {
if (dst_a < next_dst) {
next_dst = dst_a;
next = nit;
}
} else {
if (dst_b < next_dst) {
next_dst = dst_b;
next = nit;
next_rev = true;
}
}
}
moves++;
if (next_dst == 0) { // can go on to a or b while on
if (next->a == curr) {
mill_len += curr.dist(next->b);
printf("X%uY%uD01*\n", next->b.x, next->b.y);
assert(next->b != curr);
curr = next->b;
} else { // next->b == curr
mill_len += curr.dist(next->a);
printf("X%uY%uD01*\n", next->a.x, next->a.y);
assert(next->a != curr);
curr = next->a;
}
} else { // need to stop and draw new polyline
idle_moves++;
if (next_rev) {
move_len += curr.dist(next->b);
mill_len += next->b.dist(next->a);
printf("X%uY%uD02*\n", next->b.x, next->b.y);
printf("X%uY%uD01*\n", next->a.x, next->a.y);
assert(next->a != curr);
curr = next->a;
} else {
move_len += curr.dist(next->a);
mill_len += next->a.dist(next->b);
printf("X%uY%uD02*\n", next->a.x, next->a.y);
printf("X%uY%uD01*\n", next->b.x, next->b.y);
assert(next->b != curr);
curr = next->b;
}
}
result.erase(next);
}
LOG("drawing lines: %u busy movements (%u mil), %u idle movements (%u mil)", moves, mill_len, idle_moves, move_len);
// print footer & done
printf("M02*\n");
LOG("done: success");
return 0;
}