Improved render interface

This commit is contained in:
2026-03-01 22:41:59 +00:00
parent ae4ffcb932
commit 58738b9a40
4 changed files with 948 additions and 121 deletions

View File

@@ -54494,164 +54494,316 @@
(uuid "bdb227a5-f7ab-4333-aea6-3cf2db6a672d")
)
(segment
(start 75.2 92.013606)
(end 75.2 97.786394)
(width 0.2)
(start 81.224999 86.98)
(end 80.814999 87.39)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "0dfac608-ccd4-4e9b-bfae-e00a14836b18")
)
(segment
(start 94.735001 97.144999)
(end 95.78 96.1)
(width 0.2)
(layer "F.Cu")
(net 228)
(uuid "1b5c7f7c-6b53-4ec8-bd10-3ba05de94c8f")
)
(segment
(start 93.118198 99.7)
(end 94.735001 98.083197)
(width 0.2)
(layer "F.Cu")
(net 228)
(uuid "3a9f0b2d-a9a2-4ae6-950d-a045821457aa")
)
(segment
(start 75.2 97.786394)
(end 77.113606 99.7)
(width 0.2)
(layer "F.Cu")
(net 228)
(uuid "4b9bffa4-2932-4f13-9f83-8cd70a007174")
)
(segment
(start 80.814999 87.39)
(end 79.823606 87.39)
(width 0.2)
(layer "F.Cu")
(net 228)
(uuid "5d718bfa-a23c-477c-b393-723c3d5e106b")
)
(segment
(start 79.823606 87.39)
(end 75.2 92.013606)
(width 0.2)
(layer "F.Cu")
(net 228)
(uuid "69cf8735-80a1-4ff9-a07c-caa420582e29")
(uuid "153041d6-9208-4d0c-8b33-169957911f12")
)
(segment
(start 94.735001 98.083197)
(end 94.735001 97.144999)
(width 0.2)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "88c66fcf-e0ba-4543-8bf4-0ec8f1e4c29e")
(uuid "34831dc0-155b-47b0-b1a2-e92dd9ac7f8f")
)
(segment
(start 81.224999 86.98)
(end 80.814999 87.39)
(width 0.2)
(start 75.2 97.786394)
(end 77.113606 99.7)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "927e190e-f0d4-4148-b209-70dc4bf08344")
(uuid "36e4bdef-dd90-452e-be4d-ebb3919f5d56")
)
(segment
(start 93.118198 99.7)
(end 94.735001 98.083197)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "4dc4725c-08b0-4d65-bb16-ef6496b9d884")
)
(segment
(start 94.735001 97.144999)
(end 95.78 96.1)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "59c6f013-da1c-45cc-9ace-eda0727fcf73")
)
(segment
(start 79.823606 87.39)
(end 75.2 92.013606)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "6b65744e-695b-4fdb-af61-09b8b57d1ca8")
)
(segment
(start 75.2 92.013606)
(end 75.2 97.786394)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "7dcf0da8-a898-48f6-bd4c-18f94efc95c4")
)
(segment
(start 77.113606 99.7)
(end 93.118198 99.7)
(width 0.2)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "b800b33d-3dcb-4241-a1b1-4c081211769a")
(uuid "e59a1120-d3b9-4dab-9ab6-cba25bfc60fa")
)
(segment
(start 80.814999 87.39)
(end 79.823606 87.39)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "ef315610-5691-44fe-b978-ab195b3f4d1c")
)
(segment
(start 82.35 86.98)
(end 81.224999 86.98)
(width 0.2)
(width 0.15)
(layer "F.Cu")
(net 228)
(uuid "dfab2918-2595-4fa1-99b8-5a8baf3a0fe4")
)
(segment
(start 80.814999 87.84)
(end 80.01 87.84)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "2541bdcd-f767-4e28-8b2f-f1f9eec15a01")
)
(segment
(start 80.01 87.84)
(end 75.65 92.2)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "3f22f26d-ad1f-46b2-9db9-6ccf375b0fd7")
)
(segment
(start 75.65 92.2)
(end 75.65 97.6)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "4af4d38f-17b7-40cd-a2bc-219a7a385314")
)
(segment
(start 94.284999 97.896803)
(end 94.284999 97.144999)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "55cc8362-d0a5-408b-927c-c39ab4f24bcd")
)
(segment
(start 94.284999 97.144999)
(end 93.24 96.1)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "5d391cf1-ef37-4e62-9b89-de76e963a1a3")
)
(segment
(start 92.931802 99.25)
(end 94.284999 97.896803)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "60fb4081-a6f9-45b8-8c26-81eb9a73b31a")
)
(segment
(start 81.224999 88.25)
(end 80.814999 87.84)
(width 0.2)
(layer "F.Cu")
(net 229)
(uuid "6df63073-4328-47c0-99d4-960ca82cd21a")
(uuid "f2aa4e28-2fbe-4827-aae0-64b4a62c88a1")
)
(segment
(start 82.35 88.25)
(end 81.224999 88.25)
(width 0.2)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "99327d42-bce9-45b9-96de-909f14184be6")
(uuid "11f0e7f2-7b7b-47fc-8f1f-6e137ab1b3ee")
)
(segment
(start 77.3 99.25)
(end 92.931802 99.25)
(width 0.2)
(start 83.531993 99.25)
(end 83.651993 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "b92fbb9c-9ee1-4f1c-97c7-08be9abd66cd")
(uuid "26536fbd-bc1c-4081-b1aa-8f6d29831651")
)
(segment
(start 82.691993 99.01)
(end 82.691993 98.724791)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "490ff514-dd04-46da-bd2e-c07d11180ee6")
)
(segment
(start 83.291993 98.724791)
(end 83.291993 99.01)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "49c2dc1f-92d8-4730-9191-e71050451fe4")
)
(segment
(start 83.651993 99.25)
(end 85.480063 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "56656d40-88fa-4fa5-b9e4-7d0713a8220f")
)
(segment
(start 94.284999 97.144999)
(end 93.24 96.1)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "6511d2e9-bfa9-4cad-8dbb-6beca172f9a0")
)
(segment
(start 75.65 97.6)
(end 77.3 99.25)
(width 0.2)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "ff941e70-bc4e-4911-9927-9e8ebd472663")
(uuid "6f4112e5-a6da-4203-8e08-c6ee4f51b351")
)
(segment
(start 81.491993 99.01)
(end 81.491993 98.724815)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "743c96d7-eece-44d6-b31e-f4b37de354a1")
)
(segment
(start 81.731993 98.484815)
(end 81.851993 98.484815)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "75176927-01b6-4694-9371-f943059d4078")
)
(segment
(start 85.480063 99.25)
(end 92.931802 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "823e261e-945f-4052-abf8-5b9e45ae163c")
)
(segment
(start 82.331993 99.25)
(end 82.451993 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "84b05f2e-bbd2-4aca-aba8-d631efa5ced2")
)
(segment
(start 82.091993 98.724815)
(end 82.091993 99.01)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "84c8d27b-d52c-4f37-ab6e-32b3ffa6c30c")
)
(segment
(start 75.65 92.2)
(end 75.65 97.6)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "9aed0bd0-c9b1-4beb-8e48-5a0ad739713c")
)
(segment
(start 80.01 87.84)
(end 75.65 92.2)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "a26bbdd8-6b09-4841-bafe-34df099a8ea7")
)
(segment
(start 82.931993 98.484791)
(end 83.051993 98.484791)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "b92d13fa-9a3e-4541-b59e-99870d058324")
)
(segment
(start 80.814999 87.84)
(end 80.01 87.84)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "c5f0a4bd-a2df-44c2-91a3-27ce9d99511b")
)
(segment
(start 94.284999 97.896803)
(end 94.284999 97.144999)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "cb7ab279-ddaf-4098-bcdd-914e5b353ce2")
)
(segment
(start 77.3 99.25)
(end 81.251993 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "ddf78547-a37f-478b-afbd-582d8904c5fb")
)
(segment
(start 81.224999 88.25)
(end 80.814999 87.84)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "ef3af144-99c0-4c24-bafa-2354e6209cc0")
)
(segment
(start 92.931802 99.25)
(end 94.284999 97.896803)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "f8c0df63-76a4-4925-a3d3-a42457023226")
)
(arc
(start 81.851993 98.484815)
(mid 82.021699 98.555109)
(end 82.091993 98.724815)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "00726cf1-4e94-468e-b038-e9f6462e923b")
)
(arc
(start 81.491993 98.724815)
(mid 81.562287 98.555109)
(end 81.731993 98.484815)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "0a35b19d-c99d-4726-af86-8278924fb1ce")
)
(arc
(start 83.051993 98.484791)
(mid 83.221699 98.555085)
(end 83.291993 98.724791)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "5c8e2096-9dbc-43d9-8ae4-8fa33614ea2c")
)
(arc
(start 83.291993 99.01)
(mid 83.362287 99.179706)
(end 83.531993 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "70c177ac-6d5d-4478-98f5-e85f421df64f")
)
(arc
(start 82.091993 99.01)
(mid 82.162287 99.179706)
(end 82.331993 99.25)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "77f3228e-d21d-41d1-bdca-bf45236c00e6")
)
(arc
(start 81.251993 99.25)
(mid 81.421699 99.179706)
(end 81.491993 99.01)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "c752b7d3-d0d2-4c93-adf4-19de7b44b6c6")
)
(arc
(start 82.691993 98.724791)
(mid 82.762287 98.555085)
(end 82.931993 98.484791)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "e10fd845-ca3f-4503-ac41-556b878a6d03")
)
(arc
(start 82.451993 99.25)
(mid 82.621699 99.179706)
(end 82.691993 99.01)
(width 0.15)
(layer "F.Cu")
(net 229)
(uuid "f4cc9c2b-9eaf-4716-997b-e691adaae377")
)
(segment
(start 102.751 71.799)
@@ -62899,6 +63051,58 @@
"fbb4d2c5-e131-4bb5-9692-ad5ebb947546"
)
)
(generated
(uuid "beaa19a7-1c27-49eb-a148-aae52039b77e")
(type tuning_pattern)
(name "Tuning Pattern")
(layer "F.Cu")
(base_line
(pts
(xy 81.251993 99.25) (xy 85.480063 99.25)
)
)
(base_line_coupled
(pts
(xy 81.251993 99.7) (xy 81.28941 99.7)
)
)
(corner_radius_percent 80)
(end
(xy 85.480063 99.25)
)
(initial_side "left")
(last_diff_pair_gap 0.18)
(last_netname "/ESP_USB_P")
(last_status "tuned")
(last_track_width 0.15)
(last_tuning "0.0000 mm (tuned)")
(max_amplitude 1)
(min_amplitude 0.2)
(min_spacing 0.6)
(origin
(xy 81.251993 99.25)
)
(override_custom_rules no)
(rounded yes)
(single_sided no)
(target_length 1000000)
(target_length_max 1000000)
(target_length_min 0)
(target_skew 0)
(target_skew_max 0.1)
(target_skew_min -0.1)
(tuning_mode "diff_pair_skew")
(members "00726cf1-4e94-468e-b038-e9f6462e923b" "0a35b19d-c99d-4726-af86-8278924fb1ce"
"26536fbd-bc1c-4081-b1aa-8f6d29831651" "490ff514-dd04-46da-bd2e-c07d11180ee6"
"49c2dc1f-92d8-4730-9191-e71050451fe4" "56656d40-88fa-4fa5-b9e4-7d0713a8220f"
"5c8e2096-9dbc-43d9-8ae4-8fa33614ea2c" "70c177ac-6d5d-4478-98f5-e85f421df64f"
"743c96d7-eece-44d6-b31e-f4b37de354a1" "75176927-01b6-4694-9371-f943059d4078"
"77f3228e-d21d-41d1-bdca-bf45236c00e6" "84b05f2e-bbd2-4aca-aba8-d631efa5ced2"
"84c8d27b-d52c-4f37-ab6e-32b3ffa6c30c" "b92d13fa-9a3e-4541-b59e-99870d058324"
"c752b7d3-d0d2-4c93-adf4-19de7b44b6c6" "e10fd845-ca3f-4503-ac41-556b878a6d03"
"f4cc9c2b-9eaf-4716-997b-e691adaae377"
)
)
(generated
(uuid "c64bf8d2-34a0-46b9-a034-9b73fe492737")
(type tuning_pattern)

View File

@@ -1,8 +1,8 @@
{
"board": {
"active_layer": 2,
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
@@ -50,7 +50,7 @@
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_fffffff5_57555527",
"visible_layers": "ffffffff_ffffffff_fffffff5_575d5527",
"zone_display_mode": 0
},
"git": {

603
TerminalServer/pcbblend.py Normal file
View File

@@ -0,0 +1,603 @@
import bpy
import csv
import sys
import os
from mathutils import Vector, Matrix, Quaternion
import time
from glob import glob
from math import radians
import bmesh
import random
# Useful colours
# Finishes
hasl = (0.6, 0.6, 0.6, 1.0)
enig = (0.865, 0.524, 0.0, 1.0)
# Silk colours
white = (1.0, 1.0, 1.0, 1.0)
black = (0.0, 0.0, 0.0, 1.0)
# Mask colours
red = (0.347, 0.000, 0.022, 1.0)
green = (0.0, 0.17, 0.015, 1.0)
blue = (0.0, 0.195, 0.828, 1.0)
purple = (0.174, 0.0, 0.574, 1.0)
yellow = (0.814, 0.429, 0.0, 1.0)
# black - use silk colour
# white - use silk colour
class PCBImport(bpy.types.Operator):
# Location where all your converted files are stored
file_root = "/home/matt/git/TerminalServer/TerminalServer"
# Base name of the files
file_name = "TerminalServer"
# Colour you want for the mask: blue, red, green, purple, black, white, yellow
color = green
# Finish for the board: hasl, enig
finish = hasl
# Silk screen colour: black, white
silk = white
camera = [ 52.5315, -159.018, 161.144 ]
target = (2.6644, -9.21087, 0)
# Location where all the component library files are stored
component_root = "/home/matt/git/3D_Components"
# Whether or not to join everything into one single object
doJoin = False
# List of manual rotations in the form of {"refdes": angle, "refdes": angle...}
rotations = {
}
dnp = {
}
bl_label = "Import gEDA PCB Models"
bl_idname = "wm.modal_timer_operator"
bl_options = {'BLOCKING'}
file_outline = file_root + "/" + file_name + ".stl"
file_csv = file_root + "/Gerber/" + file_name + "-top-pos.csv"
components = []
txt = None
pcb = None
objects = None
def NormalInDirection(self, normal, direction, limit = 0.5 ):
return direction.dot( normal ) > limit
def GoingUp(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (0, 0, 1 ) ), limit )
def GoingDown(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (0, 0, -1 ) ), limit )
def GoingLeft(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (-1, 0, 0) ), limit)
def GoingRight(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (1, 0, 0) ), limit)
def GoingFore(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (0, 1, 0) ), limit)
def GoingBack(self, normal, limit = 0.5 ):
return self.NormalInDirection( normal, Vector( (0, -1, 0) ), limit)
def GoingSide(self, normal, limit = 0.5 ):
return self.GoingUp( normal, limit ) == False and self.GoingDown( normal, limit ) == False
def clearMeshSelections(self):
self.setMode('EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
self.setMode('OBJECT')
def meshSelectAll(self):
self.setMode('EDIT')
bpy.ops.mesh.select_all(action='SELECT')
self.setMode('OBJECT')
def setMode(self, newmode):
bpy.ops.object.mode_set(mode=newmode, toggle=False)
def deselectAll(self):
bpy.ops.object.select_all(action='DESELECT')
def selectAll(self):
bpy.ops.object.select_all(action='SELECT')
def openBuildReport(self):
self.txt = bpy.data.texts.get("BuildReport.txt")
if not self.txt:
self.txt = bpy.data.texts.new("BuildReport.txt")
self.txt.clear()
def deleteExistingBoard(self):
self.deselectAll()
for ob in bpy.data.objects:
if ob.name == self.file_name:
self.setSelect(ob, True)
bpy.ops.object.delete()
self.deselectAll()
def deleteOrphans(self):
self.deselectAll()
for m in bpy.data.materials:
if (m.users == 0):
bpy.data.materials.remove(m)
for m in bpy.data.meshes:
if (m.users == 0):
bpy.data.meshes.remove(m)
for m in bpy.data.images:
if (m.users == 0):
bpy.data.images.remove(m)
def vabs(self, v):
return (abs(v[0]), abs(v[1]), abs(v[2]))
def version(self):
return bpy.app.version[0] + (bpy.app.version[1]/100)
def setSelect(self, object, state):
if self.version() < 2.80:
object.select = state
else:
object.select_set(state)
def getSelect(self, object):
if self.version() < 2.80:
return object.select
else:
return object.select_get()
def setActiveObject(self, object):
if self.version() < 2.80:
bpy.context.scene.objects.active = object
else:
bpy.context.view_layer.objects.active = object
def linkObject(self, object):
if self.version() < 2.80:
bpy.context.scene.objects.link(object)
else:
bpy.context.scene.collection.objects.link(object)
def matchingVertices(self, f1, f2):
count = 0
for v1 in f1.vertices:
for v2 in f2.vertices:
if v1 == v2:
count += 1
return count
def selectOuterFaces(self, object):
faces = object.data.polygons
bpy.ops.mesh.select_all(action="DESELECT")
sideFaces = [f for f in faces if self.GoingSide(f.normal)]
print(sideFaces)
maxObject = None
maxArea = 0
linkedFaces = []
for f in sideFaces:
if (len(f.vertices) == 3):
if f.area > maxArea:
maxArea = f.area
maxObject = f
print("Max object:")
print(maxObject.index)
self.selectFace(object, maxObject.index)
bpy.ops.mesh.select_linked(delimit={'UV'})
def selectFace(self, object, faceid):
bm = bmesh.from_edit_mesh(object.data)
bm.faces.ensure_lookup_table()
bm.faces[faceid].select = True
bmesh.update_edit_mesh(object.data)
def importOutline(self):
self.deselectAll()
bpy.ops.wm.stl_import(filepath = self.file_outline)
outline = [c for c in bpy.context.scene.objects if self.getSelect(c)]
self.pcb = outline[0]
self.pcb["auto_created"] = True
self.pcb.data["auto_created"] = True
self.deselectAll()
self.setSelect(self.pcb, True)
self.setActiveObject(self.pcb)
bpy.context.object.data.name = self.file_name
bpy.context.object.name = self.file_name
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
self.deselectAll()
def loadMaterials(self):
with bpy.data.libraries.load(self.component_root + "/Base/materials.blend", link=False) as (data_from, data_to):
data_to.materials = ["Metal", "PCB Texture", "PCB Substrate"]
top = None
try:
top = bpy.data.materials['PCB Top']
except:
top = bpy.data.materials['PCB Texture'].copy();
top.name = "PCB Top"
btm = None
try:
btm = bpy.data.materials['PCB Bottom']
except:
btm = bpy.data.materials['PCB Texture'].copy();
btm.name = "PCB Bottom"
top.node_tree.nodes['copper'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-F_Cu.png")
top.node_tree.nodes['soldermask'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-F_Mask.png")
top.node_tree.nodes['silk'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-F_Silkscreen.png")
top.node_tree.nodes['pcbtexture'].inputs['Color'].default_value = self.color
top.node_tree.nodes['pcbtexture'].inputs['Finish'].default_value = self.finish
top.node_tree.nodes['pcbtexture'].inputs['Silk Color'].default_value = self.silk
top.node_tree.nodes['mapping'].inputs['Scale'].default_value = (1.000, 1.000, 1)
top.node_tree.nodes['mapping'].inputs['Location'].default_value = (0, 0.00, 0)
btm.node_tree.nodes['copper'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-B_Cu.png")
btm.node_tree.nodes['soldermask'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-B_Mask.png")
btm.node_tree.nodes['silk'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-B_Silkscreen.png")
btm.node_tree.nodes['pcbtexture'].inputs['Color'].default_value = self.color
btm.node_tree.nodes['pcbtexture'].inputs['Finish'].default_value = self.finish
btm.node_tree.nodes['pcbtexture'].inputs['Silk Color'].default_value = self.silk
btm.node_tree.nodes['mapping'].inputs['Scale'].default_value = (1.000, 1.000, 1)
btm.node_tree.nodes['mapping'].inputs['Location'].default_value = (0, 0, 0)
self.pcb.data.materials.append(bpy.data.materials['Metal'])
self.pcb.data.materials.append(top)
self.pcb.data.materials.append(btm)
self.pcb.data.materials.append(bpy.data.materials['PCB Substrate'])
def facesTouching(self, one, two):
return False
def faceArea(self, f):
return f.area
def assignMaterials(self):
# Metal
self.clearMeshSelections()
self.setMode('OBJECT')
for face in self.pcb.data.polygons:
face.select = self.GoingSide(face.normal)
self.pcb.active_material_index = 0
self.setMode('EDIT')
bpy.ops.object.material_slot_assign()
self.setMode('OBJECT')
## PCBTop
self.clearMeshSelections()
for face in self.pcb.data.polygons:
face.select = self.GoingUp(face.normal)
self.pcb.active_material_index = 1
self.setMode('EDIT')
bpy.ops.object.material_slot_assign()
bpy.ops.uv.cube_project(scale_to_bounds=True)
self.setMode('OBJECT')
# PCBBottom
self.clearMeshSelections()
for face in self.pcb.data.polygons:
face.select = self.GoingDown(face.normal)
self.pcb.active_material_index = 2
self.setMode('EDIT')
bpy.ops.object.material_slot_assign()
bpy.ops.uv.cube_project(scale_to_bounds=True)
self.setMode('OBJECT')
# PCB Substrate
# This one is a little harder. First
# select all the side faces.
# Second work out which is the biggest -
# that's guaranteed to be an outside face.
# Then repeatedly look through all the side
# faces finding any that touch that first one.
# Add them to an array, then repeat looking
# for any faces that touch any in the array.
self.setMode('EDIT')
self.selectOuterFaces(self.pcb)
self.pcb.active_material_index = 3
bpy.ops.object.material_slot_assign()
self.setMode('OBJECT')
def populate(self):
self.setMode('OBJECT')
with open(self.file_csv, newline='', encoding='ISO-8859-15') as fobj:
reader = csv.reader(filter(lambda row: row[0] == '"', fobj))
layout_table = list(reader)
self.objects = []
missing = []
for refdes, value, fp, x, y, rot, side in layout_table:
refdes = refdes.replace('"', '')
value = value.replace('"', '')
component = value + "/" + fp.replace('"', '')
x = float(x.replace('"', ''))
y = float(y.replace('"', ''))
rot = float(rot.replace('"', ''))
if refdes == "(unknown)":
continue
try:
if self.dnp[refdes] == 1:
continue
except:
pass
z = 0
yrot = 0
if side == "bottom":
z = -1.5;
yrot = 180 / 57.2957795
else:
z = 1.5;
loc = tuple(float(val) for val in (x, y, z))
frot = float(rot)
try:
if self.rotations[refdes]:
frot = self.rotations[refdes]
self.txt.write("Rotation " +refdes + " : " + frot + "\n")
except:
pass
frot = frot / 57.2957795
zrot = tuple(float(val) for val in (0, yrot, frot))
found = os.path.isfile(self.component_root + "/" + component + ".blend")
if (found and (component not in bpy.data.meshes.keys())):
self.txt.write("Loading component " + component + "\n")
with bpy.data.libraries.load(self.component_root + "/" + component + ".blend", link=False) as (data_from, data_to):
data_to.meshes = data_from.meshes
oname = refdes + " - " + component
dupli = bpy.data.objects.new(oname, data_to.meshes[0])
dupli["auto_created"] = True
dupli.data["auto_created"] = True
dupli["refdes"] = refdes
dupli["value"] = value
dupli["footprint"] = fp
dupli.location = loc
dupli.rotation_euler = zrot
self.linkObject(dupli)
self.objects.append(oname)
if found == False:
if component not in missing:
missing.append(component)
if len(missing) > 0:
self.txt.write("\nMissing components:\n")
for m in missing:
self.txt.write(" " + m + ".blend\n")
def createSolder(self):
bpy.ops.mesh.primitive_plane_add(size=1)
self.solder = bpy.context.object
self.solder.dimensions = self.pcb.dimensions
self.solder['auto_created'] = True
self.solder.data['auto_created'] = True
self.solder.name="Solder"
self.solder.location.z = 0.5
self.setMode("EDIT")
bpy.ops.mesh.subdivide(number_cuts=100)
bpy.ops.mesh.subdivide(number_cuts=14)
self.setMode("OBJECT")
with bpy.data.libraries.load(self.component_root + "/Base/materials.blend", link=False) as (data_from, data_to):
data_to.materials = ["Solder"]
sld = bpy.data.materials['Solder'].copy()
sld.node_tree.nodes['Image Texture'].image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-F_Paste_bw.png")
self.solder.data.materials.append(sld)
bpy.ops.object.modifier_add(type="DISPLACE")
self.solder_deform = self.solder.modifiers[0]
self.solder_deform.direction = "Z"
self.solder_deform.strength = 1
bpy.data.textures.new("Solder", "IMAGE")
tex = bpy.data.textures["Solder"]
tex.image = bpy.data.images.load(filepath = self.file_root + "/Render/" + self.file_name + "-F_Paste.png")
tex.use_color_ramp = True
self.solder_deform.texture = tex
self.solder_deform.texture_coords = "UV"
for f in self.solder.data.polygons:
f.use_smooth = True
bpy.ops.object.modifier_apply(modifier=self.solder_deform.name)
self.setMode('EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
t = len(self.solder.data.polygons)
found = False
while found == False:
r = random.randrange(t)
poly = self.solder.data.polygons[r]
found = True
for v in poly.vertices:
z = self.solder.data.vertices[v].co.z
if (z != -0.5):
found = False
if (found == True):
self.selectFace(self.solder, poly.index)
bpy.ops.mesh.faces_select_linked_flat()
if (found == True):
bpy.ops.mesh.delete(type="FACE")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.dissolve_limited()
self.setMode('OBJECT')
def cleanup(self):
self.deselectAll()
if self.doJoin:
for ob in self.objects:
self.setSelect(bpy.data.objects[ob], True)
self.setSelect(bpy.data.objects[self.file_name], True)
self.setActiveObject(bpy.data.objects[self.file_name])
bpy.ops.object.join()
self.setActiveObject(bpy.data.objects[self.file_name])
else:
for ob in self.objects:
bpy.data.objects[ob].parent = self.pcb
bpy.data.objects[ob].location[0] -= self.pcb.location[0]
bpy.data.objects[ob].location[1] -= self.pcb.location[1]
bpy.data.objects[ob].location[2] -= self.pcb.location[2]
for ob in self.objects:
self.setSelect(bpy.data.objects[ob], False)
self.setSelect(self.pcb, True)
self.setActiveObject(self.pcb)
bpy.context.object.data.name = self.file_name
bpy.context.object.name = self.file_name
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
self.pcb.location = [0, 0, 0]
for mesh in bpy.data.meshes:
mesh.make_local()
for object in bpy.data.objects:
object.make_local()
for material in bpy.data.materials:
material.make_local()
for ng in bpy.data.node_groups:
ng.make_local()
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.shade_auto_smooth()
bpy.ops.object.select_all(action="DESELECT")
def deleteExistingObjects(self):
for x in bpy.data.objects:
if (x.get("auto_created") is not None):
bpy.data.objects.remove(x)
for x in bpy.data.meshes:
if (x.get("auto_created") is not None):
bpy.data.meshes.remove(x)
for x in bpy.data.materials:
bpy.data.materials.remove(x)
for x in bpy.data.node_groups:
bpy.data.node_groups.remove(x)
bpy.data.orphans_purge()
bpy.data.orphans_purge()
bpy.data.orphans_purge()
bpy.data.orphans_purge()
bpy.data.orphans_purge()
bpy.data.orphans_purge()
bpy.data.orphans_purge()
def createDefaultObjects(self):
bpy.ops.object.camera_add(location=self.camera)
cam = bpy.context.object
cam["auto_created"] = True
bpy.ops.object.empty_add(location=self.target)
empty = bpy.context.object
empty["auto_created"] = True
con = cam.constraints.new("TRACK_TO")
con.target = empty
# sun = bpy.data.lights.new("Sun", 'SUN')
# sun_obj = bpy.data.objects.new("Sun", sun)
# self.linkObject(sun_obj)
# sun_obj.rotation_euler = self.sun
def run(self):
self.openBuildReport()
self.deleteExistingObjects()
self.deleteOrphans()
self.createDefaultObjects()
self.importOutline()
self.loadMaterials()
self.assignMaterials()
self.populate()
self.deleteOrphans()
self.cleanup()
self.createSolder()
def startTimer(self):
self._timer = self.wm.event_timer_add(2, window=self.context.window)
def stopTimer(self):
self.wm.event_timer_remove(self._timer)
def execute(self, context):
self.run()
return {'FINISHED'}
def cancel(self, context):
self.stopTimer()
def register():
bpy.utils.register_class(PCBImport)
bpy.ops.wm.modal_timer_operator()
def unregister():
bpy.utils.unregister_class(PCBImport)
if __name__ == "__main__":
register()

20
TerminalServer/render Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
PROJ=TerminalServer
mkdir -p Render
for A in B_Cu B_Paste F_Mask F_Silkscreen B_Mask B_Silkscreen F_Cu F_Paste; do
camv-rnd -x png --format PNG --monochrome --use-alpha --dpi 1000 --outfile Render/${PROJ}-${A}.png Gerber/${PROJ}-${A}.gbr
done
W=$(identify -ping -format '%w' Render/${PROJ}-F_Cu.png)
H=$(identify -ping -format '%h' Render/${PROJ}-F_Cu.png)
X=0
Y=167
magick Render/${PROJ}-F_Silkscreen.png -crop ${W}x${H}+${X}+${Y} Render/${PROJ}-F_Silkscreen.png
#magick Render/${PROJ}-F_Paste.png -background white -alpha background -alpha off -blur 0x2 Render/${PROJ}-F_Paste.png
magick Render/${PROJ}-F_Paste.png -background white -alpha background -alpha off Render/${PROJ}-F_Paste_bw.png