536 lines
23 KiB
Python
536 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
|
# place_footprints.py
|
|
#
|
|
# Copyright (C) 2022 Mitja Nemec
|
|
#
|
|
# 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.
|
|
#
|
|
#
|
|
import pcbnew
|
|
from collections import namedtuple
|
|
import os
|
|
import logging
|
|
import itertools
|
|
import math
|
|
|
|
|
|
SCALE = 1000000.0
|
|
|
|
Footprint = namedtuple('Footprint', ['ref', 'fp', 'fp_id', 'sheet_id', 'filename'])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def rotate_around_center(coordinates, angle):
|
|
""" rotate coordinates for a defined angle in degrees around coordinate center"""
|
|
new_x = coordinates[0] * math.cos(2 * math.pi * angle/360)\
|
|
- coordinates[1] * math.sin(2 * math.pi * angle/360)
|
|
new_y = coordinates[0] * math.sin(2 * math.pi * angle/360)\
|
|
+ coordinates[1] * math.cos(2 * math.pi * angle/360)
|
|
return new_x, new_y
|
|
|
|
|
|
def rotate_around_point(old_position, point, angle):
|
|
""" rotate coordinates for a defined angle in degrees around a point """
|
|
# get relative position to point
|
|
rel_x = old_position[0] - point[0]
|
|
rel_y = old_position[1] - point[1]
|
|
# rotate around
|
|
new_rel_x, new_rel_y = rotate_around_center((rel_x, rel_y), angle)
|
|
# get absolute position
|
|
new_position = (new_rel_x + point[0], new_rel_y + point[1])
|
|
return new_position
|
|
|
|
|
|
def get_index_of_tuple(list_of_tuples, index, value):
|
|
for pos, t in enumerate(list_of_tuples):
|
|
if t[index] == value:
|
|
return pos
|
|
|
|
|
|
class Placer:
|
|
@staticmethod
|
|
def get_footprint_id(footprint):
|
|
path = footprint.GetPath().AsString().upper().replace('00000000-0000-0000-0000-0000', '').split("/")
|
|
if len(path) != 1:
|
|
fp_id = path[-1]
|
|
# if path is empty, then footprint is not part of schematics
|
|
else:
|
|
fp_id = None
|
|
return fp_id
|
|
|
|
@staticmethod
|
|
def get_sheet_id(footprint):
|
|
path = footprint.GetPath().AsString().upper().replace('00000000-0000-0000-0000-0000', '').split("/")
|
|
if len(path) != 1:
|
|
sheet_id = path[-2]
|
|
# if path is empty, then footprint is not part of schematics
|
|
else:
|
|
sheet_id = None
|
|
return sheet_id
|
|
|
|
def get_sheet_path(self, footprint):
|
|
""" get sheet id """
|
|
path = footprint.GetPath().AsString().upper().replace('00000000-0000-0000-0000-0000', '').split("/")
|
|
if len(path) != 1:
|
|
sheet_path = path[0:-1]
|
|
sheet_names = [self.dict_of_sheets[x][0] for x in sheet_path if x in self.dict_of_sheets]
|
|
sheet_files = [self.dict_of_sheets[x][1] for x in sheet_path if x in self.dict_of_sheets]
|
|
sheet_path = [sheet_names, sheet_files]
|
|
else:
|
|
sheet_path = ["", ""]
|
|
return sheet_path
|
|
|
|
def get_fp_by_ref(self, ref):
|
|
for fp in self.footprints:
|
|
if fp.ref == ref:
|
|
return fp
|
|
return None
|
|
|
|
def get_footprints_with_reference_designator(self, ref_des):
|
|
list_of_footprints = []
|
|
for fp in self.footprints:
|
|
index = 0
|
|
for i in range(len(fp.ref)):
|
|
if not fp.ref[i].isdigit():
|
|
index = i+1
|
|
fp_des = fp.ref[:index]
|
|
if fp_des == ref_des:
|
|
list_of_footprints.append(fp.ref)
|
|
return list_of_footprints
|
|
|
|
def __init__(self, board):
|
|
self.board = board
|
|
self.pcb_filename = os.path.abspath(board.GetFileName())
|
|
self.sch_filename = self.pcb_filename.replace(".kicad_pcb", ".kicad_sch")
|
|
self.project_folder = os.path.dirname(self.pcb_filename)
|
|
|
|
# construct a list of footprints with all pertinent data
|
|
logger.info('getting a list of all footprints on board')
|
|
footprints = board.GetFootprints()
|
|
self.footprints = []
|
|
|
|
# get dict_of_sheets from layout data only (through footprint Sheetfile and Sheetname properties)
|
|
self.dict_of_sheets = {}
|
|
unique_sheet_ids = set()
|
|
for fp in footprints:
|
|
# construct a set of unique sheets from footprint properties
|
|
path = fp.GetPath().AsString().upper().replace('00000000-0000-0000-0000-0000', '').split("/")
|
|
sheet_path = path[0:-1]
|
|
for x in sheet_path:
|
|
unique_sheet_ids.add(x)
|
|
|
|
sheet_id = self.get_sheet_id(fp)
|
|
try:
|
|
sheet_file = fp.GetSheetfile()
|
|
sheet_name = fp.GetSheetname()
|
|
except KeyError:
|
|
logger.info("Footprint " + fp.GetReference() +
|
|
" does not have Sheetfile property, it will not be considered for placement."
|
|
" Most likely it is only in layout")
|
|
continue
|
|
# footprint is in the schematics and has Sheetfile property
|
|
if sheet_file and sheet_id:
|
|
# strip prepending "File: " if existing
|
|
self.dict_of_sheets[sheet_id] = [sheet_name, sheet_file]
|
|
# footprint is in the schematics but has no Sheetfile properties
|
|
elif sheet_id:
|
|
logger.info("Footprint " + fp.GetReference() + " does not have Sheetfile property")
|
|
raise LookupError("Footprint " + str(
|
|
fp.GetReference()) + " doesn't have Sheetfile and Sheetname properties. "
|
|
"You need to update the layout from schematics")
|
|
# footprint is on root level
|
|
else:
|
|
logger.info("Footprint " + fp.GetReference() + " on root level")
|
|
|
|
# catch corner cases with nested hierarchy, where some hierarchical pages don't have any footprints
|
|
unique_sheet_ids.remove("")
|
|
if len(unique_sheet_ids) > len(self.dict_of_sheets):
|
|
# open root schematics file and parse for other schematics files
|
|
# This might be prone to errors regarding path discovery
|
|
# thus it is used only in corner cases
|
|
schematic_found = {}
|
|
self.parse_schematic_files(self.sch_filename, schematic_found)
|
|
self.dict_of_sheets = schematic_found
|
|
|
|
for fp in footprints:
|
|
try:
|
|
sheet_file = fp.GetSheetfile()
|
|
# construct a list of all the footprints
|
|
mod_named_tuple = Footprint(fp=fp,
|
|
fp_id=self.get_footprint_id(fp),
|
|
sheet_id=self.get_sheet_path(fp)[0],
|
|
filename=self.get_sheet_path(fp)[1],
|
|
ref=fp.GetReference())
|
|
self.footprints.append(mod_named_tuple)
|
|
except KeyError:
|
|
pass
|
|
pass
|
|
|
|
def parse_schematic_files(self, filename, dict_of_sheets):
|
|
filename_dir = os.path.dirname(filename)
|
|
|
|
with open(filename, encoding='utf-8') as f:
|
|
contents = f.read()
|
|
|
|
indexes = []
|
|
level = []
|
|
sheet_definitions = []
|
|
new_lines = []
|
|
lvl = 0
|
|
# get the nesting levels at index
|
|
for idx in range(len(contents) - 20):
|
|
if contents[idx] == "(":
|
|
lvl = lvl + 1
|
|
level.append(lvl)
|
|
indexes.append(idx)
|
|
if contents[idx] == ")":
|
|
lvl = lvl - 1
|
|
level.append(lvl)
|
|
indexes.append(idx)
|
|
if contents[idx] == "\n":
|
|
new_lines.append(idx)
|
|
a = contents[idx:idx + 20]
|
|
if a.startswith("(sheet\n") or a.startswith("(sheet "):
|
|
sheet_definitions.append(idx)
|
|
|
|
start_idx = sheet_definitions
|
|
end_idx = sheet_definitions[1:]
|
|
end_idx.append(len(contents))
|
|
braces = list(zip(indexes, level))
|
|
# parse individual sheet definitions (if any)
|
|
for start, end in zip(start_idx, end_idx):
|
|
def next_bigger(l, v):
|
|
for m in l:
|
|
if m > v:
|
|
return m
|
|
|
|
uuid_loc = contents[start:end].find('(uuid') + start
|
|
uuid_loc_end = next_bigger(new_lines, uuid_loc)
|
|
uuid_complete_string = contents[uuid_loc:uuid_loc_end]
|
|
uuid = uuid_complete_string.strip("(uuid").strip(")").replace("\"", '').upper().lstrip()
|
|
|
|
v8encoding = contents[start:end].find('(property "Sheetname\"')
|
|
v7encoding = contents[start:end].find('(property "Sheet name\"')
|
|
if v8encoding != -1:
|
|
offset = v8encoding
|
|
elif v7encoding != -1:
|
|
offset = v7encoding
|
|
else:
|
|
logger.info(f'Did not found sheetname properties in the schematic file '
|
|
f'in {filename} line:{str(i)}')
|
|
raise LookupError(f'Did not found sheetname properties in the schematic file '
|
|
f'in {filename} line:{str(i)}. Unsupported schematics file format')
|
|
sheetname_loc = offset + start
|
|
sheetname_loc_end = next_bigger(new_lines, sheetname_loc)
|
|
sheetname_complete_string = contents[sheetname_loc:sheetname_loc_end]
|
|
sheetname = sheetname_complete_string.strip("(property").split('"')[1::2][1]
|
|
|
|
v8encoding = contents[start:end].find('(property "Sheetfile\"')
|
|
v7encoding = contents[start:end].find('(property "Sheet file\"')
|
|
if v8encoding != -1:
|
|
offset = v8encoding
|
|
elif v7encoding != -1:
|
|
offset = v7encoding
|
|
else:
|
|
logger.info(f'Did not found sheetfile properties in the schematic file '
|
|
f'in {filename}.')
|
|
raise LookupError(f'Did not found sheetfile properties in the schematic file '
|
|
f'in {filename}. Unsupported schematics file format')
|
|
sheetfile_loc = offset + start
|
|
sheetfile_loc_end = next_bigger(new_lines, sheetfile_loc)
|
|
sheetfile_complete_string = contents[sheetfile_loc:sheetfile_loc_end]
|
|
sheetfile = sheetfile_complete_string.strip("(property").split('"')[1::2][1]
|
|
|
|
sheetfilepath = os.path.join(filename_dir, sheetfile)
|
|
dict_of_sheets[uuid] = [sheetname, sheetfile]
|
|
|
|
# test if newfound file can be opened
|
|
if not os.path.exists(sheetfilepath):
|
|
raise LookupError(f'File {sheetfilepath} does not exists. This is either due to error in parsing'
|
|
f' schematics files, missing schematics file or an error within the schematics')
|
|
# open a newfound file and look for nested sheets
|
|
self.parse_schematic_files(sheetfilepath, dict_of_sheets)
|
|
pass
|
|
return
|
|
|
|
def get_list_of_footprints_with_same_id(self, fp_id):
|
|
footprints_with_same_id = []
|
|
for fp in self.footprints:
|
|
if fp.fp_id == fp_id:
|
|
footprints_with_same_id.append(fp)
|
|
return footprints_with_same_id
|
|
|
|
def get_sheets_to_place(self, reference_footprint, level):
|
|
sheet_id = reference_footprint.sheet_id
|
|
level_index = sheet_id.index(level)
|
|
sheet_depth = len(sheet_id)
|
|
|
|
sheet_file = reference_footprint.filename
|
|
# find level_id
|
|
level_file = sheet_file[sheet_id.index(level)]
|
|
logger.info('constructing a list of sheets suitable for replication on level:'
|
|
+ repr(level) + ", file:" + repr(level_file))
|
|
|
|
up_to_level_file = sheet_file[:level_index+1]
|
|
|
|
# get all footprints with same ID
|
|
footprints_with_same_id = self.get_list_of_footprints_with_same_id(reference_footprint.fp_id)
|
|
|
|
sheets_up_to_same_level = []
|
|
for fp in footprints_with_same_id:
|
|
# match only if the filepath matches and all but the level sheetnames match
|
|
if len(fp.sheet_id) == len(sheet_id):
|
|
fp_id_level = "/".join(fp.sheet_id[:level_index] + fp.sheet_id[level_index+1:])
|
|
ref_fp_id_level = "/".join(sheet_id[:level_index] + sheet_id[level_index+1:])
|
|
if fp.filename[:level_index+1] == up_to_level_file and fp_id_level == ref_fp_id_level:
|
|
sheets_up_to_same_level.append(fp.sheet_id)
|
|
|
|
# sort
|
|
sheets_up_to_same_level.sort(key=lambda item: (len("".join(item)), item))
|
|
|
|
# remove the sheet path for reference footprint
|
|
if sheet_id in sheets_up_to_same_level:
|
|
index = sheets_up_to_same_level.index(sheet_id)
|
|
del sheets_up_to_same_level[index]
|
|
logger.info("suitable sheets are:"+repr(sheets_up_to_same_level))
|
|
return sheets_up_to_same_level
|
|
|
|
def get_footprints_on_sheet(self, level):
|
|
footprints_on_sheet = []
|
|
level_depth = len(level)
|
|
for fp in self.footprints:
|
|
if level == fp.sheet_id[0:level_depth]:
|
|
footprints_on_sheet.append(fp)
|
|
return footprints_on_sheet
|
|
|
|
def get_footprints_not_on_sheet(self, level):
|
|
footprints_not_on_sheet = []
|
|
level_depth = len(level)
|
|
for fp in self.footprints:
|
|
if level != fp.sheet_id[0:level_depth]:
|
|
footprints_not_on_sheet.append(fp)
|
|
return footprints_not_on_sheet
|
|
|
|
@staticmethod
|
|
def get_footprints_bounding_box(footprints):
|
|
# get the first bounding box
|
|
bounding_box = footprints[0].fp.GetBoundingBox()
|
|
top = bounding_box.GetTop()
|
|
bottom = bounding_box.GetBottom()
|
|
left = bounding_box.GetLeft()
|
|
right = bounding_box.GetRight()
|
|
# iterate throught the rest of the footprints
|
|
# and resize the bounding box accordingly
|
|
for fp in footprints:
|
|
fp_box = fp.fp.GetBoundingBox()
|
|
top = min(top, fp_box.GetTop())
|
|
bottom = max(bottom, fp_box.GetBottom())
|
|
left = min(left, fp_box.GetLeft())
|
|
right = max(right, fp_box.GetRight())
|
|
return top, bottom, left, right
|
|
|
|
def get_footprints_bounding_box_size(self, footprints):
|
|
top, bottom, left, right = self.get_footprints_bounding_box(footprints)
|
|
height = (bottom-top)/1000000.0
|
|
width = (right-left)/1000000.0
|
|
return height, width
|
|
|
|
def get_footprints_bounding_box_center(self, footprints):
|
|
top, bottom, left, right = self.get_footprints_bounding_box(footprints)
|
|
pos_y = (bottom+top)/2
|
|
pos_x = (right+left)/2
|
|
return pos_x, pos_y
|
|
|
|
def place_circular(self, footprints_to_place, reference_footprint, radius, delta_angle, delta_radius,
|
|
step, rotation, copy_text_items):
|
|
logger.info("Starting placing with circular layout")
|
|
# get proper footprint list
|
|
footprints = []
|
|
for fp in footprints_to_place:
|
|
footprints.append(self.get_fp_by_ref(fp))
|
|
|
|
ref_fp = self.get_fp_by_ref(reference_footprint)
|
|
|
|
# get first footprint position
|
|
ref_fp_pos = ref_fp.fp.GetPosition()
|
|
logger.info("reference footprint position at: " + repr(ref_fp_pos))
|
|
ref_fp_index = footprints.index(ref_fp)
|
|
|
|
point_of_rotation = (ref_fp_pos[0], ref_fp_pos[1] + radius * SCALE)
|
|
|
|
logger.info("rotation center at: " + repr(point_of_rotation))
|
|
for fp in footprints:
|
|
index = footprints.index(fp)
|
|
delta_index = index - ref_fp_index
|
|
|
|
if fp.fp.IsFlipped() != ref_fp.fp.IsFlipped():
|
|
fp.fp.Flip(fp.fp.GetPosition(), False)
|
|
|
|
circular_position = rotate_around_point(ref_fp_pos, point_of_rotation, delta_index * delta_angle)
|
|
# add delta radius for spirals
|
|
radial_delta = rotate_around_point([0.0, -pcbnew.FromMM(delta_radius*delta_index)], [0.0, 0.0], delta_index * delta_angle)
|
|
new_position = [sum(i) for i in zip(circular_position, radial_delta)]
|
|
new_position = [int(x) for x in new_position]
|
|
fp.fp.SetPosition(pcbnew.VECTOR2I(*new_position))
|
|
footprint_angle = ref_fp.fp.GetOrientationDegrees()-delta_index*delta_angle
|
|
footprint_angle = footprint_angle + index // step * rotation
|
|
fp.fp.SetOrientationDegrees(footprint_angle)
|
|
|
|
if copy_text_items:
|
|
self.replicate_fp_text_items(ref_fp, fp)
|
|
|
|
def place_linear(self, footprints_to_place, reference_footprint, step_x, step_y, step, rotation, copy_text_items):
|
|
logger.info("Starting placing with linear layout")
|
|
# get proper footprint list
|
|
footprints = []
|
|
for fp in footprints_to_place:
|
|
footprints.append(self.get_fp_by_ref(fp))
|
|
|
|
ref_fp = self.get_fp_by_ref(reference_footprint)
|
|
|
|
# get reference footprint position
|
|
ref_fp_pos = ref_fp.fp.GetPosition()
|
|
ref_fp_index = footprints.index(ref_fp)
|
|
|
|
for fp in footprints:
|
|
index = footprints.index(fp)
|
|
delta_index = index-ref_fp_index
|
|
|
|
if fp.fp.IsFlipped() != ref_fp.fp.IsFlipped():
|
|
fp.fp.Flip(fp.fp.GetPosition(), False)
|
|
|
|
new_position = (ref_fp_pos.x + delta_index*step_x*SCALE, ref_fp_pos.y + delta_index*step_y * SCALE)
|
|
new_position = [int(x) for x in new_position]
|
|
fp.fp.SetPosition(pcbnew.VECTOR2I(*new_position))
|
|
footprint_angle = ref_fp.fp.GetOrientationDegrees()
|
|
footprint_angle = footprint_angle + index // step * rotation
|
|
fp.fp.SetOrientationDegrees(footprint_angle)
|
|
|
|
if copy_text_items:
|
|
self.replicate_fp_text_items(ref_fp, fp)
|
|
|
|
def place_matrix(self, footprints_to_place, reference_footprint, step_x, step_y, nr_columns, step, rotation,
|
|
copy_text_items):
|
|
logger.info("Starting placing with matrix layout")
|
|
# get proper footprint list
|
|
footprints = []
|
|
for fp in footprints_to_place:
|
|
footprints.append(self.get_fp_by_ref(fp))
|
|
|
|
ref_fp = self.get_fp_by_ref(reference_footprint)
|
|
# get reference footprint position
|
|
ref_fp_pos = ref_fp.fp.GetPosition()
|
|
ref_fp_index = footprints.index(ref_fp)
|
|
|
|
for fp in reversed(footprints[:ref_fp_index]):
|
|
if fp.ref == "R201":
|
|
# by sheet: orientation of fp, flip of silk and fab text
|
|
# by ref: orientation of fp, position and flip of silk and fab text
|
|
a = 2
|
|
if fp.ref == "R301":
|
|
# by sheet: position and flip of silk and fab text
|
|
a = 2
|
|
if fp.fp.IsFlipped() != ref_fp.fp.IsFlipped():
|
|
fp.fp.Flip(fp.fp.GetPosition(), False)
|
|
|
|
index = footprints.index(fp) - ref_fp_index
|
|
row = index // nr_columns
|
|
column = index - row * nr_columns
|
|
new_pos_x = ref_fp_pos.x + column * step_x * SCALE
|
|
new_pos_y = ref_fp_pos.y + row * step_y * SCALE
|
|
new_position = (new_pos_x, new_pos_y)
|
|
new_position = [int(x) for x in new_position]
|
|
fp.fp.SetPosition(pcbnew.VECTOR2I(*new_position))
|
|
footprint_angle = ref_fp.fp.GetOrientationDegrees()
|
|
footprint_angle = footprint_angle + index // step * rotation
|
|
fp.fp.SetOrientationDegrees(footprint_angle)
|
|
|
|
if copy_text_items:
|
|
self.replicate_fp_text_items(ref_fp, fp)
|
|
|
|
for fp in footprints[ref_fp_index+1:]:
|
|
if fp.ref == "R201":
|
|
# by sheet: orientation of fp, flip of silk and fab text
|
|
# by ref: orientation of fp, position and flip of silk and fab text
|
|
a = 2
|
|
if fp.ref == "R301":
|
|
# by sheet: position and flip of silk and fab text
|
|
a = 2
|
|
if fp.fp.IsFlipped() != ref_fp.fp.IsFlipped():
|
|
fp.fp.Flip(fp.fp.GetPosition(), False)
|
|
|
|
index = footprints.index(fp) - ref_fp_index
|
|
row = index // nr_columns
|
|
column = index - row * nr_columns
|
|
new_pos_x = ref_fp_pos.x + column * step_x * SCALE
|
|
new_pos_y = ref_fp_pos.y + row * step_y * SCALE
|
|
new_position = (new_pos_x, new_pos_y)
|
|
new_position = [int(x) for x in new_position]
|
|
fp.fp.SetPosition(pcbnew.VECTOR2I(*new_position))
|
|
footprint_angle = ref_fp.fp.GetOrientationDegrees()
|
|
footprint_angle = footprint_angle + index // step * rotation
|
|
fp.fp.SetOrientationDegrees(footprint_angle)
|
|
|
|
if copy_text_items:
|
|
self.replicate_fp_text_items(ref_fp, fp)
|
|
|
|
def replicate_fp_text_items(self, src_fp, dst_fp):
|
|
dst_anchor_fp_position = dst_fp.fp.GetPosition()
|
|
angle = src_fp.fp.GetOrientationDegrees() - dst_fp.fp.GetOrientationDegrees()
|
|
|
|
delta_pos = dst_anchor_fp_position - src_fp.fp.GetPosition()
|
|
|
|
src_fp_text_items = self.get_module_text_items(src_fp)
|
|
dst_fp_text_items = self.get_module_text_items(dst_fp)
|
|
# check if both modules (source and the one for replication) have the same number of text items
|
|
if len(src_fp_text_items) != len(dst_fp_text_items):
|
|
raise LookupError(
|
|
"Footprint: " + dst_fp.ref + " has different number of text items (" + repr(len(src_fp_text_items))
|
|
+ ")\nthan selected footprint: " + src_fp.ref + " (" + repr(len(dst_fp_text_items)) + ")")
|
|
# replicate each text item
|
|
for src_text in src_fp_text_items:
|
|
if src_text.IsKeepUpright() and angle != 0.0:
|
|
logger.info("Text of: " + src_fp.ref +
|
|
" has property \"Keep upright\" rotation might not look as intended")
|
|
|
|
index = src_fp_text_items.index(src_text)
|
|
src_text_position = src_text.GetPosition() + delta_pos
|
|
|
|
new_position = rotate_around_point(src_text_position, dst_anchor_fp_position, angle)
|
|
|
|
# convert to tuple of integers
|
|
new_position = [int(x) for x in new_position]
|
|
dst_fp_text_items[index].SetPosition(pcbnew.VECTOR2I(*new_position))
|
|
|
|
# set layer
|
|
dst_fp_text_items[index].SetLayer(src_text.GetLayer())
|
|
# copy all attributes
|
|
dst_fp_text_items[index].SetAttributes(src_text.GetAttributes())
|
|
# set orientation
|
|
dst_fp_text_items[index].SetTextAngleDegrees(src_text.GetTextAngleDegrees()-angle)
|
|
|
|
|
|
@staticmethod
|
|
def get_module_text_items(footprint):
|
|
""" get all text item belonging to a modules """
|
|
list_of_items = [footprint.fp.Reference(), footprint.fp.Value()]
|
|
|
|
footprint_items = footprint.fp.GraphicalItems()
|
|
for item in footprint_items:
|
|
if type(item) is pcbnew.PCB_TEXT:
|
|
list_of_items.append(item)
|
|
return list_of_items
|