/* * COPYRIGHT * * pcb-rnd, interactive printed circuit board design * Copyright (C) 2017,2018,2021 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 #include "board.h" #include "conf_core.h" #include "data.h" #include "data_list.h" #include "draw.h" #include "draw_wireframe.h" #include "flag.h" #include "obj_pstk.h" #include "obj_pstk_draw.h" #include "obj_pstk_list.h" #include "obj_pstk_inlines.h" #include "obj_pstk_op.h" #include "obj_subc_parent.h" #include "obj_hash.h" #include "operation.h" #include "search.h" #include "undo.h" #include "vtpadstack.h" #include "event.h" #include #include #define SQR(o) ((o)*(o)) static const char core_pstk_cookie[] = "padstack"; const pcb_proto_layer_t pcb_proto_layers[pcb_proto_num_layers] = { {"top paste", PCB_LYT_TOP | PCB_LYT_PASTE, PCB_LYC_AUTO, {2,-1}, 0}, {"top mask", PCB_LYT_TOP | PCB_LYT_MASK, PCB_LYC_SUB|PCB_LYC_AUTO, {2,-1}, PCB_PROTO_MASK_BLOAT}, {"top copper", PCB_LYT_TOP | PCB_LYT_COPPER, 0, {4,3}, 0}, {"any internal copper", PCB_LYT_INTERN | PCB_LYT_COPPER, 0, {2,4}, 0}, {"bottom copper", PCB_LYT_BOTTOM | PCB_LYT_COPPER, 0, {2,3}, 0}, {"bottom mask", PCB_LYT_BOTTOM | PCB_LYT_MASK, PCB_LYC_SUB|PCB_LYC_AUTO, {4,-1}, PCB_PROTO_MASK_BLOAT}, {"bottom paste", PCB_LYT_BOTTOM | PCB_LYT_PASTE, PCB_LYC_AUTO, {4,-1}, 0}, {"slot", PCB_LYT_MECH, PCB_LYC_AUTO, {-1,-1},0} }; void pcb_pstk_reg(pcb_data_t *data, pcb_pstk_t *pstk) { padstacklist_append(&data->padstack, pstk); pcb_obj_id_reg(data, pstk); PCB_SET_PARENT(pstk, data, data); } void pcb_pstk_unreg(pcb_pstk_t *pstk) { pcb_data_t *data = pstk->parent.data; assert(pstk->parent_type == PCB_PARENT_DATA); padstacklist_remove(pstk); pcb_obj_id_del(data, pstk); PCB_CLEAR_PARENT(pstk); } pcb_pstk_t *pcb_pstk_alloc_id(pcb_data_t *data, long int id) { pcb_pstk_t *ps; ps = calloc(sizeof(pcb_pstk_t), 1); ps->ID = id; ps->protoi = -1; ps->type = PCB_OBJ_PSTK; ps->Attributes.post_change = pcb_obj_attrib_post_change; pcb_pstk_reg(data, ps); return ps; } pcb_pstk_t *pcb_pstk_alloc(pcb_data_t *data) { return pcb_pstk_alloc_id(data, pcb_create_ID_get()); } void pcb_pstk_free(pcb_pstk_t *ps) { if ((ps->parent.data != NULL) && (ps->parent.data->padstack_tree != NULL)) rnd_rtree_delete(ps->parent.data->padstack_tree, ps, (rnd_rtree_box_t *)ps); pcb_attribute_free(&ps->Attributes); pcb_pstk_unreg(ps); free(ps->thermals.shape); pcb_obj_common_free((pcb_any_obj_t *)ps); free(ps); } pcb_pstk_t *pcb_pstk_new_tr(pcb_data_t *data, long int id, rnd_cardinal_t proto, rnd_coord_t x, rnd_coord_t y, rnd_coord_t clearance, pcb_flag_t Flags, double rot, int xmirror, int smirror) { pcb_pstk_t *ps; if (proto >= pcb_vtpadstack_proto_len(&data->ps_protos)) return NULL; if (id <= 0) id = pcb_create_ID_get(); ps = pcb_pstk_alloc_id(data, id); /* copy values */ ps->proto = proto; ps->x = x; ps->y = y; ps->Clearance = clearance; ps->Flags = Flags; ps->rot = rot; ps->xmirror = xmirror; ps->smirror = smirror; pcb_pstk_add(data, ps); rnd_event(&PCB->hidlib, PCB_EVENT_NEW_PSTK, "p", ps); pcb_poly_clear_from_poly(data, PCB_OBJ_PSTK, NULL, ps); return ps; } pcb_pstk_t *pcb_pstk_new(pcb_data_t *data, long int id, rnd_cardinal_t proto, rnd_coord_t x, rnd_coord_t y, rnd_coord_t clearance, pcb_flag_t Flags) { return pcb_pstk_new_tr(data, id, proto, x, y, clearance, Flags, 0, 0, 0); } void pcb_pstk_add(pcb_data_t *data, pcb_pstk_t *ps) { pcb_pstk_bbox(ps); if (!data->padstack_tree) rnd_rtree_init(data->padstack_tree = malloc(sizeof(rnd_rtree_t))); rnd_rtree_insert(data->padstack_tree, ps, (rnd_rtree_box_t *)ps); PCB_SET_PARENT(ps, data, data); } void pcb_pstk_bbox_ts(rnd_box_t *dst, pcb_pstk_proto_t *proto, pcb_pstk_tshape_t *ts, rnd_coord_t ox, rnd_coord_t oy, rnd_bool with_clearance, rnd_coord_t ps_clearance) { int n, sn; pcb_line_t line; rnd_coord_t max_clr = 0; assert(proto != NULL); dst->X1 = dst->X2 = ox; dst->Y1 = dst->Y2 = oy; if (ts != NULL) for(sn = 0; sn < ts->len; sn++) { pcb_pstk_shape_t *shape = ts->shape + sn; rnd_coord_t clr = 0; rnd_box_t tmpbox; if (with_clearance) { if (ps_clearance != 0) /* global clearance overrides */ clr = ps_clearance; else clr = shape->clearance/2; if (clr > max_clr) max_clr = clr; } switch(shape->shape) { case PCB_PSSH_POLY: tmpbox.X1 = tmpbox.Y1 = +RND_COORD_MAX; tmpbox.X2 = tmpbox.Y2 = -RND_COORD_MAX; for(n = 0; n < shape->data.poly.len; n++) rnd_box_bump_point(&tmpbox, shape->data.poly.x[n] + ox, shape->data.poly.y[n] + oy); if (clr != 0) { tmpbox.X1 -= clr; tmpbox.Y1 -= clr; tmpbox.X2 += clr; tmpbox.Y2 += clr; } rnd_box_bump_box(dst, &tmpbox); break; case PCB_PSSH_LINE: line.Point1.X = shape->data.line.x1 + ox; line.Point1.Y = shape->data.line.y1 + oy; line.Point2.X = shape->data.line.x2 + ox; line.Point2.Y = shape->data.line.y2 + oy; line.Thickness = shape->data.line.thickness; line.Clearance = clr*2; line.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0); pcb_line_bbox(&line); rnd_box_bump_box(dst, &line.BoundingBox); break; case PCB_PSSH_CIRC: rnd_box_bump_point(dst, ox - shape->data.circ.dia/2 - clr, oy - shape->data.circ.dia/2 - clr); rnd_box_bump_point(dst, ox + shape->data.circ.dia/2 + clr, oy + shape->data.circ.dia/2 + clr); break; case PCB_PSSH_HSHADOW: break; } } if (proto->hdia != 0) { /* corner case: no copper around the hole all 360 deg - let the hole stick out */ rnd_box_bump_point(dst, ox - proto->hdia/2 - max_clr, oy - proto->hdia/2 - max_clr); rnd_box_bump_point(dst, ox + proto->hdia/2 + max_clr, oy + proto->hdia/2 + max_clr); } rnd_close_box(dst); } /* padstack is a special case: there can be local clearance in the proto when (obj)->Clearance is 0 */ #define PCB_PSTK_HAS_CLEARANCE(obj) \ (PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, (obj))) static void pcb_pstk_bbox_(rnd_box_t *dst, pcb_pstk_t *ps, rnd_bool copper_only) { pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape(ps); pcb_pstk_bbox_ts(dst, proto, ts, ps->x, ps->y, !copper_only && PCB_PSTK_HAS_CLEARANCE(ps), ps->Clearance); } void pcb_pstk_bbox(pcb_pstk_t *ps) { pcb_pstk_bbox_(&ps->BoundingBox, ps, rnd_false); pcb_pstk_bbox_(&ps->bbox_naked, ps, rnd_true); } void pcb_pstk_copper_bbox(rnd_box_t *dst, pcb_pstk_t *ps) { pcb_pstk_bbox_(dst, ps, rnd_true); } /* hash */ int pcb_pstk_eq(const pcb_host_trans_t *tr1, const pcb_pstk_t *p1, const pcb_host_trans_t *tr2, const pcb_pstk_t *p2) { double rotdir1 = tr1->on_bottom ? -1.0 : 1.0, rotdir2 = tr2->on_bottom ? -1.0 : 1.0; TODO("padstack: should compare shape by shape: a 180 deg rotated or mirrored rectangle is still just the same rectangle!") if ((p1->smirror ^ tr1->on_bottom) != (p2->smirror ^ tr2->on_bottom)) return 0; if ((p1->xmirror ^ tr1->on_bottom) != (p2->xmirror ^ tr2->on_bottom)) return 0; if (floor(fmod((p1->rot * rotdir1) + tr1->rot, 360.0)*10000) != floor(fmod((p2->rot * rotdir2) + tr2->rot, 360.0)*10000)) return 0; if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, p1) && !PCB_FLAG_TEST(PCB_FLAG_FLOATER, p2)) { rnd_coord_t x1, y1, x2, y2; pcb_hash_tr_coords(tr1, &x1, &y1, p1->x, p1->y); pcb_hash_tr_coords(tr2, &x2, &y2, p2->x, p2->y); if ((x1 != x2) || (y1 != y2)) return 0; } if (pcb_neqs(p1->term, p2->term)) return 0; return 1; } unsigned int pcb_pstk_hash(const pcb_host_trans_t *tr, const pcb_pstk_t *p) { unsigned int crd = 0; double rotdir = tr->on_bottom ? -1.0 : 1.0; if (!PCB_FLAG_TEST(PCB_FLAG_FLOATER, p)) { rnd_coord_t x, y; pcb_hash_tr_coords(tr, &x, &y, p->x, p->y); crd = pcb_hash_coord(x) ^ pcb_hash_coord(y); } return pcb_hash_angle(tr, p->rot * rotdir) ^ pcb_hash_coord(p->Clearance) ^ pcb_hash_str(p->term) ^ crd ^ murmurhash(&p->smirror, sizeof(p->smirror)) ^ murmurhash(&p->xmirror, sizeof(p->xmirror)); } /*** utils ***/ pcb_pstk_t *pcb_pstk_copy_meta(pcb_pstk_t *dst, pcb_pstk_t *src) { if (dst == NULL) return NULL; pcb_attribute_copy_all(&dst->Attributes, &src->Attributes); dst->thermals.used = src->thermals.used; if (dst->thermals.used > 0) { dst->thermals.shape = malloc(dst->thermals.used * sizeof(dst->thermals.shape[0])); memcpy(dst->thermals.shape, src->thermals.shape, src->thermals.used * sizeof(src->thermals.shape[0])); } else dst->thermals.shape = 0; memcpy(&dst->Flags, &src->Flags, sizeof(dst->Flags)); return dst; } pcb_pstk_t *pcb_pstk_copy_orient(pcb_pstk_t *dst, pcb_pstk_t *src) { if (dst == NULL) return NULL; dst->rot = src->rot; dst->xmirror = src->xmirror; dst->smirror = src->smirror; dst->protoi = -1; /* invalidate the transformed index to get it recalculated */ return dst; } void pcb_pstk_move_(pcb_pstk_t *ps, rnd_coord_t dx, rnd_coord_t dy) { ps->x += dx; ps->y += dy; ps->BoundingBox.X1 += dx; ps->BoundingBox.Y1 += dy; ps->BoundingBox.X2 += dx; ps->BoundingBox.Y2 += dy; ps->bbox_naked.X1 += dx; ps->bbox_naked.Y1 += dy; ps->bbox_naked.X2 += dx; ps->bbox_naked.Y2 += dy; } void pcb_pstk_move(pcb_pstk_t *ps, rnd_coord_t dx, rnd_coord_t dy, rnd_bool more_to_come) { pcb_opctx_t ctx; ctx.move.pcb = NULL; ctx.move.dx = dx; ctx.move.dy = dy; ctx.move.more_to_come = more_to_come; pcb_pstkop_move(&ctx, ps); } pcb_pstk_t *pcb_pstk_by_id(pcb_data_t *base, long int ID) { /* We can not have an rtree based search here: we are often called in the middle of an operation, after the pstk got already removed from the rtree. It happens in e.g. undoable padstack operations where the padstack tries to look up its parent subc by ID, while the subc is being sent to the other side. The solution will be the ID hash. */ PCB_PADSTACK_LOOP(base); { if (padstack->ID == ID) return padstack; } PCB_END_LOOP; return NULL; } /*** draw ***/ static void set_ps_color(pcb_pstk_t *ps, int is_current, pcb_layer_type_t lyt, const pcb_layer_t *ly1, rnd_xform_t *xform) { const rnd_color_t *color, *layer_color = NULL; rnd_color_t buf; if ((lyt & PCB_LYT_PASTE) || (lyt & PCB_LYT_MASK) || (lyt & PCB_LYT_SILK)) { if (ly1 == NULL) layer_color = pcb_layer_default_color(0, lyt); else layer_color = &ly1->meta.real.color; } if (xform->invis_other_groups && !is_current) { color = &conf_core.appearance.color.invisible_objects; } else if (xform->black_current_group && is_current) { color = &conf_core.appearance.color.black_group; } else if (ps->term == NULL) { /* normal via, not a terminal */ if (xform->flag_color && !pcb_draw_force_termlab && PCB_FLAG_TEST(PCB_FLAG_WARN | PCB_FLAG_SELECTED | PCB_FLAG_FOUND, ps)) { if (PCB_FLAG_TEST(PCB_FLAG_WARN, ps)) color = &conf_core.appearance.color.warn; else if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps)) color = &conf_core.appearance.color.selected; else color = &conf_core.appearance.color.connected; if (ps->ind_onpoint) { pcb_lighten_color(color, &buf, 1.75); color = &buf; } } else { if (layer_color != NULL) color = layer_color; else if (PCB_HAS_COLOROVERRIDE(ps)) color = ps->override_color; else if (is_current) color = &conf_core.appearance.color.via; else color = &conf_core.appearance.color.via_far; } } else { /* terminal */ if (xform->flag_color && !pcb_draw_force_termlab && PCB_FLAG_TEST(PCB_FLAG_WARN | PCB_FLAG_SELECTED | PCB_FLAG_FOUND, ps)) { if (PCB_FLAG_TEST(PCB_FLAG_WARN, ps)) color = &conf_core.appearance.color.warn; else if (PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps)) color = &conf_core.appearance.color.selected; else color = &conf_core.appearance.color.connected; if (ps->ind_onpoint) { pcb_lighten_color(color, &buf, 1.75); color = &buf; } } else if (layer_color != NULL) color = layer_color; else if (PCB_HAS_COLOROVERRIDE(ps)) color = ps->override_color; else if (is_current) color = &conf_core.appearance.color.pin; else color = &conf_core.appearance.color.pin_far; } rnd_render->set_color(pcb_draw_out.fgGC, color); } static void set_ps_annot_color(rnd_hid_gc_t gc, pcb_pstk_t *ps) { rnd_render->set_color(pcb_draw_out.fgGC, PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps) ? &conf_core.appearance.color.selected : &conf_core.appearance.color.padstackmark); } static void pcb_pstk_draw_poly(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape, int fill, rnd_coord_t dthick) { long n; if (shape->data.poly.pa == NULL) pcb_pstk_shape_update_pa(&shape->data.poly); rnd_hid_set_line_cap(gc, rnd_cap_round); if (dthick != 0) { /* slow - but would be used on export mostly, not on-screen drawing */ rnd_polo_t *p, p_st[32]; rnd_coord_t *x, *y, xy_st[64]; double vl = rnd_round(-dthick/2); long n; if (shape->data.poly.inverted) dthick = -dthick; vl = rnd_round(dthick/2); if (shape->data.poly.len >= sizeof(p_st) / sizeof(p_st[0])) { p = malloc(sizeof(rnd_polo_t) * shape->data.poly.len); x = malloc(sizeof(rnd_coord_t) * shape->data.poly.len); } else { p = p_st; x = xy_st; } y = x + sizeof(p_st) / sizeof(p_st[0]); /* relative: move each point radially */ for(n = 0; n < shape->data.poly.len; n++) { p[n].x = shape->data.poly.x[n]; p[n].y = shape->data.poly.y[n]; } rnd_polo_norms(p, shape->data.poly.len); rnd_polo_offs(vl, p, shape->data.poly.len); for(n = 0; n < shape->data.poly.len; n++) { x[n] = rnd_round(p[n].x); y[n] = rnd_round(p[n].y); } if (!fill) { for(n = 1; n < shape->data.poly.len; n++) rnd_render->draw_line(gc, ps->x + x[n-1], ps->y + y[n-1], ps->x + x[n], ps->y + y[n]); rnd_render->draw_line(gc, ps->x + x[n-1], ps->y + y[n-1], ps->x + x[0], ps->y + y[0]); } else rnd_render->fill_polygon_offs(gc, shape->data.poly.len, x, y, ps->x, ps->y); if (p != p_st) { free(p); free(x); } } else { if (!fill) { for(n = 1; n < shape->data.poly.len; n++) rnd_render->draw_line(gc, ps->x + shape->data.poly.x[n-1], ps->y + shape->data.poly.y[n-1], ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n]); rnd_render->draw_line(gc, ps->x + shape->data.poly.x[n-1], ps->y + shape->data.poly.y[n-1], ps->x + shape->data.poly.x[0], ps->y + shape->data.poly.y[0]); } else rnd_render->fill_polygon_offs(gc, shape->data.poly.len, shape->data.poly.x, shape->data.poly.y, ps->x, ps->y); } } static void pcb_pstk_draw_shape_solid(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape) { rnd_coord_t r, dthick = 0; if ((info != NULL) && (info->xform != NULL) && (info->xform->bloat != 0)) dthick = info->xform->bloat; switch(shape->shape) { case PCB_PSSH_POLY: pcb_pstk_draw_poly(info, gc, ps, shape, 1, dthick); break; case PCB_PSSH_LINE: rnd_hid_set_line_cap(gc, shape->data.line.square ? rnd_cap_square : rnd_cap_round); rnd_hid_set_line_width(gc, MAX(shape->data.line.thickness + dthick, 1)); rnd_render->draw_line(gc, ps->x + shape->data.line.x1, ps->y + shape->data.line.y1, ps->x + shape->data.line.x2, ps->y + shape->data.line.y2); break; case PCB_PSSH_CIRC: r = MAX(shape->data.circ.dia/2 + dthick/2, 1); rnd_hid_set_line_cap(gc, rnd_cap_round); rnd_render->fill_circle(gc, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, r); break; case PCB_PSSH_HSHADOW: break; } } static void pcb_pstk_draw_shape_thin(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps, pcb_pstk_shape_t *shape) { rnd_coord_t r, dthick = 0; rnd_hid_set_line_cap(gc, rnd_cap_round); if ((info != NULL) && (info->xform != NULL) && (info->xform->bloat != 0)) dthick = info->xform->bloat; switch(shape->shape) { case PCB_PSSH_POLY: pcb_pstk_draw_poly(info, gc, ps, shape, 0, dthick); break; case PCB_PSSH_LINE: pcb_draw_wireframe_line(gc, ps->x + shape->data.line.x1, ps->y + shape->data.line.y1, ps->x + shape->data.line.x2, ps->y + shape->data.line.y2, MAX(shape->data.line.thickness + dthick, 1), shape->data.line.square); break; case PCB_PSSH_CIRC: r = MAX(shape->data.circ.dia/2 + dthick/2, 1); rnd_render->draw_arc(gc, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, r, r, 0, 360); break; case PCB_PSSH_HSHADOW: break; } } rnd_rtree_dir_t pcb_pstk_draw_callback(void *cl, void *obj, const rnd_rtree_box_t *box) { pcb_draw_info_t *info = cl; pcb_pstk_t *ps = (pcb_pstk_t *)obj; pcb_pstk_shape_t *shape; pcb_layergrp_t *grp = NULL; pcb_obj_noexport(info, ps, return rnd_RTREE_DIR_NOT_FOUND_CONT); if (pcb_hidden_floater((pcb_any_obj_t*)ps, info) || pcb_partial_export((pcb_any_obj_t*)ps, info)) return rnd_RTREE_DIR_FOUND_CONT; if (!PCB->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent)) return rnd_RTREE_DIR_NOT_FOUND_CONT; if (info->objcb.pstk.gid < 0) { if (info->objcb.pstk.shape_mask != 0) shape = pcb_pstk_shape(ps, info->objcb.pstk.shape_mask, info->objcb.pstk.comb); else return rnd_RTREE_DIR_NOT_FOUND_CONT; } else { int n; rnd_layer_id_t *lid; shape = pcb_pstk_shape_gid(info->pcb, ps, info->objcb.pstk.gid, info->objcb.pstk.comb, &grp); for(n = 0, lid = grp->lid; n < grp->len; n++,lid++) { if (*lid < ps->thermals.used) { if ((ps->thermals.shape[*lid] & PCB_THERMAL_ON) && ((ps->thermals.shape[*lid] & 3) == PCB_THERMAL_NOSHAPE)) return rnd_RTREE_DIR_NOT_FOUND_CONT; } } } if (shape != NULL) { if (grp == NULL) set_ps_color(ps, info->objcb.pstk.is_current, info->objcb.pstk.shape_mask, info->objcb.pstk.layer1, info->xform); else set_ps_color(ps, info->objcb.pstk.is_current, grp->ltype, info->objcb.pstk.layer1, info->xform); if (info->xform->thin_draw || info->xform->wireframe) { rnd_hid_set_line_width(pcb_draw_out.fgGC, 0); pcb_pstk_draw_shape_thin(info, pcb_draw_out.fgGC, ps, shape); } else pcb_pstk_draw_shape_solid(info, pcb_draw_out.fgGC, ps, shape); ps->draw_stamp = info->draw_stamp; if (ps->ind_editpoint) pcb_draw_delay_label_add((pcb_any_obj_t *)ps); } return rnd_RTREE_DIR_FOUND_CONT; } rnd_rtree_dir_t pcb_pstk_draw_mark_callback(void *cl, void *obj, const rnd_rtree_box_t *box) { pcb_pstk_t *ps = (pcb_pstk_t *)obj; pcb_pstk_proto_t *proto; rnd_coord_t mark, mark2; /* mark is a cross in the middle, right on the hole; cross size should extend beyond the hole */ rnd_hid_set_line_cap(pcb_draw_out.fgGC, rnd_cap_round); mark = conf_core.appearance.padstack.cross_size; proto = pcb_pstk_get_proto(ps); if (proto != NULL) mark += proto->hdia/2; mark2 = mark*2; if (mark2 < rnd_render->coord_per_pix) return rnd_RTREE_DIR_FOUND_CONT; /* draw the cross using xor */ set_ps_annot_color(pcb_draw_out.fgGC, ps); rnd_hid_set_line_width(pcb_draw_out.fgGC, 0); if (mark2 > rnd_render->coord_per_pix*3) { rnd_render->draw_line(pcb_draw_out.fgGC, ps->x-mark, ps->y, ps->x+mark, ps->y); rnd_render->draw_line(pcb_draw_out.fgGC, ps->x, ps->y-mark, ps->x, ps->y+mark); } else rnd_render->draw_line(pcb_draw_out.fgGC, ps->x-rnd_render->coord_per_pix, ps->y, ps->x+rnd_render->coord_per_pix, ps->y); return rnd_RTREE_DIR_FOUND_CONT; } rnd_rtree_dir_t pcb_pstk_draw_label_callback(void *cl, void *obj, const rnd_rtree_box_t *box) { pcb_draw_info_t *info = cl; pcb_pstk_t *ps = (pcb_pstk_t *)obj; int is_drawn = (ps->draw_stamp == info->draw_stamp); /* do not draw any label if the padstack is not visible, to avoid overlapping term labels of sides: two SMD pads on the two sides, turn off bottom to see top only */ if (!is_drawn) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* draw the label if enabled, after everything else is drawn */ if (ps->term != NULL) { if ((pcb_draw_force_termlab) || PCB_FLAG_TEST(PCB_FLAG_TERMNAME, ps)) pcb_pstk_draw_label(info, ps, 1); } if (ps->noexport) pcb_obj_noexport_mark(ps, ps->x, ps->y); return rnd_RTREE_DIR_FOUND_CONT; } rnd_rtree_dir_t pcb_pstk_draw_hole_callback(void *cl, void *obj, const rnd_rtree_box_t *box) { pcb_draw_info_t *info = cl; pcb_pstk_t *ps = (pcb_pstk_t *)obj; pcb_pstk_proto_t *proto; pcb_obj_noexport(info, ps, return rnd_RTREE_DIR_NOT_FOUND_CONT); if (pcb_hidden_floater(ps, info) || pcb_partial_export(ps, info)) return rnd_RTREE_DIR_FOUND_CONT; /* hide subc parts if requested */ if (!info->pcb->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent)) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* no hole in this layer group */ if (info->objcb.pstk.gid >= 0) { if (!pcb_pstk_bb_drills(info->pcb, ps, info->objcb.pstk.gid, &proto)) return rnd_RTREE_DIR_FOUND_CONT; } else proto = pcb_pstk_get_proto(ps); /* No hole at all */ if ((proto == NULL) || (proto->hdia <= 0)) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* hole is plated, but the caller doesn't want plated holes */ if (proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_PLATED))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* hole is unplated, but the caller doesn't want unplated holes */ if (!proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_UNPLATED))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* BBvia, but the caller doesn't want BBvias */ if (((proto->htop != 0) || (proto->hbottom != 0)) && (!(info->objcb.pstk.holetype & PCB_PHOLE_BB))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* actual hole */ rnd_hid_set_line_width(pcb_draw_out.drillGC, 0); rnd_hid_set_line_cap(pcb_draw_out.drillGC, rnd_cap_round); rnd_render->fill_circle(pcb_draw_out.drillGC, ps->x, ps->y, proto->hdia / 2); /* indicate unplated holes with an arc; unplated holes are more rare than plated holes, thus unplated holes are indicated */ if (!proto->hplated && !pcb_xform_omit_overlay(info)) { rnd_coord_t r = proto->hdia / 2; r -= r/8; /* +12.5% */ rnd_render->set_color(pcb_draw_out.fgGC, PCB_FLAG_TEST(PCB_FLAG_SELECTED, ps) ? &conf_core.appearance.color.selected : &conf_core.appearance.color.subc); rnd_hid_set_line_width(pcb_draw_out.fgGC, 0); rnd_hid_set_line_cap(pcb_draw_out.fgGC, rnd_cap_round); rnd_render->draw_arc(pcb_draw_out.fgGC, ps->x, ps->y, r, r, 20, 290); } return rnd_RTREE_DIR_FOUND_CONT; } rnd_rtree_dir_t pcb_pstk_draw_slot_callback(void *cl, void *obj, const rnd_rtree_box_t *box) { pcb_draw_info_t *info = cl; pcb_pstk_t *ps = (pcb_pstk_t *)obj; pcb_pstk_proto_t *proto; pcb_pstk_shape_t *shape; pcb_obj_noexport(info, ps, return rnd_RTREE_DIR_NOT_FOUND_CONT); if (pcb_hidden_floater(ps, info) || pcb_partial_export(ps, info)) return rnd_RTREE_DIR_FOUND_CONT; /* hide subc parts if requested */ if (!info->pcb->SubcPartsOn && pcb_gobj_parent_subc(ps->parent_type, &ps->parent)) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* no slot in this layer group */ if (info->objcb.pstk.gid >= 0) { if (!pcb_pstk_bb_drills(info->pcb, ps, info->objcb.pstk.gid, &proto)) return rnd_RTREE_DIR_FOUND_CONT; } else proto = pcb_pstk_get_proto(ps); if (proto == NULL) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* No slot at all */ if (proto->mech_idx < 0) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* hole is plated, but the caller doesn't want plated holes */ if (proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_PLATED))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* hole is unplated, but the caller doesn't want unplated holes */ if (!proto->hplated && (!(info->objcb.pstk.holetype & PCB_PHOLE_UNPLATED))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* BBslot, but the caller doesn't want BBslot */ if (((proto->htop != 0) || (proto->hbottom != 0)) && (!(info->objcb.pstk.holetype & PCB_PHOLE_BB))) return rnd_RTREE_DIR_NOT_FOUND_CONT; /* actual slot */ shape = pcb_pstk_shape(ps, PCB_LYT_MECH, PCB_LYC_AUTO); if (shape != NULL) { if (info->xform->thin_draw || info->xform->wireframe) { rnd_hid_set_line_width(pcb_draw_out.drillGC, 0); pcb_pstk_draw_shape_thin(info, pcb_draw_out.drillGC, ps, shape); } else pcb_pstk_draw_shape_solid(info, pcb_draw_out.drillGC, ps, shape); } return rnd_RTREE_DIR_FOUND_CONT; } void pcb_pstk_thindraw(pcb_draw_info_t *info, rnd_hid_gc_t gc, pcb_pstk_t *ps) { pcb_pstk_shape_t *shape = NULL; pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); pcb_board_t *pcb; rnd_layergrp_id_t gid = PCB_CURRLAYER(PCB)->meta.real.grp; pcb = pcb_data_get_top(ps->parent.data); if (pcb != NULL) { shape = pcb_pstk_shape_gid(pcb, ps, gid, 0, NULL); if (shape == NULL) shape = pcb_pstk_shape_gid(pcb, ps, gid, PCB_LYC_SUB, NULL); } else { /* no pcb means buffer */ pcb_pstk_tshape_t *ts; /* try a copper shape first */ shape = pcb_pstk_shape(ps, PCB_LYT_COPPER | PCB_LYT_TOP, 0); if (shape == NULL) shape = pcb_pstk_shape(ps, PCB_LYT_COPPER | PCB_LYT_BOTTOM, 0); /* fallback: take the first shape, whichever layer it is for */ if (shape == NULL) { ts = pcb_pstk_get_tshape(ps); if ((ts != NULL) && (ts->len > 0)) shape = ts->shape; } } if (shape != NULL) pcb_pstk_draw_shape_thin(info, gc, ps, shape); if ((proto != NULL) && (proto->hdia > 0)) { rnd_render->draw_arc(gc, ps->x, ps->y, proto->hdia/2, proto->hdia/2, 0, 360); } else { pcb_pstk_shape_t *mech_shape = pcb_pstk_shape(ps, PCB_LYT_MECH, PCB_LYC_AUTO); if ((mech_shape != NULL) && (mech_shape != shape)) pcb_pstk_draw_shape_thin(info, gc, ps, mech_shape); } } void pcb_pstk_draw_label(pcb_draw_info_t *info, pcb_pstk_t *ps, rnd_bool vis_side) { rnd_bool vert; rnd_coord_t dx, dy; if (ps->term == NULL) return; dx = ps->BoundingBox.X2 - ps->BoundingBox.X1; dy = ps->BoundingBox.Y2 - ps->BoundingBox.Y1; if ((dx == 0) && (dy == 0)) { pcb_pstk_bbox(ps); dx = ps->BoundingBox.X2 - ps->BoundingBox.X1; dy = ps->BoundingBox.Y2 - ps->BoundingBox.Y1; } vert = dx < dy; #ifdef PCB_PSTK_LABEL_OFFCENTER rnd_coord_t offs = 0; pcb_pstk_proto_t *proto; proto = pcb_pstk_get_proto(ps); if ((proto != NULL) && (proto->hdia > 0)) offs = proto->hdia/2; pcb_term_label_draw(info, ps->x + offs, ps->y, conf_core.appearance.term_label_size, vert, rnd_false, (pcb_any_obj_t *)ps); #endif pcb_term_label_draw(info, ps->x, ps->y, conf_core.appearance.term_label_size, vert, rnd_true, (pcb_any_obj_t *)ps); } static rnd_xform_t dummy_xform; void pcb_pstk_draw_preview(pcb_board_t *pcb, const pcb_pstk_t *ps, char *layers, rnd_bool mark, rnd_bool label, const rnd_box_t *drawn_area) { pcb_draw_info_t info; int n, draw_hole = 0; rnd_layer_id_t lid; info.pcb = pcb; info.drawn_area = drawn_area; info.xform_caller = info.xform_exporter = info.xform = &dummy_xform; info.layer = NULL; info.objcb.pstk.gid = -1; info.objcb.pstk.holetype = PCB_PHOLE_UNPLATED | PCB_PHOLE_PLATED; /* draw non-currents */ info.objcb.pstk.is_current = 0; for(n = pcb_proto_num_layers-1; n >= 0; n--) { if ((layers == NULL) || (layers[n] == 1)) { info.objcb.pstk.shape_mask = pcb_proto_layers[n].mask; info.objcb.pstk.comb = pcb_proto_layers[n].comb; info.objcb.pstk.layer1 = info.layer = NULL; if (!(info.objcb.pstk.shape_mask & PCB_LYT_COPPER)) { if (pcb_layer_list(pcb, info.objcb.pstk.shape_mask, &lid, 1) == 1) info.objcb.pstk.layer1 = info.layer = pcb_get_layer(pcb->Data, lid); } if (info.objcb.pstk.shape_mask == PCB_LYT_MECH) draw_hole = 1; else pcb_pstk_draw_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); } } /* draw current with strong */ info.objcb.pstk.is_current = 1; for(n = pcb_proto_num_layers-1; n >= 0; n--) { if ((layers == NULL) || (layers[n] > 1)) { info.objcb.pstk.shape_mask = pcb_proto_layers[n].mask; info.objcb.pstk.comb = pcb_proto_layers[n].comb; info.objcb.pstk.layer1 = info.layer = NULL; if (!(info.objcb.pstk.shape_mask & PCB_LYT_COPPER)) { if (pcb_layer_list(pcb, info.objcb.pstk.shape_mask, &lid, 1) == 1) info.objcb.pstk.layer1 = info.layer = pcb_get_layer(pcb->Data, lid); } if (info.objcb.pstk.shape_mask == PCB_LYT_MECH) draw_hole = 2; else pcb_pstk_draw_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); } } if (draw_hole) { info.objcb.pstk.shape_mask = PCB_LYT_MECH; info.objcb.pstk.is_current = (draw_hole > 1); pcb_pstk_draw_hole_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); pcb_pstk_draw_slot_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); } if (mark) pcb_pstk_draw_mark_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); if (label) pcb_pstk_draw_label_callback(&info, (void *)ps, (rnd_rtree_box_t *)ps); } void pcb_pstk_invalidate_erase(pcb_pstk_t *ps) { pcb_draw_invalidate(ps); } void pcb_pstk_invalidate_draw(pcb_pstk_t *ps) { pcb_draw_invalidate(ps); } static int pcb_pstk_near_box_(pcb_pstk_t *ps, rnd_box_t *box, pcb_pstk_shape_t *shape) { pcb_any_line_t pad; rnd_vector_t v; pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); int isneg = PCB_IS_BOX_NEGATIVE(box), is_in, n; /* cheap check: hole */ if (proto->hdia > 0) { /* in negative case, touching the hole means hit */ if (isneg) if (PCB_CIRCLE_TOUCHES_BOX(ps->x, ps->y, proto->hdia/2, box)) return 1; /* in positive case, not including the hole means bad */ if (!isneg) if (!PCB_POINT_IN_BOX(ps->x-proto->hdia/2,ps->y-proto->hdia/2, box) || !PCB_POINT_IN_BOX(ps->x+proto->hdia/2,ps->y+proto->hdia/2, box)) return 0; } switch(shape->shape) { case PCB_PSSH_CIRC: if (isneg) return PCB_CIRCLE_TOUCHES_BOX(ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, shape->data.circ.dia/2, box); return PCB_POINT_IN_BOX(ps->x+shape->data.circ.x-proto->hdia/2,ps->y+shape->data.circ.y-shape->data.circ.dia/2, box) && PCB_POINT_IN_BOX(ps->x+shape->data.circ.x+proto->hdia/2,ps->y+shape->data.circ.y+shape->data.circ.dia/2, box); case PCB_PSSH_LINE: pad.Point1.X = shape->data.line.x1 + ps->x; pad.Point1.Y = shape->data.line.y1 + ps->y; pad.Point2.X = shape->data.line.x2 + ps->x; pad.Point2.Y = shape->data.line.y2 + ps->y; pad.Thickness = shape->data.line.thickness; pad.Clearance = 0; pad.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0); return isneg ? PCB_PAD_TOUCHES_BOX(&pad, box) : PCB_PAD_IN_BOX(&pad, box); case PCB_PSSH_HSHADOW: if (proto->hdia > 0) { /* If there is a hole, we have already checked that the hole is near. Since hshadow is the hole. The negative case was already handled above at the hole, need to handle the positive for hit. Note: for slots there is a separate shape that will match */ if (!isneg && PCB_POINT_IN_BOX(ps->x-proto->hdia/2,ps->y-proto->hdia/2, box) && PCB_POINT_IN_BOX(ps->x+proto->hdia/2,ps->y+proto->hdia/2, box)) return 1; } return 0; case PCB_PSSH_POLY: if (shape->data.poly.pa == NULL) pcb_pstk_shape_update_pa(&shape->data.poly); if (isneg) { /* shortcut: if any point is in our box in negative, we are done */ for(n = 0; n < shape->data.poly.len; n++) { int pib = PCB_POINT_IN_BOX(ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n], box); if (pib) return 1; } } /* fully within */ is_in = 1; for(n = 0; n < shape->data.poly.len; n++) { int pib = PCB_POINT_IN_BOX(ps->x + shape->data.poly.x[n], ps->y + shape->data.poly.y[n], box); if (!pib) { is_in = 0; break; } } if (!isneg) /* normal box - accept only if we are in */ return is_in; /* negative box, not fully within, no poly point in the box, check whether the box is fully within the poly - if any of it's corners is in */ v[0] = box->X1 - ps->x; v[1] = box->Y1 - ps->y; if (rnd_polyarea_contour_inside(shape->data.poly.pa, v)) return 1; /* negative box, not fully within, no poly point in the box, check whether box lines intersect poly lines We expect shapes to be simple so linear search is faster than being clever */ for(n = 1; n < shape->data.poly.len; n++) { if (PCB_XYLINE_ISECTS_BOX(ps->x+shape->data.poly.x[n-1],ps->y+shape->data.poly.y[n-1],ps->x+shape->data.poly.x[n],ps->y+shape->data.poly.y[n], box)) return 1; } n = shape->data.poly.len-1; if (PCB_XYLINE_ISECTS_BOX(ps->x+shape->data.poly.x[n-1],ps->y+shape->data.poly.y[n-1],ps->x+shape->data.poly.x[0],ps->y+shape->data.poly.y[0], box)) return 1; } return 0; } #define HOLE_IN_BOX(ps, dia, b) \ ( \ PCB_POINT_IN_BOX((ps)->x - dia/2, (ps)->y - dia/2, (b)) && \ PCB_POINT_IN_BOX((ps)->x + dia/2, (ps)->y + dia/2, (b)) \ ) #define HOLE_TOUCHES_BOX(ps, dia, b) \ PCB_CIRCLE_TOUCHES_BOX((ps)->x, (ps)->y, dia/2, (b)) int pcb_pstk_near_box(pcb_pstk_t *ps, rnd_box_t *box, pcb_layer_t *layer) { pcb_pstk_shape_t *shp; pcb_pstk_tshape_t *tshp = pcb_pstk_get_tshape(ps); /* special case: no-shape padstack is used for hole */ if ((tshp == NULL) || (tshp->len == 0)) { pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); if (proto->hdia <= 0) return 0; return (PCB_IS_BOX_NEGATIVE(box) ? HOLE_TOUCHES_BOX(ps, proto->hdia, box) : HOLE_IN_BOX(ps, proto->hdia, box)); } /* no layer means: "is any shape near?" */ if (layer == NULL) { int n; if (tshp == NULL) return 0; for(n = 0; n < tshp->len; n++) if (pcb_pstk_near_box_(ps, box, &tshp->shape[n]) != 0) return 1; return 0; } /* else check only on the specific layer */ shp = pcb_pstk_shape(ps, pcb_layer_flags_(layer), layer->comb); if (shp == NULL) return 0; return pcb_pstk_near_box_(ps, box, shp); } static int pcb_is_point_in_pstk_(pcb_pstk_t *ps, rnd_coord_t x, rnd_coord_t y, rnd_coord_t radius, pcb_pstk_shape_t *shape) { pcb_any_line_t pad; rnd_vector_t v; switch(shape->shape) { case PCB_PSSH_HSHADOW: return 0; case PCB_PSSH_CIRC: return PCB_POINT_IN_CIRCLE(x, y, ps->x + shape->data.circ.x, ps->y + shape->data.circ.y, shape->data.circ.dia/2 + radius); case PCB_PSSH_LINE: pad.Point1.X = shape->data.line.x1 + ps->x; pad.Point1.Y = shape->data.line.y1 + ps->y; pad.Point2.X = shape->data.line.x2 + ps->x; pad.Point2.Y = shape->data.line.y2 + ps->y; pad.Thickness = shape->data.line.thickness; pad.Clearance = 0; pad.Flags = pcb_flag_make(shape->data.line.square ? PCB_FLAG_SQUARE : 0); return pcb_is_point_in_line(x, y, radius, &pad); case PCB_PSSH_POLY: if (shape->data.poly.pa == NULL) pcb_pstk_shape_update_pa(&shape->data.poly); v[0] = x - ps->x; v[1] = y - ps->y; return rnd_polyarea_contour_inside(shape->data.poly.pa, v); } return 0; } int pcb_is_point_in_pstk(rnd_coord_t x, rnd_coord_t y, rnd_coord_t radius, pcb_pstk_t *ps, pcb_layer_t *layer) { pcb_pstk_shape_t *shp; pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); /* cheap check: hole clicked (also necessary for a mounting hole too) */ if (proto->hdia > 0) { double dist2 = SQR((double)x - (double)ps->x) + SQR((double)y - (double)ps->y); if (dist2 <= SQR((double)radius + (double)proto->hdia/2.0)) return 1; } /* no layer means: "is point in any shape?" */ if (layer == NULL) { int n; pcb_pstk_tshape_t *tshp = pcb_pstk_get_tshape(ps); if (tshp == NULL) return 0; for(n = 0; n < tshp->len; n++) if (pcb_is_point_in_pstk_(ps, x, y, radius, &tshp->shape[n]) != 0) return 1; return 0; } /* else check only on the specific layer */ shp = pcb_pstk_shape(ps, pcb_layer_flags_(layer), layer->comb); if (shp == NULL) return 0; return pcb_is_point_in_pstk_(ps, x, y, radius, shp); } /* Check the minimum distance between a hole's edge and a shape's edge and indicate error if it's smaller than min */ static rnd_bool pcb_pstk_shape_hole_break(pcb_pstk_shape_t *shp, rnd_coord_t hdia, rnd_coord_t min) { double dist, neck, mindist2, dist2; pcb_line_t line; int n; switch(shp->shape) { case PCB_PSSH_HSHADOW: return 0; /* there's no copper that could break */ case PCB_PSSH_CIRC: if (shp->data.circ.dia <= 0) return 0; /* historical: compat padstack loader will create 0 sized copper circles for the clearance side effect */ dist = sqrt((double)shp->data.circ.x*(double)shp->data.circ.x + (double)shp->data.circ.y*(double)shp->data.circ.y); neck = (double)(shp->data.circ.dia - hdia) / 2.0 - dist; break; case PCB_PSSH_LINE: line.Point1.X = shp->data.line.x1; line.Point1.Y = shp->data.line.y1; line.Point2.X = shp->data.line.x2; line.Point2.Y = shp->data.line.y2; line.Thickness = shp->data.line.thickness; dist = sqrt(pcb_point_line_dist2(0, 0, &line)); neck = (double)(shp->data.line.thickness - hdia) / 2.0 - dist; break; case PCB_PSSH_POLY: /* square of the minimal distance required for a neck */ mindist2 = hdia/2 + min; mindist2 *= mindist2; /* cheapest test: if any corner is closer to the hole than min, we are doomed */ for(n = 0; n < shp->data.poly.len; n++) { dist2 = (double)shp->data.poly.x[n] * (double)shp->data.poly.x[n] + (double)shp->data.poly.y[n] * (double)shp->data.poly.y[n]; if (dist2 < mindist2) return 1; } /* more expensive: check each edge */ line.Point1.X = shp->data.poly.x[shp->data.poly.len - 1]; line.Point1.Y = shp->data.poly.y[shp->data.poly.len - 1]; line.Thickness = 1; for(n = 0; n < shp->data.poly.len; n++) { line.Point2.X = shp->data.poly.x[n]; line.Point2.Y = shp->data.poly.y[n]; dist2 = pcb_point_line_dist2(0, 0, &line); if (dist2 < mindist2) return 1; /* shift coords for the next iteration */ line.Point1.X = line.Point2.X; line.Point1.Y = line.Point2.Y; } return 0; /* survived all tests: we are fine! */ } return neck < min; } void pcb_pstk_drc_check_and_warn(pcb_pstk_t *ps, rnd_coord_t *err_minring, rnd_coord_t *err_minhole, rnd_coord_t minring, rnd_coord_t mindrill) { pcb_pstk_proto_t *proto = pcb_pstk_get_proto(ps); if (proto->hdia > 0) { int n; pcb_pstk_tshape_t *ts = pcb_pstk_get_tshape_(ps->parent.data, ps->proto, 0); for(n = 0; n < ts->len; n++) { if (!(ts->shape[n].layer_mask & PCB_LYT_COPPER)) continue; /* only copper shapes can break */ if (pcb_pstk_shape_hole_break(&ts->shape[n], proto->hdia, minring)) { (*err_minring)++; break; } } } TODO("slot: check if slot breaks other shapes") if ((mindrill > 0) && (proto->hdia > 0) && (proto->hdia < mindrill)) *err_minhole = proto->hdia; } /*** undoable mirror ***/ typedef struct { pcb_pstk_t *pstk; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ rnd_coord_t y_offs; int swap_side; int disable_xmirror; } undo_pstk_mirror_t; static int undo_pstk_mirror_swap(void *udata) { undo_pstk_mirror_t *g = udata; pcb_pstk_t *ps = g->pstk; int xmirror = !ps->xmirror, smirror = (g->swap_side ? (!ps->smirror) : ps->smirror); /* change the mirror flag - this will automatically cause mirroring in every aspect */ if (g->disable_xmirror) pcb_pstk_change_instance(ps, NULL, NULL, NULL, NULL, &smirror); else pcb_pstk_change_instance(ps, NULL, NULL, NULL, &xmirror, &smirror); /* if mirror center is not 0, also move, to emulate that the mirror took place around that point */ if (g->y_offs != PCB_PSTK_DONT_MIRROR_COORDS) { pcb_poly_restore_to_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps); pcb_pstk_invalidate_erase(ps); if (ps->parent.data->padstack_tree != NULL) rnd_rtree_delete(ps->parent.data->padstack_tree, ps, (rnd_rtree_box_t *)ps); ps->y = PCB_SWAP_Y(ps->y) + g->y_offs; pcb_pstk_bbox(ps); if (ps->parent.data->padstack_tree != NULL) rnd_rtree_insert(ps->parent.data->padstack_tree, ps, (rnd_rtree_box_t *)ps); pcb_poly_clear_from_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps); pcb_pstk_invalidate_draw(ps); } return 0; } static void undo_pstk_mirror_print(void *udata, char *dst, size_t dst_len) { undo_pstk_mirror_t *g = udata; rnd_snprintf(dst, dst_len, "pstk mirror y_offs=%$mm swap_side=%d disable_xmirror=%d", g->y_offs, g->swap_side, g->disable_xmirror); } static const uundo_oper_t undo_pstk_mirror = { core_pstk_cookie, NULL, undo_pstk_mirror_swap, undo_pstk_mirror_swap, undo_pstk_mirror_print }; void pcb_pstk_mirror(pcb_pstk_t *ps, rnd_coord_t y_offs, int swap_side, int disable_xmirror, rnd_bool undoable) { undo_pstk_mirror_t gtmp, *g = >mp; if (undoable) g = pcb_undo_alloc(pcb_data_get_top(ps->parent.data), &undo_pstk_mirror, sizeof(undo_pstk_mirror_t)); g->pstk = ps; g->y_offs = y_offs; g->swap_side = swap_side; g->disable_xmirror = disable_xmirror; undo_pstk_mirror_swap(g); if (undoable) pcb_undo_inc_serial(); } void pcb_pstk_scale(pcb_pstk_t *ps, double sx, double sy, int undoable) { pcb_pstk_proto_t *prt; int n; if ((sx == 1.0) && (sy == 1.0)) return; pcb_pstk_pre(ps); prt = malloc(sizeof(pcb_pstk_proto_t)); pcb_pstk_proto_copy(prt, pcb_pstk_get_proto(ps)); /* after the copy we have the canonical transformed shape only; scale each shape in it */ for(n = 0; n < prt->tr.array[0].len; n++) pcb_pstk_shape_scale(prt, 0, n, sx, sy, undoable); if (prt->hdia > 0.0) prt->hdia = rnd_round((double)prt->hdia * ((sx+sy)/2.0)); pcb_pstk_proto_update(prt); ps->proto = pcb_pstk_proto_insert_or_free(ps->parent.data, prt, 1, undoable); if (sx != 1.0) ps->x = rnd_round((double)ps->x * sx); if (sy != 1.0) ps->y = rnd_round((double)ps->y * sy); pcb_pstk_post(ps); } unsigned char *pcb_pstk_get_thermal(pcb_pstk_t *ps, unsigned long lid, rnd_bool_t alloc) { if (ps->thermals.used <= lid) { unsigned long oldu = ps->thermals.used; if (!alloc) return NULL; ps->thermals.used = lid+1; ps->thermals.shape = realloc(ps->thermals.shape, ps->thermals.used); memset(ps->thermals.shape + oldu, 0, ps->thermals.used - oldu); } return ps->thermals.shape + lid; } /*** undoable thermal change ***/ typedef struct { pcb_pstk_t *ps; rnd_layer_id_t lid; unsigned char shape; } undo_pstk_thermal_t; static int undo_pstk_thermal_swap(void *udata) { undo_pstk_thermal_t *t = udata; unsigned char old, *th = pcb_pstk_get_thermal(t->ps, t->lid, 1); if (th != NULL) { pcb_board_t *pcb = pcb_data_get_top(t->ps->parent.data); pcb_layer_t *layer = pcb_get_layer(pcb->Data, t->lid); pcb_poly_restore_to_poly(pcb->Data, PCB_OBJ_PSTK, layer, t->ps); old = *th; *th = t->shape; t->shape = old; pcb_poly_clear_from_poly(pcb->Data, PCB_OBJ_PSTK, layer, t->ps); pcb_pstk_invalidate_draw(t->ps); } return 0; } static void undo_pstk_thermal_print(void *udata, char *dst, size_t dst_len) { undo_pstk_thermal_t *t = udata; rnd_snprintf(dst, dst_len, "pstk_thermal: #%ld %ld %d", t->ps->ID, t->lid, t->shape); } static const uundo_oper_t undo_pstk_thermal = { core_pstk_cookie, NULL, /* free */ undo_pstk_thermal_swap, undo_pstk_thermal_swap, undo_pstk_thermal_print }; void pcb_pstk_set_thermal(pcb_pstk_t *ps, unsigned long lid, unsigned char shape, int undoable) { undo_pstk_thermal_t *t; pcb_board_t *pcb = NULL; if (undoable) { assert(ps->parent_type == PCB_PARENT_DATA); pcb = pcb_data_get_top(ps->parent.data); } if (!undoable || (pcb == NULL)) { unsigned char *th = pcb_pstk_get_thermal(ps, lid, 1); if (th != NULL) *th = shape; return; } t = pcb_undo_alloc(pcb, &undo_pstk_thermal, sizeof(undo_pstk_thermal_t)); t->ps = ps; t->lid = lid; t->shape = shape; undo_pstk_thermal_swap(t); } /*** Undoable instance parameter change ***/ typedef struct { long int parent_ID; /* -1 for pcb, positive for a subc */ long int ID; /* ID of the padstack */ rnd_cardinal_t proto; rnd_coord_t clearance; double rot; int xmirror, smirror; } padstack_change_instance_t; #define swap(a,b,type) \ do { \ type tmp = a; \ a = b; \ b = tmp; \ } while(0) pcb_data_t *pcb_pstk_data_hack = NULL; static int undo_change_instance_swap(void *udata) { padstack_change_instance_t *u = udata; pcb_data_t *data; pcb_pstk_t *ps; /* data is either parent subc's data or board's data */ if (u->parent_ID != -1) { pcb_subc_t *subc; int n; /* Look up the parent subc by ID; on the board, in the hack-data and in all buffers */ subc = pcb_subc_by_id(PCB->Data, u->parent_ID); if ((subc == NULL) && (pcb_pstk_data_hack != NULL)) subc = pcb_subc_by_id(pcb_pstk_data_hack, u->parent_ID); for(n = 0; (subc == NULL) && (n < PCB_MAX_BUFFER); n++) subc = pcb_subc_by_id(pcb_buffers[n].Data, u->parent_ID); if (subc == NULL) { rnd_message(RND_MSG_ERROR, "Can't undo padstack change: parent subc #%ld is not found\n", u->parent_ID); return -1; } data = subc->data; } else data = PCB->Data; ps = pcb_pstk_by_id(data, u->ID); if (ps == NULL) { rnd_message(RND_MSG_ERROR, "Can't undo padstack change: padstack ID #%ld is not available\n", u->ID); return -1; } pcb_poly_restore_to_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps); pcb_pstk_invalidate_erase(ps); if (ps->parent.data->padstack_tree != NULL) rnd_rtree_delete(ps->parent.data->padstack_tree, ps, (rnd_rtree_box_t *)ps); swap(ps->proto, u->proto, rnd_cardinal_t); swap(ps->Clearance, u->clearance, rnd_coord_t); swap(ps->rot, u->rot, double); swap(ps->xmirror, u->xmirror, int); swap(ps->smirror, u->smirror, int); /* force re-render the prototype */ ps->protoi = -1; pcb_pstk_get_tshape(ps); pcb_pstk_bbox(ps); if (ps->parent.data->padstack_tree != NULL) rnd_rtree_insert(ps->parent.data->padstack_tree, ps, (rnd_rtree_box_t *)ps); pcb_poly_clear_from_poly(ps->parent.data, PCB_OBJ_PSTK, NULL, ps); pcb_pstk_invalidate_draw(ps); return 0; } static void undo_change_instance_print(void *udata, char *dst, size_t dst_len) { padstack_change_instance_t *u = udata; rnd_snprintf(dst, dst_len, "padstack change: clearance=%$mm rot=%.2f xmirror=%d smirror=%d\n", u->clearance, u->rot, u->xmirror, u->smirror); } static const uundo_oper_t undo_pstk_change_instance = { core_pstk_cookie, NULL, /* free */ undo_change_instance_swap, undo_change_instance_swap, undo_change_instance_print }; int pcb_pstk_change_instance(pcb_pstk_t *ps, rnd_cardinal_t *proto, const rnd_coord_t *clearance, double *rot, int *xmirror, int *smirror) { padstack_change_instance_t *u; long int parent_ID; pcb_opctx_t ctx; if ((proto != NULL) && ((*proto < 0) || (*proto >= ps->parent.data->ps_protos.used))) return -1; if (ps->ID <= 0) { /* off-board padstack: undo and clipping disabled */ if (proto != NULL) ps->proto = *proto; if (clearance != NULL) ps->Clearance = *clearance; if (rot != NULL) ps->rot = *rot; if (xmirror != NULL) ps->xmirror = *xmirror; if (smirror != NULL) ps->xmirror = *smirror; pcb_pstk_bbox(ps); return 0; } switch(ps->parent.data->parent_type) { case PCB_PARENT_INVALID: /* happens if parent is a buffer: do not undo operations done on the buffer */ ps->proto = proto ? *proto : ps->proto; ps->Clearance = clearance ? *clearance : ps->Clearance; ps->rot = rot ? *rot : ps->rot; ps->xmirror = xmirror ? *xmirror : ps->xmirror; ps->smirror = smirror ? *smirror : ps->smirror; /* re-render the prototype so the new shape is generated and cached */ ps->protoi = -1; pcb_pstk_get_tshape(ps); return 0; case PCB_PARENT_BOARD: parent_ID = -1; break; case PCB_PARENT_SUBC: parent_ID = ps->parent.data->parent.subc->ID; break; default: return -1; } ctx.clip.clear = 0; ctx.clip.restore = 1; pcb_pstkop_clip(&ctx, ps); u = pcb_undo_alloc(PCB, &undo_pstk_change_instance, sizeof(padstack_change_instance_t)); u->parent_ID = parent_ID; u->ID = ps->ID; u->proto = proto ? *proto : ps->proto; u->clearance = clearance ? *clearance : ps->Clearance; u->rot = rot ? *rot : ps->rot; u->xmirror = xmirror ? *xmirror : ps->xmirror; u->smirror = smirror ? *smirror : ps->smirror; pcb_pstk_bbox(ps); ctx.clip.clear = 1; ctx.clip.restore = 0; pcb_pstkop_clip(&ctx, ps); undo_change_instance_swap(u); pcb_undo_inc_serial(); return 0; } rnd_bool pcb_pstk_is_group_empty(pcb_board_t *pcb, rnd_layergrp_id_t gid) { pcb_layergrp_t *g = &pcb->LayerGroups.grp[gid]; PCB_PADSTACK_LOOP(pcb->Data); { rnd_cardinal_t n; for(n = 0; n < g->len; n++) if (pcb_pstk_shape_at(pcb, padstack, &pcb->Data->Layer[g->lid[n]])) return rnd_false; } PCB_END_LOOP; return rnd_true; } #include "obj_pstk_op.c" void pcb_pstk_rotate90(pcb_pstk_t *pstk, rnd_coord_t cx, rnd_coord_t cy, int steps) { pcb_opctx_t ctx; ctx.rotate.center_x = cx; ctx.rotate.center_y = cy; ctx.rotate.number = steps; pcb_pstkop_rotate90(&ctx, pstk); } void pcb_pstk_rotate(pcb_pstk_t *pstk, rnd_coord_t cx, rnd_coord_t cy, double cosa, double sina, double angle) { pcb_opctx_t ctx; ctx.rotate.center_x = cx; ctx.rotate.center_y = cy; ctx.rotate.angle = angle; ctx.rotate.cosa = cosa; ctx.rotate.sina = sina; pcb_pstkop_rotate_(&ctx, pstk, 0); } rnd_coord_t obj_pstk_get_clearance(pcb_board_t *pcb, pcb_pstk_t *ps, pcb_layer_t *layer) { pcb_pstk_shape_t *shp; if (!PCB_FLAG_TEST(PCB_FLAG_CLEARLINE, ps)) return 0; if (ps->Clearance > 0) /* global clarance overrides */ return ps->Clearance; shp = pcb_pstk_shape_at(pcb, ps, layer); if (shp == NULL) return 0; return shp->clearance; } void pcb_pstk_pre(pcb_pstk_t *pstk) { pcb_data_t *data = pstk->parent.data; if (data->padstack_tree != NULL) rnd_rtree_delete(data->padstack_tree, pstk, (rnd_rtree_box_t *)pstk); pcb_poly_restore_to_poly(data, PCB_OBJ_PSTK, NULL, pstk); } void pcb_pstk_post(pcb_pstk_t *pstk) { pcb_data_t *data = pstk->parent.data; if (data->padstack_tree != NULL) rnd_rtree_insert(data->padstack_tree, pstk, (rnd_rtree_box_t *)pstk); pcb_poly_clear_from_poly(data, PCB_OBJ_PSTK, NULL, pstk); } rnd_layer_id_t pcb_proto_board_layer_for(const pcb_data_t *data, pcb_layer_type_t mask, pcb_layer_combining_t comb) { rnd_layer_id_t lid; const pcb_layer_t *ly; for(lid = 0, ly = data->Layer; lid < data->LayerN; lid++,ly++) { pcb_layer_type_t typ = pcb_layer_flags_(ly); if ((typ == mask) && (ly->comb == comb)) return lid; } return -1; } rnd_coord_t pcb_pstk_pen_dia(pcb_board_t *pcb) { rnd_box_t bb; pcb_pstk_proto_t *proto = pcb_pstk_get_proto_(pcb->Data, conf_core.design.via_proto); if (proto == NULL) return 0; pcb_pstk_bbox_ts(&bb, proto, &proto->tr.array[0], 0, 0, 0, 0); return RND_MAX(bb.X2 - bb.X1, bb.Y2 - bb.Y1); } rnd_coord_t pcb_pstk_pen_hole_dia(pcb_board_t *pcb) { pcb_pstk_proto_t *proto = pcb_pstk_get_proto_(pcb->Data, conf_core.design.via_proto); if (proto == NULL) return 0; return proto->hdia; }