236 lines
7.2 KiB
Python
Executable File
236 lines
7.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright 2019 Maurice https://github.com/easyw/
|
|
# Copyright 2020 Matt Huszagh https://github.com/matthuszagh
|
|
|
|
# GNU GENERAL PUBLIC LICENSE
|
|
# Version 3, 29 June 2007
|
|
#
|
|
# Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
# Everyone is permitted to copy and distribute verbatim copies
|
|
# of this license document, but changing it is not allowed.
|
|
#
|
|
# 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.
|
|
#
|
|
# 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.
|
|
|
|
import os
|
|
import pcbnew
|
|
import wx
|
|
# import numpy as np
|
|
from . import TraceClearanceDlg
|
|
import math
|
|
|
|
class TraceClearance_Dlg(TraceClearanceDlg.TraceClearanceDlg):
|
|
"""
|
|
"""
|
|
|
|
def SetSizeHints(self, sz1, sz2):
|
|
if wx.__version__ < '4.0':
|
|
self.SetSizeHintsSz(sz1, sz2)
|
|
else:
|
|
super(TraceClearance_Dlg, self).SetSizeHints(sz1, sz2)
|
|
|
|
def __init__(self, parent):
|
|
"""
|
|
"""
|
|
TraceClearanceDlg.TraceClearanceDlg.__init__(self, parent)
|
|
self.SetMinSize(self.GetSize())
|
|
|
|
|
|
class TraceClearance(pcbnew.ActionPlugin):
|
|
"""
|
|
"""
|
|
|
|
def defaults(self):
|
|
"""
|
|
"""
|
|
self.name = "Trace Clearance Generator\n version 1.4"
|
|
self.category = ""
|
|
self.description = (
|
|
"Generate a copper pour keepout for a selected trace."
|
|
)
|
|
self.show_toolbar_button = True
|
|
self.icon_file_name = os.path.join(
|
|
os.path.dirname(__file__), "./trace_clearance.png"
|
|
)
|
|
|
|
def Run(self):
|
|
"""
|
|
"""
|
|
_pcbnew_frame = [x for x in wx.GetTopLevelWindows() if x.GetName() == 'PcbFrame'][0]
|
|
# _pcbnew_frame = [
|
|
# x
|
|
# for x in wx.GetTopLevelWindows()
|
|
# if x.GetTitle().lower().startswith("pcbnew")
|
|
# ][0]
|
|
wx_params = TraceClearance_Dlg(_pcbnew_frame)
|
|
wx_params.m_clearance.SetValue("0.2")
|
|
wx_params.m_bitmap.SetBitmap(
|
|
wx.Bitmap(
|
|
os.path.join(
|
|
os.path.dirname(os.path.realpath(__file__)),
|
|
"trace_clearance_dialog.png",
|
|
)
|
|
)
|
|
)
|
|
modal_res = wx_params.ShowModal()
|
|
clearance = pcbnew.FromMM(
|
|
self.InputValid(wx_params.m_clearance.GetValue())
|
|
)
|
|
if clearance is not None:
|
|
pcb = pcbnew.GetBoard()
|
|
if modal_res == wx.ID_OK:
|
|
tracks = selected_tracks(pcb)
|
|
if len(tracks) > 0:
|
|
set_keepouts(pcb, tracks, clearance)
|
|
else:
|
|
self.Warn("At least one track must be selected.")
|
|
elif modal_res == wx.ID_CANCEL:
|
|
wx_params.Destroy()
|
|
|
|
def Warn(self, message, caption="Warning!"):
|
|
"""
|
|
"""
|
|
dlg = wx.MessageDialog(None, message, caption, wx.OK | wx.ICON_WARNING)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
|
|
def InputValid(self, value):
|
|
"""
|
|
"""
|
|
try:
|
|
float_val = float(value)
|
|
except:
|
|
self.Warn("Clearance must be a floating point number.")
|
|
|
|
if float_val <= 0:
|
|
self.Warn("Clearance must be positive.")
|
|
|
|
return float_val
|
|
|
|
|
|
def selected_tracks(pcb):
|
|
"""
|
|
TODO should we use a common import with solder expander to avoid
|
|
redundant functionality?
|
|
"""
|
|
tracks = []
|
|
if hasattr(pcbnew,'TRACK'):
|
|
track_item = pcbnew.TRACK
|
|
else:
|
|
track_item = pcbnew.PCB_TRACK
|
|
for item in pcb.GetTracks():
|
|
if type(item) is track_item and item.IsSelected():
|
|
tracks.append(item)
|
|
return tracks
|
|
|
|
|
|
def set_keepouts(pcb, tracks, clearance):
|
|
"""
|
|
"""
|
|
for track in tracks:
|
|
track_start = track.GetStart()
|
|
track_end = track.GetEnd()
|
|
if track_start.x == track_end.x and track_start.y == track_end.y:
|
|
continue
|
|
track_width = track.GetWidth()
|
|
layer = track.GetLayerSet()
|
|
|
|
if hasattr(pcbnew,'ZONE_CONTAINER'):
|
|
keepout = pcbnew.ZONE_CONTAINER(pcb)
|
|
pts = poly_points(track_start, track_end, track_width, clearance)
|
|
keepout.AddPolygon(pts)
|
|
keepout.SetIsKeepout(True)
|
|
keepout.SetDoNotAllowCopperPour(True)
|
|
keepout.SetDoNotAllowVias(False)
|
|
keepout.SetDoNotAllowTracks(False)
|
|
keepout.SetLayerSet(layer)
|
|
else:
|
|
keepout = pcbnew.ZONE(pcb)
|
|
pts = poly_points(track_start, track_end, track_width, clearance)
|
|
# wx.LogMessage(str(pts))
|
|
keepout.AddPolygon(pts)
|
|
#keepout.SetIsKeepout(True)
|
|
keepout.SetIsRuleArea(True) # was SetIsKeepout
|
|
keepout.SetDoNotAllowCopperPour(True)
|
|
keepout.SetDoNotAllowVias(False)
|
|
keepout.SetDoNotAllowTracks(False)
|
|
keepout.SetLayerSet(layer)
|
|
pcb.Add(keepout)
|
|
|
|
pcbnew.Refresh()
|
|
|
|
|
|
def poly_points(track_start, track_end, track_width, clearance):
|
|
"""
|
|
"""
|
|
delta = track_width / 2 + clearance
|
|
dx = track_end.x - track_start.x
|
|
dy = track_end.y - track_start.y
|
|
# theta = np.arctan2(dy, dx)
|
|
theta = math.atan2(dy, dx)
|
|
# len = np.sqrt(np.power(dx, 2) + np.power(dy, 2))
|
|
len = math.sqrt(math.pow(dx, 2) + math.pow(dy, 2))
|
|
dx_norm = dx / len
|
|
dy_norm = dy / len
|
|
|
|
delta_x = delta * -dy_norm
|
|
delta_y = delta * dx_norm
|
|
pt_delta = pcbnew.wxPoint(delta_x, delta_y)
|
|
|
|
pts = []
|
|
pts.append(track_start + pt_delta)
|
|
for pt in semicircle_points(track_start, delta, theta, True):
|
|
pts.append(pt)
|
|
pts.append(track_start - pt_delta)
|
|
pts.append(track_end - pt_delta)
|
|
for pt in semicircle_points(track_end, delta, theta, False):
|
|
pts.append(pt)
|
|
pts.append(track_end + pt_delta)
|
|
return pcbnew.wxPoint_Vector(pts)
|
|
|
|
|
|
def semicircle_points(circle_center, radius, angle_norm, is_start=True):
|
|
"""
|
|
"""
|
|
num_points = 20
|
|
|
|
# angles = np.linspace(
|
|
# angle_norm + np.pi / 2, angle_norm + 3 * np.pi / 2, num_points + 2
|
|
# )
|
|
start = angle_norm + math.pi / 2
|
|
stop = angle_norm + 3 * math.pi / 2
|
|
num_vals = num_points
|
|
delta = (stop-start)/(num_vals-1)
|
|
evenly_spaced = [start + i * delta for i in range(num_vals)]
|
|
# print(evenly_spaced)
|
|
angles = evenly_spaced
|
|
# wx.LogMessage(str(angles))
|
|
angles = angles[1:-1]
|
|
# wx.LogMessage(str(angles)+'1')
|
|
if not is_start:
|
|
# angles = np.add(angles, np.pi)
|
|
angles.append(math.pi)
|
|
pts = []
|
|
for ang in angles:
|
|
# pts.append(
|
|
# circle_center
|
|
# + pcbnew.wxPoint(radius * np.cos(ang), radius * np.sin(ang))
|
|
# )
|
|
pts.append(
|
|
circle_center
|
|
+ pcbnew.wxPoint(radius * math.cos(ang), radius * math.sin(ang))
|
|
)
|
|
return pcbnew.wxPoint_Vector(pts)
|