/* * COPYRIGHT * * pcb-rnd, interactive printed circuit board design * Copyright (C) 2017 Tibor 'Igor2' Palinkas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Contact: * Project page: http://repo.hu/projects/pcb-rnd * lead developer: http://repo.hu/projects/pcb-rnd/contact.html * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") */ #include "config.h" #include "thermal.h" #include #include #include "data.h" #include "draw.h" #include "undo.h" #include "conf_core.h" #include "obj_common.h" #include "obj_pstk.h" #include "obj_pstk_inlines.h" #include "obj_pinvia_therm.h" #include "polygon.h" #include "funchash_core.h" static const char core_thermal_cookie[] = "core/thermal.c"; rnd_cardinal_t pcb_themal_style_new2old(unsigned char t) { switch(t) { case 0: return 0; case PCB_THERMAL_ON | PCB_THERMAL_SHARP | PCB_THERMAL_DIAGONAL: return 1; case PCB_THERMAL_ON | PCB_THERMAL_SHARP: return 2; case PCB_THERMAL_ON | PCB_THERMAL_SOLID: return 3; case PCB_THERMAL_ON | PCB_THERMAL_ROUND | PCB_THERMAL_DIAGONAL: return 4; case PCB_THERMAL_ON | PCB_THERMAL_ROUND: return 5; } return 0; } unsigned char pcb_themal_style_old2new(rnd_cardinal_t t) { switch(t) { case 0: return 0; case 1: return PCB_THERMAL_ON | PCB_THERMAL_SHARP | PCB_THERMAL_DIAGONAL; case 2: return PCB_THERMAL_ON | PCB_THERMAL_SHARP; case 3: return PCB_THERMAL_ON | PCB_THERMAL_SOLID; case 4: return PCB_THERMAL_ON | PCB_THERMAL_ROUND | PCB_THERMAL_DIAGONAL; case 5: return PCB_THERMAL_ON | PCB_THERMAL_ROUND; } return 0; } pcb_thermal_t pcb_thermal_str2bits(const char *str) { /* shape */ if (strcmp(str, "noshape") == 0) return PCB_THERMAL_NOSHAPE; if (strcmp(str, "round") == 0) return PCB_THERMAL_ROUND; if (strcmp(str, "sharp") == 0) return PCB_THERMAL_SHARP; if (strcmp(str, "solid") == 0) return PCB_THERMAL_SOLID; /* orientation */ if (strcmp(str, "diag") == 0) return PCB_THERMAL_DIAGONAL; if (strcmp(str, "on") == 0) return PCB_THERMAL_ON; return 0; } const char *pcb_thermal_bits2str(pcb_thermal_t *bit) { if ((*bit) & PCB_THERMAL_ON) { *bit &= ~PCB_THERMAL_ON; return "on"; } if ((*bit) & PCB_THERMAL_DIAGONAL) { *bit &= ~PCB_THERMAL_DIAGONAL; return "diag"; } if ((*bit) & 3) { int shape = (*bit) & 3; *bit &= ~3; switch(shape) { case PCB_THERMAL_NOSHAPE: return "noshape"; case PCB_THERMAL_ROUND: return "round"; case PCB_THERMAL_SHARP: return "sharp"; case PCB_THERMAL_SOLID: return "solid"; default: return NULL; } } return NULL; } void pcb_thermal_bits2chars(char dst[3], pcb_thermal_t bits) { char *end = dst; if (!(bits & PCB_THERMAL_ON)) { *end = '\0'; return; } switch(bits & 3) { case PCB_THERMAL_NOSHAPE: *end++ = 'n'; goto skip_diag; case PCB_THERMAL_ROUND: *end++ = 'o'; break; case PCB_THERMAL_SHARP: *end++ = 'x'; break; case PCB_THERMAL_SOLID: *end++ = '@'; goto skip_diag; } if (bits & PCB_THERMAL_DIAGONAL) *end++ = 'd'; skip_diag:; *end++ = '\0'; } const char *pcb_thermal_bits2chars_const(pcb_thermal_t bits) { if (bits & PCB_THERMAL_ON) { switch(bits & 3) { case PCB_THERMAL_NOSHAPE: return "n"; case PCB_THERMAL_ROUND: return (bits & PCB_THERMAL_DIAGONAL) ? "od" : "o"; case PCB_THERMAL_SHARP: return (bits & PCB_THERMAL_DIAGONAL) ? "xd" : "o"; case PCB_THERMAL_SOLID: return "@"; } } return ""; } int pcb_thermal_chars2bits(pcb_thermal_t *dst, const char *src) { pcb_thermal_t res = 0; if ((src == NULL) || (src[0] == '\0')) { *dst = 0; return 0; } switch(src[0]) { case 'n': res |= PCB_THERMAL_NOSHAPE; goto skip_diag; case 'o': res |= PCB_THERMAL_ROUND; break; case 'x': res |= PCB_THERMAL_SHARP; break; case '@': res |= PCB_THERMAL_SOLID; goto skip_diag; default: return -1; } if (src[1] == 'd') res |= PCB_THERMAL_DIAGONAL; else if (src[1] != '\0') return -1; skip_diag:; *dst = res | PCB_THERMAL_ON; return 0; } /* generate a round-cap line polygon */ static rnd_polyarea_t *pa_line_at(double x1, double y1, double x2, double y2, rnd_coord_t clr, rnd_bool square) { pcb_line_t ltmp; if (square) ltmp.Flags = pcb_flag_make(PCB_FLAG_SQUARE); else ltmp.Flags = pcb_no_flags(); ltmp.Point1.X = rnd_round(x1); ltmp.Point1.Y = rnd_round(y1); ltmp.Point2.X = rnd_round(x2); ltmp.Point2.Y = rnd_round(y2); return pcb_poly_from_pcb_line(<mp, clr); } /* generate a round-cap arc polygon knowing the center and endpoints */ static rnd_polyarea_t *pa_arc_at(double cx, double cy, double r, double e1x, double e1y, double e2x, double e2y, rnd_coord_t clr, double max_span_angle) { double sa, ea, da; pcb_arc_t atmp; sa = atan2(-(e1y - cy), e1x - cx) * RND_RAD_TO_DEG + 180.0; ea = atan2(-(e2y - cy), e2x - cx) * RND_RAD_TO_DEG + 180.0; /* rnd_trace("sa=%f ea=%f diff=%f\n", sa, ea, ea-sa);*/ atmp.Flags = pcb_no_flags(); atmp.X = rnd_round(cx); atmp.Y = rnd_round(cy); da = ea-sa; if ((da < max_span_angle) && (da > -max_span_angle)) { atmp.StartAngle = sa; atmp.Delta = ea-sa; } else { if (ea < sa) { double tmp = ea; ea = sa; sa = tmp; } atmp.StartAngle = ea; atmp.Delta = 360-ea+sa; } atmp.Width = atmp.Height = r; return pcb_poly_from_pcb_arc(&atmp, clr); } rnd_polyarea_t *pcb_thermal_area_line(pcb_board_t *pcb, pcb_line_t *line, rnd_layer_id_t lid, pcb_poly_t *in_poly) { rnd_polyarea_t *pa, *pb, *pc; double dx, dy, len, vx, vy, nx, ny, clr, clrth, x1, y1, x2, y2, mx, my; rnd_coord_t th, lclr; if ((line->Point1.X == line->Point2.X) && (line->Point1.Y == line->Point2.Y)) { /* corner case zero-long line is a circle: do the same as for vias */ lclr = pcb_obj_clearance_o05(line, in_poly); /* PCB is use for the thermal scale here */ return ThermPoly_(pcb == NULL ? PCB : pcb, line->Point1.X, line->Point1.Y, line->Thickness, lclr * 2, pcb_themal_style_new2old(line->thermal & 3)); } x1 = line->Point1.X; y1 = line->Point1.Y; x2 = line->Point2.X; y2 = line->Point2.Y; mx = (x1+x2)/2.0; my = (y1+y2)/2.0; dx = x1 - x2; dy = y1 - y2; len = sqrt(dx*dx + dy*dy); vx = dx / len; vy = dy / len; nx = -vy; ny = vx; lclr = pcb_obj_clearance_o05(line, in_poly); clr = lclr; clrth = (lclr + line->Thickness) / 2; assert(line->thermal & PCB_THERMAL_ON); /* caller should have checked this */ switch(line->thermal & 3) { case PCB_THERMAL_NOSHAPE: case PCB_THERMAL_SOLID: return 0; case PCB_THERMAL_ROUND: if (PCB_FLAG_TEST(PCB_FLAG_SQUARE, line)) { if (line->thermal & PCB_THERMAL_DIAGONAL) { /* side clear lines */ pa = pa_line_at( x1 - clrth * nx + clrth * vx - clr*vx, y1 - clrth * ny + clrth * vy - clr*vy, x2 - clrth * nx - clrth * vx + clr*vx, y2 - clrth * ny - clrth * vy + clr*vy, clr, rnd_false); pb = pa_line_at( x1 + clrth * nx + clrth * vx - clr*vx, y1 + clrth * ny + clrth * vy - clr*vy, x2 + clrth * nx - clrth * vx + clr*vx, y2 + clrth * ny - clrth * vy + clr*vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* cross clear lines */ pa = pc; pc = NULL; pb = pa_line_at( x1 - clrth * nx + clrth * vx + clr*nx, y1 - clrth * ny + clrth * vy + clr*ny, x1 + clrth * nx + clrth * vx - clr*nx, y1 + clrth * ny + clrth * vy - clr*ny, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x2 - clrth * nx - clrth * vx + clr*nx, y2 - clrth * ny - clrth * vy + clr*ny, x2 + clrth * nx - clrth * vx - clr*nx, y2 + clrth * ny - clrth * vy - clr*ny, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); } else { /* side clear lines */ pa = pa_line_at( x1 - clrth * nx + clrth * vx, y1 - clrth * ny + clrth * vy, mx - clrth * nx + clr * vx, my - clrth * ny + clr * vy, clr, rnd_false); pb = pa_line_at( x1 + clrth * nx + clrth * vx, y1 + clrth * ny + clrth * vy, mx + clrth * nx + clr * vx, my + clrth * ny + clr * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x2 - clrth * nx - clrth * vx, y2 - clrth * ny - clrth * vy, mx - clrth * nx - clr * vx, my - clrth * ny - clr * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x2 + clrth * nx - clrth * vx, y2 + clrth * ny - clrth * vy, mx + clrth * nx - clr * vx, my + clrth * ny - clr * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* cross clear lines */ pa = pc; pc = NULL; pb = pa_line_at( x1 - clrth * nx + clrth * vx, y1 - clrth * ny + clrth * vy, x1 - clr * nx + clrth * vx, y1 - clr * ny + clrth * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x1 + clrth * nx + clrth * vx, y1 + clrth * ny + clrth * vy, x1 + clr * nx + clrth * vx, y1 + clr * ny + clrth * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x2 - clrth * nx - clrth * vx, y2 - clrth * ny - clrth * vy, x2 - clr * nx - clrth * vx, y2 - clr * ny - clrth * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( x2 + clrth * nx - clrth * vx, y2 + clrth * ny - clrth * vy, x2 + clr * nx - clrth * vx, y2 + clr * ny - clrth * vy, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); } } else { /* round cap line: arcs! */ if (line->thermal & PCB_THERMAL_DIAGONAL) { /* side clear lines */ pa = pa_line_at( x1 - clrth * nx - clr * vx * 0.75, y1 - clrth * ny - clr * vy * 0.75, x2 - clrth * nx + clr * vx * 0.75, y2 - clrth * ny + clr * vy * 0.75, clr, rnd_false); pb = pa_line_at( x1 + clrth * nx - clr * vx * 0.75, y1 + clrth * ny - clr * vy * 0.75, x2 + clrth * nx + clr * vx * 0.75, y2 + clrth * ny + clr * vy * 0.75, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* x1;y1 cap arc */ pa = pc; pc = NULL; pb = pa_arc_at(x1, y1, clrth, x1 - clrth * nx + clr * vx * 2.0, y1 - clrth * ny + clr * vy * 2.0, x1 + clrth * nx + clr * vx * 2.0, y1 + clrth * ny + clr * vy * 2.0, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* x2;y2 cap arc */ pa = pc; pc = NULL; pb = pa_arc_at(x2, y2, clrth, x2 - clrth * nx - clr * vx * 2.0, y2 - clrth * ny - clr * vy * 2.0, x2 + clrth * nx - clr * vx * 2.0, y2 + clrth * ny - clr * vy * 2.0, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); } else { /* non-diagonal */ /* split side lines */ pa = pa_line_at( x1 - clrth * nx - clr * vx * 0.00, y1 - clrth * ny - clr * vy * 0.00, mx - clrth * nx + clr * vx * 1.00, my - clrth * ny + clr * vy * 1.00, clr, rnd_false); pb = pa_line_at( x1 + clrth * nx - clr * vx * 0.00, y1 + clrth * ny - clr * vy * 0.00, mx + clrth * nx + clr * vx * 1.00, my + clrth * ny + clr * vy * 1.00, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( mx - clrth * nx - clr * vx * 1.00, my - clrth * ny - clr * vy * 1.00, x2 - clrth * nx + clr * vx * 0.00, y2 - clrth * ny + clr * vy * 0.00, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_line_at( mx + clrth * nx - clr * vx * 1.00, my + clrth * ny - clr * vy * 1.00, x2 + clrth * nx + clr * vx * 0.00, y2 + clrth * ny + clr * vy * 0.00, clr, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* split round cap, x1;y1 */ pa = pc; pc = NULL; pb = pa_arc_at(x1, y1, clrth, x1 - clrth * nx, y1 - clrth * ny, x1 + clrth * vx - clr * nx, y1 + clrth * vy - clr * ny, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_arc_at(x1, y1, clrth, x1 + clrth * nx, y1 + clrth * ny, x1 + clrth * vx + clr * nx, y1 + clrth * vy + clr * ny, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); /* split round cap, x2;y2 */ pa = pc; pc = NULL; pb = pa_arc_at(x2, y2, clrth, x2 - clrth * nx, y2 - clrth * ny, x2 - clrth * vx - clr * nx, y2 - clrth * vy - clr * ny, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); pa = pc; pc = NULL; pb = pa_arc_at(x2, y2, clrth, x2 + clrth * nx, y2 + clrth * ny, x2 - clrth * vx + clr * nx, y2 - clrth * vy + clr * ny, clr, 180.0); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_UNITE); } } return pc; case PCB_THERMAL_SHARP: pa = pcb_poly_from_pcb_line(line, line->Thickness + pcb_obj_clearance_p2(line, in_poly)); th = line->Thickness/2 < clr ? line->Thickness/2 : clr; clrth *= 2; if (line->thermal & PCB_THERMAL_DIAGONAL) { /* x1;y1 V-shape */ pb = pa_line_at(x1, y1, x1-nx*clrth+vx*clrth, y1-ny*clrth+vy*clrth, th, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); pa = pc; pc = NULL; pb = pa_line_at(x1, y1, x1+nx*clrth+vx*clrth, y1+ny*clrth+vy*clrth, th, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); /* x2;y2 V-shape */ pa = pc; pc = NULL; pb = pa_line_at(x2, y2, x2-nx*clrth-vx*clrth, y2-ny*clrth-vy*clrth, th, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); pa = pc; pc = NULL; pb = pa_line_at(x2, y2, x2+nx*clrth-vx*clrth, y2+ny*clrth-vy*clrth, th, rnd_false); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); } else { /* perpendicular */ pb = pa_line_at(mx-nx*clrth, my-ny*clrth, mx+nx*clrth, my+ny*clrth, th, rnd_true); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); /* straight */ pa = pc; pc = NULL; pb = pa_line_at(x1+vx*clrth, y1+vy*clrth, x2-vx*clrth, y2-vy*clrth, th, rnd_true); rnd_polyarea_boolean_free(pa, pb, &pc, RND_PBO_SUB); } return pc; } return NULL; } /* combine a base poly-area into the result area (pres) */ static void polytherm_base(rnd_polyarea_t **pres, const rnd_polyarea_t *src) { rnd_polyarea_t *p; if (*pres != NULL) { rnd_polyarea_boolean(src, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); *pres = p; } else rnd_polyarea_copy0(pres, src); } #define CONG_MAX 256 static int cong_map(char *cong, pcb_poly_it_t *it, rnd_coord_t clr) { int n, go, first = 1; rnd_coord_t cx, cy; double cl2 = (double)clr * (double)clr * 1.5; double px, py, x, y; for(n = 0, go = pcb_poly_vect_first(it, &cx, &cy); go; go = pcb_poly_vect_next(it, &cx, &cy), n++) { x = cx; y = cy; if (first) { /*rnd_trace("prev %mm;%mm\n", cx, cy);*/ pcb_poly_vect_peek_prev(it, &cx, &cy); /*rnd_trace(" %mm;%mm\n", cx, cy);*/ px = cx; py = cy; first = 0; } if (n >= CONG_MAX-1) { n++; break; } cong[n] = (rnd_distance2(x, y, px, py) < cl2); px = x; py = y; } return n; } /* combine a round clearance line set into pres; "it" is an iterator already initialized to a polyarea contour */ static void polytherm_round(rnd_polyarea_t **pres, pcb_poly_it_t *it, rnd_coord_t clr, rnd_bool is_diag, double tune) { rnd_polyarea_t *ptmp, *p; double fact = 0.5, fact_ortho=0.75; rnd_coord_t cx, cy; double px, py, x, y, dx, dy, vx, vy, nx, ny, mx, my, len; int n, go, first = 1; char cong[CONG_MAX]; clr -= 2; if (tune > 0) { fact *= tune; fact_ortho *= tune; } cong_map(cong, it, clr); /* iterate over the vectors of the contour */ for(n = 0, go = pcb_poly_vect_first(it, &cx, &cy); go; go = pcb_poly_vect_next(it, &cx, &cy), n++) { x = cx; y = cy; if (first) { pcb_poly_vect_peek_prev(it, &cx, &cy); px = cx; py = cy; first = 0; } /*rnd_trace("[%d] %d %mm;%mm\n", n, cong[n], (rnd_coord_t)x, (rnd_coord_t)y);*/ dx = x - px; dy = y - py; mx = (x+px)/2.0; my = (y+py)/2.0; len = sqrt(dx*dx + dy*dy); vx = dx / len; vy = dy / len; nx = -vy; ny = vx; /* skip points too dense */ if ((n >= CONG_MAX) || (cong[n])) { if (!is_diag) { /* have to draw a short clearance for the small segments */ ptmp = pa_line_at(x - nx * clr/2, y - ny * clr/2, px - nx * clr/2, py - ny * clr/2, clr+4, rnd_false); rnd_polyarea_boolean(ptmp, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; } px = x; py = y; continue; } /* cheat: clr-2+4 to guarantee some overlap with the poly cutout */ if (is_diag) { /* one line per edge, slightly shorter than the edge */ ptmp = pa_line_at(x - vx * clr * fact - nx * clr/2, y - vy * clr * fact - ny * clr/2, px + vx * clr *fact - nx * clr/2, py + vy * clr * fact - ny * clr/2, clr+4, rnd_false); rnd_polyarea_boolean(ptmp, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; } else { /* two half lines per edge */ ptmp = pa_line_at(x - nx * clr/2 , y - ny * clr/2, mx + vx * clr * fact_ortho - nx * clr/2, my + vy * clr * fact_ortho - ny * clr/2, clr+4, rnd_false); rnd_polyarea_boolean(ptmp, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; ptmp = pa_line_at(px - nx * clr/2, py - ny * clr/2, mx - vx * clr * fact_ortho - nx * clr/2, my - vy * clr * fact_ortho - ny * clr/2, clr+4, rnd_false); rnd_polyarea_boolean(ptmp, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; /* optical tuning: make sure the clearance is large enough around corners even if lines didn't meet - just throw in a big circle */ ptmp = rnd_poly_from_circle(x, y, clr); rnd_polyarea_boolean(ptmp, *pres, &p, RND_PBO_UNITE); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; } px = x; py = y; } } static void polytherm_sharp(rnd_polyarea_t **pres, pcb_poly_it_t *it, rnd_coord_t clr, rnd_bool is_diag, double tune) { rnd_polyarea_t *ptmp, *p; rnd_coord_t cx, cy, x2c, y2c; double px, py, x, y, dx, dy, vx, vy, nx, ny, mx, my, len, x2, y2, vx2, vy2, len2, dx2, dy2; int n, go, first = 1; char cong[CONG_MAX], cong2[CONG_MAX]; if (tune <= 0) tune = 1; if (is_diag) { int start = -1, v = cong_map(cong2, it, clr); memset(cong, 1, sizeof(cong)); /* in case of sharp-diag, draw the bridge in the middle of each congestion; run *2 and do module v so congestions spanning over the end are no special */ for(n = 0; n < v*2; n++) { if (cong2[n % v] == 0) { if (start >= 0) cong[((start+n)/2) % v] = 0; start = n; } } } else { /* normal congestion logic: use long edges only */ cong_map(cong, it, clr); } /* iterate over the vectors of the contour */ for(n = 0, go = pcb_poly_vect_first(it, &cx, &cy); go; go = pcb_poly_vect_next(it, &cx, &cy), n++) { x = cx; y = cy; if (first) { pcb_poly_vect_peek_prev(it, &cx, &cy); px = cx; py = cy; first = 0; } /*rnd_trace("[%d] %d %mm;%mm\n", n, cong[n], (rnd_coord_t)x, (rnd_coord_t)y);*/ /* skip points too dense */ if ((n >= CONG_MAX) || (cong[n])) { px = x; py = y; continue; } dx = x - px; dy = y - py; len = sqrt(dx*dx + dy*dy); vx = dx / len; vy = dy / len; if (is_diag) { pcb_poly_vect_peek_next(it, &x2c, &y2c); x2 = x2c; y2 = y2c; dx2 = x - x2; dy2 = y - y2; len2 = sqrt(dx2*dx2 + dy2*dy2); vx2 = dx2 / len2; vy2 = dy2 / len2; nx = -(vy2 - vy); ny = vx2 - vx; /* line from each corner at the average angle of the two edges from the corner */ TODO("can not enable 'tune' here because of a poly lib bug for rectangular poly vs. 20 mil diagonal sharp with tun=1.5"); ptmp = pa_line_at(x-nx*clr*1.2, y-ny*clr*1.2, x + nx*clr*6, y + ny*clr*6, clr/2*tune, rnd_true); rnd_polyarea_boolean(*pres, ptmp, &p, RND_PBO_SUB); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; } else { nx = -vy; ny = vx; mx = (x+px)/2.0; my = (y+py)/2.0; /* perpendicular line from the middle of each edge */ ptmp = pa_line_at(mx, my, mx - nx*clr, my - ny*clr, clr/2*tune, rnd_true); rnd_polyarea_boolean(*pres, ptmp, &p, RND_PBO_SUB); rnd_polyarea_free(pres); rnd_polyarea_free(&ptmp); *pres = p; } px = x; py = y; } } /* generate round thermal around a polyarea specified by the iterator */ static void pcb_thermal_area_pa_round(rnd_polyarea_t **pres, pcb_poly_it_t *it, rnd_coord_t clr, rnd_bool_t is_diag) { rnd_pline_t *pl; /* cut out the poly so terminals will be displayed proerply */ polytherm_base(pres, it->pa); /* generate the clear-lines */ pl = pcb_poly_contour(it); if (pl != NULL) polytherm_round(pres, it, clr, is_diag, 0/*conf_core.design.thermal.poly_scale*/); } /* generate sharp thermal around a polyarea specified by the iterator */ static void pcb_thermal_area_pa_sharp(rnd_polyarea_t **pres, pcb_poly_it_t *it, rnd_coord_t clr, rnd_bool_t is_diag) { rnd_pline_t *pl; /* add the usual clearance glory around the polygon */ pcb_poly_pa_clearance_construct(pres, it, clr); pl = pcb_poly_contour(it); if (pl != NULL) polytherm_sharp(pres, it, clr, is_diag, 0/*conf_core.design.thermal.poly_scale*/); /* trim internal stubs */ polytherm_base(pres, it->pa); } rnd_polyarea_t *pcb_thermal_area_poly(pcb_board_t *pcb, pcb_poly_t *poly, rnd_layer_id_t lid, pcb_poly_t *in_poly) { rnd_polyarea_t *pa, *pres = NULL; rnd_coord_t clr = pcb_obj_clearance_o05(poly, in_poly); pcb_poly_it_t it; assert(poly->thermal & PCB_THERMAL_ON); /* caller should have checked this */ switch(poly->thermal & 3) { case PCB_THERMAL_NOSHAPE: case PCB_THERMAL_SOLID: return NULL; case PCB_THERMAL_ROUND: for(pa = pcb_poly_island_first(poly, &it); pa != NULL; pa = pcb_poly_island_next(&it)) pcb_thermal_area_pa_round(&pres, &it, clr, (poly->thermal & PCB_THERMAL_DIAGONAL)); return pres; case PCB_THERMAL_SHARP: for(pa = pcb_poly_island_first(poly, &it); pa != NULL; pa = pcb_poly_island_next(&it)) pcb_thermal_area_pa_sharp(&pres, &it, clr, (poly->thermal & PCB_THERMAL_DIAGONAL)); return pres; } return NULL; } /* Generate a clearance around a padstack shape, with no thermal */ static rnd_polyarea_t *pcb_thermal_area_pstk_nothermal(pcb_board_t *pcb, pcb_pstk_t *ps, rnd_layer_id_t lid, pcb_pstk_shape_t *shp, rnd_coord_t clearance) { pcb_poly_it_t it; rnd_polyarea_t *pres = NULL; pcb_pstk_shape_t tmpshp; retry:; switch(shp->shape) { case PCB_PSSH_HSHADOW: shp = pcb_pstk_hshadow_shape(ps, shp, &tmpshp); if (shp == NULL) return NULL; goto retry; case PCB_PSSH_CIRC: return rnd_poly_from_circle(ps->x + shp->data.circ.x, ps->y + shp->data.circ.y, shp->data.circ.dia/2 + clearance); case PCB_PSSH_LINE: return pa_line_at(ps->x + shp->data.line.x1, ps->y + shp->data.line.y1, ps->x + shp->data.line.x2, ps->y + shp->data.line.y2, shp->data.line.thickness + clearance*2, shp->data.line.square); case PCB_PSSH_POLY: if (shp->data.poly.pa == NULL) pcb_pstk_shape_update_pa(&shp->data.poly); if (shp->data.poly.pa == NULL) return NULL; pcb_poly_iterate_polyarea(shp->data.poly.pa, &it); pcb_poly_pa_clearance_construct(&pres, &it, clearance); rnd_polyarea_move(pres, ps->x, ps->y); return pres; } return NULL; } rnd_polyarea_t *pcb_thermal_area_pstk(pcb_board_t *pcb, pcb_pstk_t *ps, rnd_layer_id_t lid, pcb_poly_t *in_poly) { unsigned char thr; pcb_pstk_shape_t *shp, tmpshp; rnd_polyarea_t *pres = NULL; pcb_layer_t *layer; rnd_coord_t clearance, gclearance = ps->Clearance; /* this is a legal direct access to the Clearance field, poly->enforce_clearance is applied later */ /* thermal needs a stackup - only a PCB has a stackup, buffers don't */ if (pcb == NULL) return NULL; /* if we have no clearance, there's no reason to do anything; ps->Clearance == 0 doesn't mean no clearance because of the per shape clearances */ if (!PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, ps)) return NULL; layer = pcb_get_layer(pcb->Data, lid); /* retrieve shape; assume 0 (no shape) for layers not named */ if (lid < ps->thermals.used) thr = ps->thermals.shape[lid]; else thr = 0; shp = pcb_pstk_shape_at_(pcb, ps, layer, 1); if (shp == NULL) { rnd_layergrp_id_t gid; pcb_layergrp_t *grp; pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); if (proto->hdia < 0) return NULL; /* corner case: subtracting a hole from a mech/drill poly (e.g. gcode) */ gid = pcb_layer_get_group_(layer); grp = pcb_get_layergrp(pcb, gid); if (PCB_LAYER_IS_DRILL(grp->ltype, grp->purpi) || ((grp->ltype & PCB_LYT_MECH) && (grp->purpi == (proto->hplated ? F_proute : F_uroute)))) { thr = 0; shp = &tmpshp; shp->shape = PCB_PSSH_CIRC; shp->data.circ.x = 0; shp->data.circ.y = 0; shp->data.circ.dia = proto->hdia; } else return NULL; } if (gclearance <= 0) /* global clearance overrides */ clearance = shp->clearance/2; else clearance = gclearance; /* apply polygon enforced clearance on top of the per shape clearance */ clearance = RND_MAX(clearance, in_poly->enforce_clearance); if (clearance <= 0) { rnd_layergrp_id_t gid = pcb_layer_get_group_(layer); pcb_layergrp_t *grp = pcb_get_layergrp(pcb, gid); pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); if (grp == NULL) return NULL; if (PCB_LAYER_IS_DRILL(grp->ltype, grp->purpi) || ((grp->ltype & PCB_LYT_MECH) && (grp->purpi == (proto->hplated ? F_proute : F_uroute)))) { /* shape on a slot layer - do it, but without termals! */ thr = 0; } else return NULL; } if (!(thr & PCB_THERMAL_ON)) return pcb_thermal_area_pstk_nothermal(pcb, ps, lid, shp, clearance); switch(thr & 3) { case PCB_THERMAL_NOSHAPE: tmpshp.shape = PCB_PSSH_HSHADOW; tmpshp.clearance = shp->clearance; return pcb_thermal_area_pstk_nothermal(pcb, ps, lid, &tmpshp, clearance); case PCB_THERMAL_SOLID: return NULL; case PCB_THERMAL_ROUND: case PCB_THERMAL_SHARP: retry:; switch(shp->shape) { case PCB_PSSH_HSHADOW: shp = pcb_pstk_hshadow_shape(ps, shp, &tmpshp); if (shp == NULL) return NULL; goto retry; case PCB_PSSH_CIRC: return ThermPoly_(pcb, ps->x + shp->data.circ.x, ps->y + shp->data.circ.y, shp->data.circ.dia, clearance*2, pcb_themal_style_new2old(thr)); case PCB_PSSH_LINE: { pcb_line_t ltmp; if (shp->data.line.square) ltmp.Flags = pcb_flag_make(PCB_FLAG_SQUARE); else ltmp.Flags = pcb_no_flags(); ltmp.Point1.X = ps->x + shp->data.line.x1; ltmp.Point1.Y = ps->y + shp->data.line.y1; ltmp.Point2.X = ps->x + shp->data.line.x2; ltmp.Point2.Y = ps->y + shp->data.line.y2; ltmp.Thickness = shp->data.line.thickness; ltmp.Clearance = clearance*2; ltmp.thermal = thr; pres = pcb_thermal_area_line(pcb, <mp, lid, in_poly); } return pres; case PCB_PSSH_POLY: { pcb_poly_it_t it; if (shp->data.poly.pa == NULL) pcb_pstk_shape_update_pa(&shp->data.poly); if (shp->data.poly.pa == NULL) return NULL; pcb_poly_iterate_polyarea(shp->data.poly.pa, &it); if (thr & PCB_THERMAL_ROUND) pcb_thermal_area_pa_round(&pres, &it, clearance, (thr & PCB_THERMAL_DIAGONAL)); else pcb_thermal_area_pa_sharp(&pres, &it, clearance, (thr & PCB_THERMAL_DIAGONAL)); if (pres != NULL) rnd_polyarea_move(pres, ps->x, ps->y); } return pres; } } return NULL; } rnd_polyarea_t *pcb_thermal_area(pcb_board_t *pcb, pcb_any_obj_t *obj, rnd_layer_id_t lid, pcb_poly_t *in_poly) { switch(obj->type) { case PCB_OBJ_LINE: return pcb_thermal_area_line(pcb, (pcb_line_t *)obj, lid, in_poly); case PCB_OBJ_POLY: return pcb_thermal_area_poly(pcb, (pcb_poly_t *)obj, lid, in_poly); case PCB_OBJ_PSTK: return pcb_thermal_area_pstk(pcb, (pcb_pstk_t *)obj, lid, in_poly); case PCB_OBJ_ARC: break; default: break; } return NULL; } /*** undoable thermal change ***/ typedef struct { pcb_board_t *pcb; pcb_any_obj_t *obj; unsigned char shape; } undo_anyobj_thermal_t; static int undo_anyobj_thermal_swap(void *udata) { undo_anyobj_thermal_t *t = udata; unsigned char old, *th = pcb_obj_common_get_thermal(t->obj, 0, 1); if (th != NULL) { pcb_poly_restore_to_poly(t->pcb->Data, t->obj->type, t->obj->parent.layer, t->obj); old = *th; *th = t->shape; t->shape = old; pcb_poly_clear_from_poly(t->pcb->Data, t->obj->type, t->obj->parent.layer, t->obj); pcb_draw_invalidate(t->obj); } return 0; } static void undo_anyobj_thermal_print(void *udata, char *dst, size_t dst_len) { undo_anyobj_thermal_t *t = udata; rnd_snprintf(dst, dst_len, "anyobj_thermal: #%ld %d", t->obj->ID, t->shape); } static const uundo_oper_t undo_anyobj_thermal = { core_thermal_cookie, NULL, /* free */ undo_anyobj_thermal_swap, undo_anyobj_thermal_swap, undo_anyobj_thermal_print }; int pcb_anyobj_set_thermal(pcb_any_obj_t *obj, unsigned char shape, int undoable) { undo_anyobj_thermal_t *t; pcb_board_t *pcb = NULL; if (obj->type == PCB_OBJ_PSTK) return -1; /* needs a layer */ if (undoable) { assert(obj->parent_type == PCB_PARENT_LAYER); pcb = pcb_data_get_top(obj->parent.layer->parent.data); } if (!undoable || (pcb == NULL)) { unsigned char *th = pcb_obj_common_get_thermal(obj, 0, 1); if (th != NULL) *th = shape; return 0; } t = pcb_undo_alloc(pcb, &undo_anyobj_thermal, sizeof(undo_anyobj_thermal_t)); t->pcb = pcb; t->obj = obj; t->shape = shape; undo_anyobj_thermal_swap(t); return 0; } void *pcb_anyop_change_thermal(pcb_opctx_t *ctx, pcb_any_obj_t *obj) { if (PCB_FLAG_TEST(PCB_FLAG_LOCK, obj)) return NULL; if (pcb_anyobj_set_thermal(obj, ctx->chgtherm.style, 1) != 0) return NULL; return obj; }