Разводка регулярных структур в KiCAD: путь лентяя

в 10:25, , рубрики: CAD/CAM, diy или сделай сам, kicad, python, Производство и разработка электроники
image
Похоже, пост оказался неинтересным.
Извините.
Кому нужен код — пожалуйста, копируйте.

Скрыто

Я в одной из предыдущих статей писал — я, фактически, безработный. Юридически — нет, во-первых я пенсионер, но вполне мог бы и работать. Во-вторых, вроде как и работаю в одной маленькой фирме из двух человек, но последний год у нас с контрактами напряженка. С голоду не умираем, пенсии вполне достаточно на жизнь, но ведь развлекаться как-то надо?
Время от времени от скуки публикую статьи-обзоры на сайте шопоголиков, администрация сайта даже денег довольно-таки регулярно за это дает. Очень хорошая отмазка для супруги — нет, я не шопоголик, это я, вроде как, при деле. И вот здесь взялся публиковать статейки — если на том сайте такие публиковать, только минусов нахватаешь — типа ты что, слишком умный, что ли? — а вот на тебе минус и не балуй. И в следующий раз пиши про какую-нибудь мыльницу.

В одной из предыдущих своих статей я рассказывал о дисплее на базе адресуемых светодиодов. Сейчас расскажу, как проектировались последние варианты плат для них.

Самих светодиодов было довольно-таки много — 1296 штук, плюс в плате должны быть вырезы. Дисплей, размещенный в окне, не должен полностью заслонять свет. А дисплей, предназначенный для замораживания, если его сделать сплошным, и порвать при заморозке может. Или компоненты оторвать. А может и еще что — специально обученный товарищ рассказывал, что случается при заморозке печатных плат во льду, но у меня в одно ухо влетело, во второе — вылетело.

Плата здесь приведена только для примера — реальная структура была чуть сложнее и были еще компоненты, которые надо было втолкать на эту же плату — но по сравнению с матрицей светодиодов это были совсем мелочи и окончательная разводка была сделана вручную.
Сразу предупреждаю, что я не большой специалист ни в Python, ни в KiCAD — для меня это лишь инструменты, которыми я пользуюсь время от времени. Наверное, я вообще во всем по жизни чайник — но зато медный и со свистком.

Первое дело — конечно, схема. Ее нужно нарисовать так, чтобы компоненты, расположенные рядом, имели и соответствующие номера.

image

image

image

Готовим форму платы — у нее множество отверстий, руками делать вспотеешь. Эта процедура вообще сделана по сермяге. Нарисовал пустую плату с одним только контуром и сделал генерацию более сложного контура по образу и подобию. Потом уже обнаружил, что в KiCAD есть библиотека pcbnew, предназначенная для разработки своих скриптов, но переделывать уже не хотелось, работает — не трогай.
Итак, создаем пустую плату и запускаем

вот этот скрипт:

panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0
holeX = 7.0
holeY = 7.0
hole_cut = 1.0
panel_gap = 2
origin=[0,0]

pcb_name = 'habr_dummy.kicad_pcb'
pcb_name2 = 'habr_edges.kicad_pcb'

def Create_edges():
    try:
        pcb_file = open(pcb_name, 'r')     
        #pcb_data = pcb_file.read()
        lineList = pcb_file.readlines()
        #pos = pcb_data.find(')')
        pcb_file.close()   
    except IOError:
        print("Cannot open file '%s'." % pcb_name)    
        return

    panel_size=[0,0]
    panel_size[0]= deltaX * panel_rows - panel_gap
    panel_size[1]= deltaY * panel_lines - panel_gap
    #print(panel_size)
    corners=[]
    corners.append([origin[0] - panel_size[0]/2, origin[1] - panel_size[1]/2])
    corners.append([origin[0] + panel_size[0]/2, origin[1] - panel_size[1]/2])
    corners.append([origin[0] + panel_size[0]/2, origin[1] + panel_size[1]/2])
    corners.append([origin[0] - panel_size[0]/2, origin[1] + panel_size[1]/2])
    #print(corners) 
    holes=[]
    for x in range(panel_rows-1):
        for y in range(panel_lines-1):  
            hole_orig =[0,0]
            hole_orig [0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX + deltaX/2
            hole_orig [1] = origin[1] - (deltaY * (panel_lines-1))/2 + y*deltaY + deltaY/2
            hole_corners=[] 
            
            hole_corners.append([hole_orig[0] - holeX/2 + hole_cut, hole_orig[1] - holeY/2])     # left down
            hole_corners.append([hole_orig[0] + holeX/2 - hole_cut, hole_orig[1] - holeY/2 ])        
            hole_corners.append([hole_orig[0] + holeX/2, hole_orig[1] - holeY/2 + hole_cut])
            hole_corners.append([hole_orig[0] + holeX/2 , hole_orig[1] + holeY/2 - hole_cut])
            hole_corners.append([hole_orig[0] + holeX/2 - hole_cut, hole_orig[1] + holeY/2])
            
            hole_corners.append([hole_orig[0] - holeX/2 + hole_cut, hole_orig[1] + holeY/2])
            hole_corners.append([hole_orig[0] - holeX/2, hole_orig[1] + holeY/2 - hole_cut])
            hole_corners.append([hole_orig[0] - holeX/2, hole_orig[1] - holeY/2 + hole_cut])
            
            holes.append(hole_corners) 

    pos = len(lineList)-1

    # build board edge
    for i in range(len(corners)):
        k = i+1
        if k==len(corners):
            k=0;
        new_line = '  (gr_line (start '
        new_line += str(corners[i][0]) + ' '
        new_line += str(corners[i][1]) + ') (end '
        new_line += str(corners[k][0]) + ' '
        new_line += str(corners[k][1])
        new_line += ') (layer Edge.Cuts) (width 0.05))n'
        #print(new_line)
        lineList.insert(pos, new_line) 
        pos += 1
    # build holes
    for hole in holes:
        for i in range(len(hole)):
            k = i+1
            if k==len(hole):
                k=0;
            new_line = '  (gr_line (start '
            new_line += str(hole[i][0]) + ' '
            new_line += str(hole[i][1]) + ') (end '
            new_line += str(hole[k][0]) + ' '
            new_line += str(hole[k][1])
            new_line += ') (layer Edge.Cuts) (width 0.05))n'
            #print(new_line)
            lineList.insert(pos, new_line) 
            pos += 1

    lineList.insert(pos, 'n') 

    try:
        pcb_file = open(pcb_name2, 'w')        
        for line in lineList:
            #pcb_file.write(line.decode('utf-8'))
            pcb_file.write(line)    
        pcb_file.close()
    except IOError:
        print("Cannot write file '%s'." % pcb_name2)    
  
Create_edges()

Сразу сделана хорошая часть работы:

image

image

Монтажных отверстий всего 6, но уж поставим их тоже сразу. Эти же отверстия будут служить и для подвода напряжения.

Код

import pcbnew

panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0

pcb_name2 = 'habr_edges.kicad_pcb'
                   
def MountHoles(pcb):
    libpath = "/usr/share/kicad/modules/MountingHole.pretty"  
    for i in range (2):
        x = panel_rows/4 * deltaX + deltaX/2 
        if i==0:
            x = -x   
        for j in range (3):
            y = panel_lines/3 * deltaY 
            if j==1:
                y = 0
            elif j==2:     
                y = -y
            print(x,y)
            footprint = pcbnew.FootprintLoad(libpath, "MountingHole_3.2mm_M3_DIN965_Pad")      

            pcb.Add(footprint)   
            footprint.SetPosition(pcbnew.wxPoint(pcbnew.Millimeter2iu(x),pcbnew.Millimeter2iu(y)))   
            footprint.Reference().SetVisible(False)    
    
def Convert():
    print("start")     
    pcb = pcbnew.LoadBoard(pcb_name2)
    MountHoles(pcb)
    pcb.Save(pcb_name2)
    print("created!")     
 
Convert()

image

image

Вырезы около монтажных отверстий получаются слишком большими — поправим их в конце, не все же автоматизировать, что-то и руками сделать надо.

image

Теперь на получившуюся плату загружаем список компонентов.

image

Мой компьютер после этого практически прекращает шевелиться — я его покупал несколько лет назад и основным критерием было отсутствие любых вентиляторов и шума соответственно. И должна быть возможность подключения двух, а лучше трех дисплеев. Цель была достигнута — и, кроме всего, системный блок потребляет всего 1 ампер от 12-Вольтового источника питания. Есть задачи, когда его быстродействия начинает не хватать, но их не так уж и много.

Размещаем светодиоды

Код

import pcbnew

panel_rows = 36
panel_lines = 36
deltaX = 10.0
deltaY = 10.0
holeX = 7.0
holeY = 7.0
hole_cut = 1.0
panel_gap = 2
origin=[0,0]

pcb_name = 'habr_populated.kicad_pcb'
pcb_name2 = 'habr_layout.kicad_pcb'
def LED_placement(pcb):
    led_position=[]
    for y in range (panel_lines):
        line_pos=[]  
        for x in range (panel_rows):
            diode_ref =  y*panel_rows + x +1
            # Find the component
            c = pcb.FindModuleByReference("D"+str(diode_ref))           
            # Place it somewhere
            pos = [0.0,0.0]
            rot =0;
            pos[1] = origin[1] + (deltaY * (panel_lines-1))/2 - y*deltaY
            if y%2==0:
                pos[0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX
                #rot = (-45+180)*10
                rot = (270+180)*10
            else:
                pos[0] = origin[0] + (deltaX * (panel_rows-1))/2 - x*deltaX
                #rot = -45 *10
                rot = 270 *10
            line_pos.append(pos) 
            c.SetPosition(pcbnew.wxPointMM(pos[0], pos[1]))
            # Rotate it (angle in 1/10 degreee)
            c.SetOrientation(rot)
            c.Reference().SetVisible(False) 
        led_position.append(line_pos)
    return led_position    

def Convert():   
    print("start")     
    pcb = pcbnew.LoadBoard(pcb_name)
    led_position = LED_placement(pcb)
    pcb.Save(pcb_name2)

    print("created!")     

 Convert()

image

image

теперь конденсаторы

Код

def Cap_placement(pcb):
    cap_position=[]
    for y in range (panel_lines-1):
        line_pos=[]  
        for x in range (panel_rows):
            cap_ref =  y*panel_rows + x +1
            # Find the component
            c = pcb.FindModuleByReference("C"+str(cap_ref))           
            # Place it somewhere
            pos = [0.0,0.0]
            rot =0;
            pos[1] = origin[1] + (deltaY * (panel_lines-1))/2 - y*deltaY - deltaY/2 
            if y%2==0:
                pos[0] = origin[0] - (deltaX * (panel_rows-1))/2 + x*deltaX  
                rot = (270+180)*10
            else:
                pos[0] = origin[0] + (deltaX * (panel_rows-1))/2 - x*deltaX
                rot = 270 *10
            line_pos.append(pos) 
            c.SetPosition(pcbnew.wxPointMM(pos[0], pos[1]))
            # Rotate it (angle in 1/10 degreee)
            c.SetOrientation(rot)
            c.Reference().SetVisible(False) 
        cap_position.append(line_pos)
    return cap_position

image

image

На стороне компонентов у нас будет положительное напряжение питания, на обратной — земля.
Проведем короткие проводочки от светодиодов и конденсаторов и установим проходные отверстия.

Код

def AddTrack(pcb, track, netCode, width, layer):
    for i in range (len(track)-1):
        t = pcbnew.TRACK(pcb)
        pcb.Add(t)
        t.SetStart(pcbnew.wxPoint(track[i][0], track[i][1]))
        t.SetEnd(pcbnew.wxPoint(track[i+1][0], track[i+1][1]))
        t.SetWidth(pcbnew.Millimeter2iu(width))
        t.SetNetCode(netCode)
        t.SetLayer(layer)
        
def AddVia(pcb, pos, netCode, dia, drill):
    v = pcbnew.VIA(pcb)
    pcb.Add(v)
    v.SetViaType(pcbnew.VIA_THROUGH)
    v.SetWidth(pcbnew.Millimeter2iu(dia))
    v.SetNetCode(netCode)
    v.SetPosition(pcbnew.wxPoint(pos[0],pos[1]))
    #v.SetLayerPair(0,31)
    v.SetDrill(pcbnew.Millimeter2iu(drill))

def LedGroundViaTrace(pcb, led_position, netCode):  
    #ground vias
    for y in range (panel_lines):
        for x in range (panel_rows):
            v = pcbnew.VIA(pcb)
            pcb.Add(v)
            v.SetViaType(pcbnew.VIA_THROUGH)
            v.SetWidth(pcbnew.Millimeter2iu(0.8))  # 1mm
            v.SetNetCode(netCode)
            pos = led_position[y][x]
            x0=pos[0]   
            if y%2==0:
                y0 = pos[1]-1.5-0.65                
            else:
                y0 = pos[1]+1.5+0.65                
            v.SetPosition(pcbnew.wxPointMM(x0,y0))
            v.SetLayerPair(0,31)
            v.SetDrill(pcbnew.Millimeter2iu(0.4))

    #line to via
    for y in range (panel_lines):
        for x in range (panel_rows):
            pos = led_position[y][x]
            if y%2==0:
                x0=pos[0]+1
                y0 = pos[1]-0.65  
                y1=y0-0.5
                x1=pos[0]
                y2=y1-1      
            else:
                x0=pos[0]-1
                y0 = pos[1]+0.65  
                y1=y0+0.5
                x1=pos[0]
                y2=y1+1                
                                   
            t = pcbnew.TRACK(pcb)
            pcb.Add(t)
            t.SetStart(pcbnew.wxPointMM(x0,y0))
            t.SetEnd(pcbnew.wxPointMM(x0, y1))
            t.SetWidth(pcbnew.Millimeter2iu(0.25))
            t.SetNetCode(netCode)
            
            t = pcbnew.TRACK(pcb)
            pcb.Add(t)
            t.SetStart(pcbnew.wxPointMM(x0,y1))
            t.SetEnd(pcbnew.wxPointMM(x1, y2))
            t.SetWidth(pcbnew.Millimeter2iu(0.25))
            t.SetNetCode(netCode)            


def CapGroundViaTrace(pcb, cap_position, netCode):  
    #ground vias
    for y in range (panel_lines-1):
        for x in range (panel_rows):
            v = pcbnew.VIA(pcb)
            pcb.Add(v)
            v.SetViaType(pcbnew.VIA_THROUGH)
            v.SetWidth(pcbnew.Millimeter2iu(0.8))  # 1mm
            v.SetNetCode(netCode)
            pos = cap_position[y][x]
            x0=pos[0];
            if y%2==0:
                y0 = pos[1]-1.5
            else:
                y0 = pos[1]+1.5
            v.SetPosition(pcbnew.wxPointMM(x0,y0))
            v.SetLayerPair(0,31)
            v.SetDrill(pcbnew.Millimeter2iu(0.4))

    #line to via
    for y in range (panel_lines-1):
        for x in range (panel_rows):
            t = pcbnew.TRACK(pcb)
            pcb.Add(t)
            pos = cap_position[y][x]
            if y%2==0:
                y0 = pos[1]-0.485
                y1 = pos[1]-1.5
            else:
                y0 = pos[1]+0.485
                y1 = pos[1]+1.5
            t.SetStart(pcbnew.wxPointMM(pos[0],y0))
            t.SetEnd(pcbnew.wxPointMM(pos[0], y1))
            t.SetWidth(pcbnew.Millimeter2iu(0.25))
            t.SetNetCode(netCode)

image

image

По сути дела остались только линии данных. Не забываем, что в сумме ток у нас приличный и линии тока лучше не разрывать, поэтому линия данных идет не по прямой, а по другой стороне платы.

Код

def DataLines(pcb):
    for y in range (panel_lines):
        for x in range (panel_rows-1):
            diode_ref = "D"+str(y*panel_rows+x+1)
            diode = pcb.FindModuleByReference(diode_ref)    
            for pad in diode.Pads():
                if pad.GetPadName()=='1':
                    #PIN1 DOUT
                    netCode = pad.GetNet().GetNet()    
                    x0 = pad.GetPosition().x
                    y0 = pad.GetPosition().y    
                    if y%2==0:
                        x1 = x0 + pcbnew.Millimeter2iu(0.8)   
                        x2 = x1 + pcbnew.Millimeter2iu(0.65)  
                        y1 = y0 - pcbnew.Millimeter2iu(0.65) 
                        x3 = x2 + pcbnew.Millimeter2iu(5.1) 
                        x4 = x3 + pcbnew.Millimeter2iu(0.65) 
                        y2 = y1 - pcbnew.Millimeter2iu(0.65)
                        x5 = x4 + pcbnew.Millimeter2iu(0.8)  
                    else:
                        x1 = x0 - pcbnew.Millimeter2iu(0.8)
                        x2 = x1 - pcbnew.Millimeter2iu(0.65)
                        y1 = y0 + pcbnew.Millimeter2iu(0.65) 
                        x3 = x2 - pcbnew.Millimeter2iu(5.1) 
                        x4 = x3 - pcbnew.Millimeter2iu(0.65) 
                        y2 = y1 + pcbnew.Millimeter2iu(0.65)
                        x5 = x4 - pcbnew.Millimeter2iu(0.8)                              
                    # top 
                    track=[]
                    track.append([x0, y0])
                    track.append([x1, y0])
                    track.append([x2, y1])               
                    AddTrack(pcb, track, netCode, 0.25, 0)
                    AddVia(pcb, [x2, y1], netCode, 0.8, 0.4)
                    #bottom                    
                    track=[]
                    track.append([x2, y1]) 
                    track.append([x3, y1]) 
                    AddTrack(pcb, track, netCode, 0.25, 31)
                    AddVia(pcb, [x3, y1], netCode, 0.8, 0.4)                        
                    # top 
                    track=[]
                    track.append([x3, y1]) 
                    track.append([x4, y2]) 
                    track.append([x5, y2]) 
                    AddTrack(pcb, track, netCode, 0.25, 0)               
                             
def CrossLines(pcb):
    for y in range (panel_lines-1):  #panel_rows
        diode_ref = "D"+str((y+1)*panel_rows )
        diode = pcb.FindModuleByReference(diode_ref)    
        for pad in diode.Pads():
            if pad.GetPadName()=='1':
                #PIN1 DOUT
                netCode = pad.GetNet().GetNet()    
                x0 = pad.GetPosition().x
                y0 = pad.GetPosition().y    
                    
                if y%2==0:
                    x1 = x0 + pcbnew.Millimeter2iu(0.8)   
                    x2 = x1 + pcbnew.Millimeter2iu(0.65)  
                    y1 = y0 - pcbnew.Millimeter2iu(0.65) 
                    y2 = y1 - pcbnew.Millimeter2iu(deltaY - 1.3) 
                    y3 = y2 - pcbnew.Millimeter2iu(0.65) 
                else:
                    x1 = x0 - pcbnew.Millimeter2iu(0.8)
                    x2 = x1 - pcbnew.Millimeter2iu(0.65)
                    y1 = y0 - pcbnew.Millimeter2iu(0.65) 
                    y2 = y1 - pcbnew.Millimeter2iu(deltaY - 1.3) 
                    y3 = y2 - pcbnew.Millimeter2iu(0.65)                            
                # top 
                track=[]
                track.append([x0, y0])
                track.append([x1, y0])
                track.append([x2, y1])       
                track.append([x2, y2])    
                track.append([x1, y3])           
                track.append([x0, y3]) 
                AddTrack(pcb, track, netCode, 0.25, 0)

image

image

Полигоны, конечно, и ручкам сделать несложно, да уж ладно, раз взялись писать подпрограммы, то доделаем и это

Код

def DrawPolygons(pcb):
    # grownd plane DrawPolygons(pcb)
    plane_size=[0,0]
    plane_size[0]= deltaX * panel_rows/2 -  panel_gap/2 - 0.5 
    plane_size[1]= deltaY * panel_lines/2 - panel_gap/2 - 0.5
    
    Contour=[]
    Contour.append( [pcbnew.Millimeter2iu(plane_size[0]),   pcbnew.Millimeter2iu(plane_size[1]) ])
    Contour.append( [pcbnew.Millimeter2iu(plane_size[0]),  -pcbnew.Millimeter2iu(plane_size[1]) ])
    Contour.append( [-pcbnew.Millimeter2iu(plane_size[0]), -pcbnew.Millimeter2iu(plane_size[1]) ])
    Contour.append( [-pcbnew.Millimeter2iu(plane_size[0]),  pcbnew.Millimeter2iu(plane_size[1]) ])  
    
    nets = pcb.GetNetsByName()
    net = nets.find("GND").value()[1]
    netCode = net.GetNet()
    
    newarea = pcb.InsertArea(netCode, pcbnew.B_Cu, pcbnew.B_Cu, Contour[0][0], Contour[0][1], pcbnew.ZONE_CONTAINER.DIAGONAL_EDGE)    
    newoutline = newarea.Outline()
    for i in range (1,4):
        newoutline.Append(Contour[i][0],Contour[i][1]);
    
    poly_set = pcbnew.SHAPE_POLY_SET()
    poly_set.NewOutline()
    for i in range (0,4):
        poly_set.Append(Contour[i][0],Contour[i][1])    
    newarea.SetFilledPolysList(poly_set)    
    
    nets = pcb.GetNetsByName()
    net = nets.find("+5V").value()[1]
    netCode = net.GetNet()
    
    newarea = pcb.InsertArea(netCode, pcbnew.F_Cu, pcbnew.F_Cu, Contour[0][0], Contour[0][1], pcbnew.ZONE_CONTAINER.DIAGONAL_EDGE)    
    newoutline = newarea.Outline()
    for i in range (1,4):
        newoutline.Append(Contour[i][0],Contour[i][1]);
    
    poly_set = pcbnew.SHAPE_POLY_SET()
    poly_set.NewOutline()
    for i in range (0,4):
        poly_set.Append(Contour[i][0],Contour[i][1])    
    newarea.SetFilledPolysList(poly_set)  

image

image

image

И суммарная процедура теперь выглядит так

Код

def Convert():
    
    print("start")     
    pcb = pcbnew.LoadBoard(pcb_name)
    led_position = LED_placement(pcb)
    cap_position = Cap_placement(pcb)
    nets = pcb.GetNetsByName()
    net = nets.find("GND").value()[1]
    netCode = net.GetNet()    
    CapGroundViaTrace(pcb, cap_position, netCode)
    LedGroundViaTrace(pcb, led_position, netCode)
    DataLines(pcb)
    CrossLines(pcb)
    DrawPolygons(pcb)
    pcb.Save(pcb_name2)
    print("created!")     
 
Convert()

image

image

Скрыто

Когда все функции написаны, уже легко менять размеры, формы, количество пикселей, добавлять перламутровые пуговицы и потакать любым прихотям заказчика, если плата делается на заказ. Хотя, наверно, заказчику лучше рассказать, как употел разработчик этой платы рисуя бесконечные соединения, а то ведь не оценит.

В заключение на жизнь пожаловаться, что ли. Хотя Остап Бендер советовал с этим обращаться во всемирную лигу сексуальных реформ (смех смехом, а она действительно существовала в то время).

Отвечайте нам, а то,
Если вы не отзовётесь,
мы напишем в «Спортлото» ©

В Финляндии вовсю кричат о нехватке рабочей силы, особенно квалифицированной в области IT. Но о чем крик — я не понимаю.
Мой случай несколько не входит в общие рамки — пенсионер по инвалидности мало кому нужен. Да, в офис приходить каждый день мне сложно. Но ведь сейчас все топят за дистанционную работу.
А тут-то мне равных немного — мало у кого дома есть столько оборудования, как у меня. И опыт работы в фирмах, круче которых, наверно, только яйца вкрутую, и то не факт.
И знакомых полно — руководителей среднего звена. Но они работают в крупных фирмах и кадровиков не могут убедить никак, всем нужны молодые работники на постоянную работу.
А пенсионера со странными требованиями (например, зарплата не должна быть больше определенного уровня), всерьез никто не воспринимает. Типа, иди, кури. А я ищу не денег, а просто применения сил — потому, наверно, и не хотят связываться.
Остаются мелкие фирмы, хозяева которых рисковать с персоналом не могут и берут хороших знакомых, если они идут, конечно. У мелких фирм, как правило, и зарплата мелкая.
Но в такой мелкой фирме я и так работаю — мой друг, владелец и тоже пенсионер, и я. Но последний год у нас контрактов нет.

В нашем переулке, в двух соседних от меня домах, живут инженеры по электронике, были далеко не последними специалистами. С коллапсом Nokia многие фирмы, которые работали с ней, тоже позакрывали свои подразделения. Много народа осталось без работы, и они тоже.
Ладно, один из них не молодой, а второй через какое-то время тяжело заболел. Но у меня есть и несколько знакомых инженеров, в самом расцвете сил — но перебиваются случайными заработками. Я с ними сталкивался как раз, когда у них была такая временная работа, большинство из них очень квалифицированные специалисты.

Автор: Владимир

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js