def check_cinder_volume_driver(config): version = get_latest_driver_version(TAGS) need_version = version.strip("v") loc = detect_cinder_install() dfile = os.path.join(loc, "volume/drivers/datera/datera_iscsi.py") if not os.path.exists(dfile): errloc = os.path.join(loc, "volume/drivers") return ff( "Couldn't detect Datera Cinder driver install at " "{}".format(errloc), "680E61DB") version = None with io.open(dfile, 'r') as f: for line in f: version = VERSION_RE.match(line) if version: version = version.group(1) break if not version: return ff( "No version detected for Datera Cinder driver at " "{}".format(dfile), "A37FD778") if version != need_version: return ff( "Cinder Driver version mismatch, have: {}, want: " "{}".format(version, need_version), "5B6EFC71")
def check_sysctl(config): vprint("Checking various sysctl settings") settings = sorted([ ("net.ipv4.tcp_timestamps", "0", "F0D7A1AD"), ("net.ipv4.tcp_sack", "1", "7A9AB850"), ("net.core.netdev_max_backlog", "250000", "7656C46C"), ("net.core.somaxconn", "1024", "34A7B822"), ("net.core.rmem_max", "16777216", "4C4B3F0B"), ("net.core.wmem_max", "16777216", "7F8479C2"), ("net.core.rmem_default", "8388608", "FBCA17D5"), ("net.core.wmem_default", "8388608", "68191DE5"), ("net.core.optmem_max", "8388608", "8FA26A66"), ("net.ipv4.tcp_rmem", "\"4096 87380 16777216\"", "2A6057BD"), ("net.ipv4.tcp_wmem", "\"4096 65536 16777216\"", "CD37F436"), ("net.ipv4.tcp_low_latency", "1", "6BE2899E"), ("net.ipv4.tcp_fin_timeout", "15", "59FD5DF7"), ("net.ipv4.tcp_syncookies", "1", "01C594E7"), ("net.ipv4.tcp_adv_win_scale", "1", "1F523B04"), ("net.ipv4.tcp_window_scaling", "1", "A8A6F381"), ("net.ipv4.tcp_max_syn_backlog", "8192", "2862CB28"), ("net.ipv4.tcp_tw_reuse", "1", "989229FC"), ("net.ipv4.tcp_synack_retries", "2", "55EF997B") ]) for setting, value, code in settings: found = exe("sysctl --values {}".format(setting)).strip() found = found.strip().strip("\"").split() value = value.strip().strip("\"").split() if len(found) == 1: found = found[0] if len(value) == 1: value = value[0] if found != value: ff("{}={} is not set. Found: {}".format(setting, value, found), code)
def generate_silkscreen_female( lines: List[str], category: str, kind: str, variant: str, pin_count: int, top_offset: float, ) -> None: uuid_polygon = uuid(category, kind, variant, 'polygon-contour') lines.append(' (polygon {} (layer top_placement)'.format(uuid_polygon)) lines.append( ' (width {}) (fill false) (grab_area true)'.format(line_width)) y_max, y_min = get_rectangle_bounds(pin_count, spacing, top_offset, False) lines.append(' (vertex (position -1.27 {}) (angle 0.0))'.format( ff(y_max))) lines.append(' (vertex (position 1.27 {}) (angle 0.0))'.format( ff(y_max))) lines.append(' (vertex (position 1.27 {}) (angle 0.0))'.format( ff(y_min))) lines.append(' (vertex (position -1.27 {}) (angle 0.0))'.format( ff(y_min))) lines.append(' (vertex (position -1.27 {}) (angle 0.0))'.format( ff(y_max))) lines.append(' )')
def check_cinder_image_cache_conf(config): with io.open(ETC, 'r') as f: section = None for line in f: section = ETC_SECTION_RE.match(line) if section: break if not section: return ff( "[datera] section missing from " "/etc/cinder/cinder.conf", "525BAAB0") cache_check = False vtype_check = False for line in f: if 'datera_enable_image_cache' in line and 'True' in line: cache_check = True if ('datera_image_cache_volume_type_id' in line and UUID4_STR_RE.search(line)): vtype_check = True if not cache_check: ff("datera_enable_image_cache not set in cinder.conf", "C5B86514") if not vtype_check: ff( "datera_image_cache_volume_type_id is not set to a valid volume" " type id in cinder.conf", "B845D5B1")
def check_glance_driver(config): version = get_latest_driver_version(TAGS) need_version = version.strip("v") loc = detect_glance_install() if not loc: return ff("Could not detect Glance install location", "6515ADB8") dfile = os.path.join(loc, "_drivers/datera.py") if not os.path.exists(dfile): errloc = os.path.join(loc, "_drivers") return ff( "Couldn't detect Datera Glance driver install at " "{}".format(errloc), "DD51CEC9") version = None with io.open(dfile, 'r') as f: for line in f: version = VERSION_RE.match(line) if version: version = version.group(1) break if not version: return ff( "No version detected for Datera Glance driver at " "{}".format(dfile), "75A8A315") if version != need_version: return ff( "Glance Driver version mismatch, have: {}, want: " "{}".format(version, need_version), "B65FD598") entry = find_entry_points_file() if not entry: return ff("Could not find entry_points.txt file for glance_store", "842A4DB1") efound = None with io.open(entry) as f: for line in f: if 'datera' in line: efound = line break if not efound: return ff("Could not find 'datera' entry in {}".format(entry), "22DC6275") k, v = efound.split("=") if k.strip() != 'datera': return ff("entry_points.txt entry malformed", "3F9F67BF") if v.strip() != 'glance_store._drivers.datera:Store': return ff("entry_points.txt entry malformed", "3F9F67BF") backend = os.path.join(loc, "backend.py") bfound = False with io.open(backend) as f: for line in f: if 'datera' in line: bfound = True break if 'class Indexable' in line: break if not bfound: ff( "'datera' has not been added to the 'default_store' StrOpt's " "'choices' parameter", "C521E039")
def vip1_check(config): vip1 = config["vip1_ip"] if not exe_check("ping -c 2 -W 1 {}".format(vip1), err=False): ff("Could not ping vip1 ip {}".format(vip1), "1827147B", fix=NET_FIX) timeout = 5 while not exe_check( "ip neigh show | grep {} | grep REACHABLE".format(vip1)): timeout -= 1 time.sleep(1) if timeout < 0: wf("Arp state for vip1 [{}] is not 'REACHABLE'".format(vip1), "3C33D70D") break
def check_docker_config(config): if not os.path.exists(CONFIG_FILE): return ff("Missing Datera config file at '/root/.datera-config-file'", "A433E6C6") dconfig = None with io.open(CONFIG_FILE) as f: try: dconfig = json.loads(f.read()) except json.JSONDecodeError: return ff("Malformed config file, not valid JSON", "AA27965F") for key in ("datera-cluster", "username", "password", "debug", "os-user", "tenant"): if key not in dconfig: ff("Config missing {} key".format(key), "945148B0")
def check_docker_volume(config): vprint("Checking docker volume driver") if not exe_check("docker ps >/dev/null 2>&1"): return ff("Docker is not installed", "42BAAC76") if exe_check("docker plugin ls | grep {}".format(PLUGIN)): return ff("Datera Docker plugin is not installed", "6C531C5D") plugin = exe("docker plugin ls | grev -v DESCRIPTION | " "grep {}".format(PLUGIN)) if len(plugin.strip().split('\n')) > 1: wf("More than one version of Datera docker driver installed", "B3BF691D") if 'enabled' not in plugin or 'disabled' in plugin: ff("Datera docker plugin is not enabled") test_name = "ddct-test1" if not exe_check( "docker volume create -d {} --name {} --opt replica=1 --opt " "size=1".format(PLUGIN, test_name)): return ff("Could not create a volume with the Datera Docker plugin", "621A6F51") api = config['api'] try: api.app_instances.get(test_name) except ApiNotFoundError: return ff("Docker volume {} did not create on the Datera backend" "".format(test_name), "B106D1CD") if not exe_check("docker volume rm {}".format(test_name)): ff("Could not delete Docker volume {}".format(test_name), "AF3DB8B3")
def check_irq(config): vprint("Checking irqbalance settings, (should be turned off)") if not exe_check("which systemctl"): if not exe_check( "service irqbalance status | " "grep 'Active: active'", err=True): fix = "service irqbalance stop" return ff("irqbalance is active", "B19D9FF1", fix=fix) else: if not exe_check( "systemctl status irqbalance | " "grep 'Active: active'", err=True): fix = "systemctl stop irqbalance && systemctl disable irqbalance" return ff("irqbalance is active", "B19D9FF1", fix=fix)
def check_udev(config): vprint("Checking udev rules config") frules = "/etc/udev/rules.d/99-iscsi-luns.rules" if not os.path.exists(frules): fix = "A copy of the udev rules are available from: {}".format( UDEV_URL) ff("Datera udev rules are not installed", "1C8F2E07", fix=fix) snum = "/sbin/fetch_device_serial_no.sh" if not os.path.exists(snum): fix = ("A copy of fetch_device_serial_no.sh is available at: " "{}".format(FETCH_SO_URL)) ff("fetch_device_serial_no.sh is missing from /sbin", "6D03F50B", fix=fix)
def vip2_check(config): vip2 = config.get("vip2_ip") if not vip2: wf("No vip2_ip found", "16EB208B") return if vip2 and not exe_check("ping -c 2 -W 1 {}".format(vip2), err=False): ff("Could not ping vip2 ip {}".format(vip2), "3D76CE5A", fix=NET_FIX) timeout = 5 while not exe_check( "ip neigh show | grep {} | grep REACHABLE".format(vip2)): timeout -= 1 time.sleep(1) if timeout < 0: wf("Arp state for vip2 [{}] is not 'REACHABLE'".format(vip2), "4F6B8D91") break
def _draw(config: DfnConfig, uuid: Callable[[str], str], lines: List[str]) -> None: lines.append(' (circle {} (layer top_documentation)'.format( uuid('hole-circle-doc'))) lines.append( ' (width 0.1) (fill false) (grab_area false) (diameter {}) (position 0.0 0.0)' .format(ff(diameter))) lines.append(' )')
def mgmt_check(config): mgmt = config["mgmt_ip"] if not exe_check("ping -c 2 -W 1 {}".format(mgmt), err=False): ff("Could not ping management ip {}".format(mgmt), "65FC68BB", fix=NET_FIX) timeout = 5 while not exe_check( "ip neigh show | grep {} | grep REACHABLE".format(mgmt)): timeout -= 1 time.sleep(1) if timeout < 0: fix = "Check the connection to {}".format(mgmt) wf("Arp state for mgmt [{}] is not 'REACHABLE'".format(mgmt), "BF6A912A", fix=fix) break
def check_cpufreq(config): vprint("Checking cpufreq settings") if not exe_check("which cpupower"): if get_os() == UBUNTU: version = exe("uname -r").strip() fix = "apt-get install linux-tools-{}".format(version) else: # RHEL puts this stuff in kernel-tools fix = "yum install kernel-tools" return ff("cpupower is not installed", "20CEE732", fix=fix) if not exe_check( "cpupower frequency-info --governors | " "grep performance", err=False): fix = ("No-fix -- if this system is a VM governors might not be " "available and this check can be ignored") return ff("No 'performance' governor found for system", "333FBD45", fix=fix)
def check_mtu_l3(ip, config): """ We just care that the packet gets there. Fragmentation is gonna happen with L3 routing. """ vprint("Performing L3 MTU check") sif = get_interface_for_ip(ip) if not sif: return ff( "Couldn't find interface with network matching ip {}" "".format(ip), "710BFC7E") # Ping check if not exe_check("ping -s 32000 -c 2 -W 1 {}".format(ip)): if not exe_check("ping -c 2 -W 1 {}".format(ip)): return ff("Could not ping interface [{}]".format(ip), "EC2D3621") ff( "Could not ping interface [{}] with large (32k) packet size, packet" "fragmentation may not be working correctly.".format(ip), "A4CA0D72")
def check_block_devices(config): vprint("Checking block device settings") grub = "/etc/default/grub" if not os.path.exists(grub): return ff("Could not find default grub file at {}".format(grub), "6F7B6A25") with io.open(grub, "r") as f: data = f.readlines() line = filter(lambda x: x.startswith("GRUB_CMDLINE_LINUX_DEFAULT="), data) line2 = filter(lambda x: x.startswith("GRUB_CMDLINE_LINUX="), data) if len(line) != 1 and len(line2) != 1: return ff( "GRUB_CMDLINE_LINUX_DEFAULT and GRUB_CMDLINE_LINUX are" " missing from GRUB file", "A65B6D97") if ((len(line) > 0 and "elevator=noop" not in line[0]) and (len(line2) > 0 and "elevator=noop" not in line2[0])): fix = ("Add 'elevator=noop' to /etc/default/grub in the " "'GRUB_CMDLINE_LINUX_DEFAULT' line") return ff("Scheduler is not set to noop", "47BB5083", fix=fix)
def check_csi_yaml(config): yml = config.get('csi-yaml') if not yml: ff( "In order to perform the CSI yaml check you must provide the yaml" " file location with the '--csi-yaml' argument", "1C84788A") return entries = get_k8s_yaml(yml) nodes = entries['nodes'] controller = entries['controller'] for k1, k2, fc1, fc2 in (('DAT_MGMT', 'mgmt_ip', "A4CEE995", "D520EF2E"), ('DAT_USER', 'username', "4FAC9E6F", "FA4AD639"), ('DAT_PASS', 'password', "3F37E06E", "F6B7A8FE")): if nodes[k1] != config[k2]: ff( "CSI 'node' service environment variable {} does not match " "UDC config. [{} != {}]".format(k1, nodes[k1], config[k2]), fc1) if controller[k1] != config[k2]: ff( "CSI 'controller' service environment variable DAT_MGMT does " "not match UDC config. [{} != {}]".format( k1, controller[k1], config[k2]), fc2)
def generate_silkscreen_male( lines: List[str], category: str, kind: str, variant: str, pin_count: int, top_offset: float, ) -> None: uuid_polygon = uuid(category, kind, variant, 'polygon-contour') # Start in top right corner, go around the pads clockwise lines.append(' (polygon {} (layer top_placement)'.format(uuid_polygon)) lines.append( ' (width {}) (fill false) (grab_area true)'.format(line_width)) # Down on the right for pin in range(1, pin_count + 1): y = get_y(pin, pin_count, spacing, False) lines.append(' (vertex (position 1.27 {}) (angle 0.0))'.format( ff(y + 1))) lines.append(' (vertex (position 1.27 {}) (angle 0.0))'.format( ff(y - 1))) lines.append(' (vertex (position 1.0 {}) (angle 0.0))'.format( ff(y - 1.27))) # Up on the left for pin in range(pin_count, 0, -1): y = get_y(pin, pin_count, spacing, False) lines.append(' (vertex (position -1.0 {}) (angle 0.0))'.format( ff(y - 1.27))) lines.append(' (vertex (position -1.27 {}) (angle 0.0))'.format( ff(y - 1))) lines.append(' (vertex (position -1.27 {}) (angle 0.0))'.format( ff(y + 1))) # Back to start top_y = get_y(1, pin_count, spacing, False) + spacing / 2 lines.append(' (vertex (position -1.0 {}) (angle 0.0))'.format( ff(top_y))) lines.append(' (vertex (position 1.0 {}) (angle 0.0))'.format(ff(top_y))) lines.append(' (vertex (position 1.27 {}) (angle 0.0))'.format( ff(top_y - 0.27))) lines.append(' )')
def check_mtu_normal(name, ip, config): vprint("Performing MTU check") cname = iface_dict[name] sif = get_interface_for_ip(ip) if not sif: return ff( "Couldn't find interface with network matching ip {}" "".format(ip), "710BFC7E") try: match = MTU_RE.match(exe("ip ad show {} | grep mtu".format(sif))) except subprocess.CalledProcessError: return ff("Couldn't find client {} interface MTU".format(name), "CBF8CC4C") if not match: return ff("Couldn't find client {} interface MTU".format(name), "CBF8CC4C") local_mtu = match.groups(1)[0] cluster_mtu = None api = config['api'] if name == "MGMT": cluster_mtu = api.system.network.mgmt_vip.get( )['network_paths'][0]['mtu'] elif name == "VIP1": cluster_mtu = api.system.network.get( )['access_vip']['network_paths'][0]['mtu'] elif name == "VIP2": cluster_mtu = api.system.network.get( )['access_vip']['network_paths'][0]['mtu'] if not cluster_mtu: return ff("Couldn't find cluster {} interface MTU".format(cname), "057AF23D") if str(local_mtu) != str(cluster_mtu): ff( "Local interface {} MTU does not match cluster {} interface MTU " "[{} != {}]".format(sif, cname, local_mtu, cluster_mtu), "D7F667BC") # Ping check if not exe_check("ping -s 32000 -c 2 -W 1 {}".format(ip)): ff( "Could not ping interface with large (32k) packet size, packet " "fragmentation may not be working correctly", "A4CA0D72")
def check_multipath(config): vprint("Checking multipath settings") if not exe_check("which multipath", err=False): ff("Multipath binary could not be found, is it installed?", "2D18685C") if not exe_check("which systemctl"): if not exe_check("service multipathd status | grep 'Active: active'", err=False): fix = "service multipathd start" ff("multipathd not enabled", "541C10BF", fix=fix) else: if not exe_check("systemctl status multipathd | grep 'Active: active'", err=False): fix = "systemctl start multipathd" ff("multipathd not enabled", "541C10BF", fix=fix)
def check_arp(config): vprint("Checking ARP settings") if not exe_check( "sysctl --all 2>/dev/null | " "grep 'net.ipv4.conf.all.arp_announce = 2'", err=False): fix = "sysctl net.ipv4.conf.all.arp_announce=2" ff("net.ipv4.conf.all.arp_announce != 2 in sysctl", "9000C3B6", fix=fix) if not exe_check( "sysctl --all 2>/dev/null | " "grep 'net.ipv4.conf.all.arp_ignore = 1'", err=False): fix = "sysctl net.ipv4.conf.all.arp_ignore=1" ff("net.ipv4.conf.all.arp_ignore != 1 in sysctl", "BDB4D5D8", fix=fix) gcf = "/proc/sys/net/ipv4/route/gc_interval" gc = int(exe("cat {}".format(gcf))) if gc != 5: fix = "echo 5 > {}".format(gcf) ff("{} is currently set to {}".format(gcf, gc), "A06CD19F", fix=fix)
def add_footprint_variant(key: str, name: str, pad_size: Tuple[float, float]): uuid_footprint = _uuid('footprint-{}'.format(key)) uuid_silkscreen = _uuid('polygon-silkscreen-{}'.format(key)) uuid_pin1_dot = _uuid('pin1-dot-silkscreen-{}'.format(key)) uuid_outline = _uuid('polygon-outline-{}'.format(key)) uuid_text_name = _uuid('text-name-{}'.format(key)) uuid_text_value = _uuid('text-value-{}'.format(key)) lines.append(' (footprint {}'.format(uuid_footprint)) lines.append(' (name "{}")'.format(name)) lines.append(' (description "")') # Pads pad_x_offset = float(width) / 2 for p in range(1, pin_count // 2 + 1): # Down on the left y = get_y(p, pin_count // 2, spacing, False) shape = 'rect' if p == 1 else 'round' pad_uuid = uuid_pads[p - 1] lines.append(' (pad {} (side tht) (shape {})'.format( pad_uuid, shape)) lines.append( ' (position {} {}) (rotation 0.0) (size {} {}) (drill {})' .format( ff(-pad_x_offset), ff(y), pad_size[0], pad_size[1], drill_diameter, )) lines.append(' )') for p in range(1, pin_count // 2 + 1): # Up on the right y = -get_y(p, pin_count // 2, spacing, False) shape = 'round' pad_uuid = uuid_pads[p + pin_count // 2 - 1] lines.append(' (pad {} (side tht) (shape {})'.format( pad_uuid, shape)) lines.append( ' (position {} {}) (rotation 0.0) (size {} {}) (drill {})' .format( ff(pad_x_offset), ff(y), pad_size[0], pad_size[1], drill_diameter, )) lines.append(' )') # Silkscreen: Rectangle lines.append( ' (polygon {} (layer top_placement)'.format(uuid_silkscreen)) lines.append(' (width {}) (fill false) (grab_area false)'.format( line_width)) y_max, y_min = get_rectangle_bounds(pin_count // 2, spacing, top_offset, False) silkscreen_x_offset = pad_x_offset - pad_size[0] / 2 \ - silkscreen_offset - line_width / 2 sxo = ff(silkscreen_x_offset) # Used for shorter lines below :) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( sxo, ff(y_max))) # NW lines.append(' (vertex (position -{} {}) (angle 180.0))'.format( ff(silkscreen_x_offset / 3), ff(y_max), )) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(silkscreen_x_offset / 3), ff(y_max), )) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( sxo, ff(y_max))) # NE lines.append(' (vertex (position {} {}) (angle 0.0))'.format( sxo, ff(y_min))) # SE lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( sxo, ff(y_min))) # SW lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( sxo, ff(y_max))) # NW lines.append(' )') # Documentation # TODO: The code below is almost identical to the silkscreen, code could be reused lines.append( ' (polygon {} (layer top_documentation)'.format(uuid_outline)) lines.append(' (width {}) (fill false) (grab_area true)'.format( line_width)) y_max, y_min = get_rectangle_bounds(pin_count // 2, spacing, top_offset, False) outline_x_offset = pad_x_offset - pin_package_offset oxo = ff(outline_x_offset) # Used for shorter lines below :) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( oxo, ff(y_max))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( oxo, ff(y_max))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( oxo, ff(y_min))) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( oxo, ff(y_min))) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( oxo, ff(y_max))) lines.append(' )') # Silkscreen: Pin 1 dot pin1_dot_diameter = float(width) / 7.62 lines.append( ' (circle {} (layer top_placement)'.format(uuid_pin1_dot)) lines.append(' (width 0.0) (fill true) (grab_area false) ' '(diameter {}) (position -{} {})'.format( ff(pin1_dot_diameter), ff(silkscreen_x_offset - pin1_dot_diameter), ff(y_max - pin1_dot_diameter), )) lines.append(' )') # Labels y_max, y_min = get_rectangle_bounds(pin_count // 2, spacing, top_offset + 1.27, False) text_attrs = '(height {}) (stroke_width 0.2) ' \ '(letter_spacing auto) (line_spacing auto)'.format(pkg_text_height) lines.append( ' (stroke_text {} (layer top_names)'.format(uuid_text_name)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center bottom) (position 0.0 {}) (rotation 0.0)'. format(ff(y_max), )) lines.append( ' (auto_rotate true) (mirror false) (value "{{NAME}}")') lines.append(' )') lines.append( ' (stroke_text {} (layer top_values)'.format(uuid_text_value)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center top) (position 0.0 {}) (rotation 0.0)'. format(ff(y_min), )) lines.append( ' (auto_rotate true) (mirror false) (value "{{VALUE}}")') lines.append(' )') lines.append(' )')
def check_glance_conf(config): pass section = None with io.open(ETC, 'r') as f: for line in f: default = ETC_DEFAULT_RE.match(line) if default: break if not default: ff("[DEFAULT] section missing from {}".format(ETC), "228241A8") for line in f: section = ETC_SECTION_RE.match(line) if section: break if not section: return ff("[glance_store] section missing from {}".format(ETC), "AFCBBDD7") dsection = [] section_match = re.compile(r"^\[.*\]") for line in f: if section_match.match(line): break dsection.append(line) ip = config['mgmt_ip'] user = config['username'] passwd = config['password'] san_check = False user_check = False pass_check = False stores_check = False default_check = False for line in dsection: if line.startswith("stores"): stores_check = True if "datera" not in line: ff("datera is not set under 'stores' in {}".format(ETC), "0D862946") if line.startswith("default_store"): default_check = True if "datera" not in line: wf("datera is not set as default_store in {}".format(ETC), "B74CEBC3") if line.startswith("datera_san_ip"): san_check = True if line.split("=")[-1].strip() != ip: ff("datera_san_ip doesn't match mgmt ip", "2330CACB") if line.startswith("datera_san_login"): user_check = True if line.split("=")[-1].strip() != user: ff("datera_san_login doesn't match username", "E9F02293") if line.startswith("datera_san_password"): pass_check = True if line.split("=")[-1].strip() != passwd: ff("datera_san_password doesn't match password", "4B16C4F7") if not stores_check: ff("'stores' entry not found under [glance_store]", "11F30DCF") if not default_check: ff("'default_store' entry not found under [glance_store]", "540C3008") if not san_check: ff("'datera_san_ip' entry not found under [glance_store]", "42481C71") if not user_check: ff("'datera_san_login' entry not found under [glance_store]", "6E281004") if not pass_check: ff("'datera_san_password' entry not found under [glance_store]", "F5DEC8B1")
def generate_pkg( dirpath: str, author: str, name: str, name_lower: str, kind: str, pkgcat: str, keywords: str, min_pads: int, max_pads: int, top_offset: float, pad_drills: Iterable[float], generate_silkscreen: Callable[[List[str], str, str, str, int, float], None], create_date: Optional[str], ): category = 'pkg' for i in range(min_pads, max_pads + 1): for drill in pad_drills: lines = [] variant = '1x{}-D{:.1f}'.format(i, drill) def _uuid(identifier): return uuid(category, kind, variant, identifier) uuid_pkg = _uuid('pkg') uuid_pads = [_uuid('pad-{}'.format(p)) for p in range(i)] uuid_footprint = _uuid('footprint-default') uuid_text_name = _uuid('text-name') uuid_text_value = _uuid('text-value') # General info lines.append('(librepcb_package {}'.format(uuid_pkg)) lines.append(' (name "{} 1x{} ⌀{:.1f}mm")'.format(name, i, drill)) lines.append(' (description "A 1x{} {} with {}mm pin spacing ' 'and {:.1f}mm drill holes.\\n\\n' 'Generated with {}")'.format(i, name_lower, spacing, drill, generator)) lines.append(' (keywords "connector, 1x{}, d{:.1f}, {}")'.format( i, drill, keywords)) lines.append(' (author "{}")'.format(author)) lines.append(' (version "0.1")') lines.append(' (created {})'.format(create_date or now())) lines.append(' (deprecated false)') lines.append(' (category {})'.format(pkgcat)) for j in range(1, i + 1): lines.append(' (pad {} (name "{}"))'.format( uuid_pads[j - 1], j)) lines.append(' (footprint {}'.format(uuid_footprint)) lines.append(' (name "default")') lines.append(' (description "")') # Pads for j in range(1, i + 1): y = get_y(j, i, spacing, False) shape = 'rect' if j == 1 else 'round' lines.append(' (pad {} (side tht) (shape {})'.format( uuid_pads[j - 1], shape)) lines.append( ' (position 0.0 {}) (rotation 0.0) (size {} {}) (drill {})' .format( y, pad_size[0], pad_size[1], drill, )) lines.append(' )') # Silkscreen generate_silkscreen(lines, category, kind, variant, i, top_offset) # Labels y_max, y_min = get_rectangle_bounds(i, spacing, top_offset + 1.27, False) text_attrs = '(height {}) (stroke_width 0.2) ' \ '(letter_spacing auto) (line_spacing auto)'.format(pkg_text_height) lines.append( ' (stroke_text {} (layer top_names)'.format(uuid_text_name)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center bottom) (position 0.0 {}) (rotation 0.0)'. format(ff(y_max), )) lines.append( ' (auto_rotate true) (mirror false) (value "{{NAME}}")') lines.append(' )') lines.append( ' (stroke_text {} (layer top_values)'.format(uuid_text_value)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center top) (position 0.0 {}) (rotation 0.0)'. format(ff(y_min), )) lines.append( ' (auto_rotate true) (mirror false) (value "{{VALUE}}")') lines.append(' )') lines.append(' )') lines.append(')') pkg_dir_path = path.join(dirpath, uuid_pkg) if not (path.exists(pkg_dir_path) and path.isdir(pkg_dir_path)): makedirs(pkg_dir_path) with open(path.join(pkg_dir_path, '.librepcb-pkg'), 'w') as f: f.write('0.1\n') with open(path.join(pkg_dir_path, 'package.lp'), 'w') as f: f.write('\n'.join(lines)) f.write('\n') print('1x{} {} ⌀{:.1f}mm: Wrote package {}'.format( i, kind, drill, uuid_pkg))
def generate_sym( dirpath: str, author: str, name: str, name_lower: str, kind: str, cmpcat: str, keywords: str, min_pads: int, max_pads: int, create_date: Optional[str], ): category = 'sym' for i in range(min_pads, max_pads + 1): lines = [] variant = '1x{}'.format(i) def _uuid(identifier): return uuid(category, kind, variant, identifier) uuid_sym = _uuid('sym') uuid_pins = [_uuid('pin-{}'.format(p)) for p in range(i)] uuid_polygon = _uuid('polygon-contour') uuid_decoration = _uuid('polygon-decoration') uuid_text_name = _uuid('text-name') uuid_text_value = _uuid('text-value') # General info lines.append('(librepcb_symbol {}'.format(uuid_sym)) lines.append(' (name "{} 1x{}")'.format(name, i)) lines.append(' (description "A 1x{} {}.\\n\\n' 'Generated with {}")'.format(i, name_lower, generator)) lines.append(' (keywords "connector, 1x{}, {}")'.format(i, keywords)) lines.append(' (author "{}")'.format(author)) lines.append(' (version "0.1")') lines.append(' (created {})'.format(create_date or now())) lines.append(' (deprecated false)') lines.append(' (category {})'.format(cmpcat)) for j in range(1, i + 1): lines.append(' (pin {} (name "{}")'.format(uuid_pins[j - 1], j)) lines.append( ' (position 5.08 {}) (rotation 180.0) (length 3.81)'.format( get_y(j, i, spacing, True))) lines.append(' )') # Polygons y_max, y_min = get_rectangle_bounds(i, spacing, spacing, True) lines.append(' (polygon {} (layer sym_outlines)'.format(uuid_polygon)) lines.append( ' (width {}) (fill false) (grab_area true)'.format(line_width)) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( spacing, ff(y_max))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( spacing, ff(y_max))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( spacing, ff(y_min))) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( spacing, ff(y_min))) lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( spacing, ff(y_max))) lines.append(' )') # Decorations if kind == KIND_HEADER: # Headers: Small rectangle for j in range(1, i + 1): y = get_y(j, i, spacing, True) dx = spacing / 8 * 1.5 dy = spacing / 8 / 1.5 lines.append(' (polygon {} (layer sym_outlines)'.format( uuid_decoration)) lines.append( ' (width {}) (fill true) (grab_area true)'.format( line_width)) vertex = ' (vertex (position {} {}) (angle 0.0))' lines.append(vertex.format(ff(spacing / 2 - dx), ff(y + dy))) lines.append(vertex.format(ff(spacing / 2 + dx), ff(y + dy))) lines.append(vertex.format(ff(spacing / 2 + dx), ff(y - dy))) lines.append(vertex.format(ff(spacing / 2 - dx), ff(y - dy))) lines.append(vertex.format(ff(spacing / 2 - dx), ff(y + dy))) lines.append(' )') elif kind == KIND_SOCKET: # Sockets: Small semicircle for j in range(1, i + 1): y = get_y(j, i, spacing, True) d = spacing / 4 * 0.75 w = line_width * 0.75 lines.append(' (polygon {} (layer sym_outlines)'.format( uuid_decoration)) lines.append( ' (width {}) (fill false) (grab_area false)'.format(w)) lines.append( ' (vertex (position {} {}) (angle 135.0))'.format( ff(spacing / 2 + d * 0.5 - d - w), ff(y - d)), ) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(spacing / 2 + d * 0.5 - d - w), ff(y + d))) lines.append(' )') # Text y_max, y_min = get_rectangle_bounds(i, spacing, spacing, True) lines.append( ' (text {} (layer sym_names) (value "{{{{NAME}}}}")'.format( uuid_text_name)) lines.append( ' (align center bottom) (height {}) (position 0.0 {}) (rotation 0.0)' .format( ff(sym_text_height), ff(y_max), )) lines.append(' )') lines.append( ' (text {} (layer sym_names) (value "{{{{VALUE}}}}")'.format( uuid_text_value)) lines.append( ' (align center top) (height {}) (position 0.0 {}) (rotation 0.0)' .format( ff(sym_text_height), ff(y_min), )) lines.append(' )') lines.append(')') sym_dir_path = path.join(dirpath, uuid_sym) if not (path.exists(sym_dir_path) and path.isdir(sym_dir_path)): makedirs(sym_dir_path) with open(path.join(sym_dir_path, '.librepcb-sym'), 'w') as f: f.write('0.1\n') with open(path.join(sym_dir_path, 'symbol.lp'), 'w') as f: f.write('\n'.join(lines)) f.write('\n') print('1x{} {}: Wrote symbol {}'.format(i, kind, uuid_sym))
def check_single_volume_performance_fio_4k(config): vprint("Checking FIO performance, single volume") if not exe_check("which fio"): ff("FIO is not installed", "0BB2848F") api = config['api']
def test_format_float(inval: float, outval: str): assert ff(inval) == outval
def _generate_footprint(key: str, name: str, pad_extension: float) -> None: # Create Meta-data uuid_footprint = _uuid('footprint-{}'.format(key)) lines.append(' (footprint {}'.format(uuid_footprint)) lines.append(' (name "{}")'.format(name)) lines.append(' (description "")') pad_length = config.lead_length + config.toe_heel + pad_extension exposed_length = config.exposed_length abs_pad_pos_x = (config.width / 2) - (config.lead_length / 2) + (config.toe_heel / 2) + (pad_extension / 2) # Check clearance and make pads smaller if required if make_exposed: clearance = (config.width / 2) - config.lead_length - (exposed_length / 2) if clearance < MIN_CLEARANCE: print("Increasing clearance from {:.2f} to {:.2f}".format(clearance, MIN_CLEARANCE)) d_clearance = (MIN_CLEARANCE - clearance) / 2 pad_length = pad_length - d_clearance exposed_length = exposed_length - 2 * d_clearance abs_pad_pos_x = abs_pad_pos_x + (d_clearance / 2) if exposed_length < MIN_TRACE: print("Increasing exposed path width from {:.2f} to {:.2f}".format(exposed_length, MIN_TRACE)) d_exp = MIN_TRACE - exposed_length exposed_length = exposed_length + d_exp pad_length = pad_length - (d_exp / 2) abs_pad_pos_x = abs_pad_pos_x + (d_exp / 4) # Place pads for pad_idx, pad_nr in enumerate(range(1, config.pin_count + 1)): half_n_pads = config.pin_count // 2 pad_pos_y = get_y(pad_idx % half_n_pads + 1, half_n_pads, config.pitch, False) if pad_idx < (config.pin_count / 2): pad_pos_x = - abs_pad_pos_x else: pad_pos_x = abs_pad_pos_x pad_pos_y = - pad_pos_y lines.append(' (pad {} (side top) (shape rect)'.format(uuid_pads[pad_idx])) lines.append(' (position {} {}) (rotation 0.0) (size {} {}) (drill 0.0)'.format( ff(pad_pos_x), ff(pad_pos_y), ff(pad_length), ff(config.lead_width))) lines.append(' )') # Make exposed pad, if required # TODO: Handle pin1_corner_dx_dy in config once custom pad shapes are possible if make_exposed: lines.append(' (pad {} (side top) (shape rect)'.format(uuid_exp)) lines.append(' (position 0.0 0.0) (rotation 0.0) (size {} {}) (drill 0.0)'.format( ff(exposed_length), ff(config.exposed_width))) lines.append(' )') # Measure clearance pad-exposed pad clearance = abs(pad_pos_x) - (pad_length / 2) - (exposed_length / 2) if np.around(clearance, decimals=2) < MIN_CLEARANCE: print("Warning: minimal clearance violated in {}: {:.4f} < {:.2f}".format(full_name, clearance, MIN_CLEARANCE)) # Create Silk Screen (lines and dot only) silk_down = (config.length / 2 - SILKSCREEN_OFFSET - get_y(1, half_n_pads, config.pitch, False) - config.lead_width / 2 - SILKSCREEN_LINE_WIDTH / 2) # required for round ending of line # Measure clearance silkscreen to exposed pad silk_top_line_height = config.length / 2 if make_exposed: silk_clearance = silk_top_line_height - (SILKSCREEN_LINE_WIDTH / 2) - (config.exposed_width / 2) if np.around(silk_clearance, decimals=2) < SILKSCREEN_OFFSET: silk_top_line_height = silk_top_line_height + (SILKSCREEN_OFFSET - silk_clearance) silk_down = silk_down + (SILKSCREEN_OFFSET - silk_clearance) print("Increasing exp-silk clearance from {:.4f} to {:.2f}".format(silk_clearance, SILKSCREEN_OFFSET)) for idx, silkscreen_pos in enumerate([-1, 1]): uuid_silkscreen_poly = _uuid('polygon-silkscreen-{}-{}'.format(key, idx)) lines.append(' (polygon {} (layer top_placement)'.format(uuid_silkscreen_poly)) lines.append(' (width {}) (fill false) (grab_area false)'.format( SILKSCREEN_LINE_WIDTH)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(-config.width / 2), ff(silkscreen_pos * (silk_top_line_height - silk_down)))) # If this is negative, the silkscreen line has to be moved away from # the real position, in order to keep the required distance to the # pad. We then only draw a single line, so we can omit the parts below. if silk_down > 0: lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(-config.width / 2), ff(silkscreen_pos * silk_top_line_height))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(config.width / 2), ff(silkscreen_pos * silk_top_line_height))) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( ff(config.width / 2), ff(silkscreen_pos * (silk_top_line_height - silk_down)))) lines.append(' )') # Create leads on docu uuid_leads = [_uuid('lead-{}'.format(p)) for p in range(1, config.pin_count + 1)] for pad_idx, pad_nr in enumerate(range(1, config.pin_count + 1)): lead_uuid = uuid_leads[pad_idx] # Make silkscreen lead exact pad width and length half_n_pads = config.pin_count // 2 pad_pos_y = get_y(pad_idx % half_n_pads + 1, half_n_pads, config.pitch, False) if pad_idx >= (config.pin_count / 2): pad_pos_y = - pad_pos_y y_min = pad_pos_y - config.lead_width / 2 y_max = pad_pos_y + config.lead_width / 2 x_max = config.width / 2 x_min = x_max - config.lead_length if pad_idx < (config.pin_count / 2): x_min, x_max = - x_min, - x_max # Convert numbers to librepcb format x_min_str, x_max_str = ff(x_min), ff(x_max) y_min_str, y_max_str = ff(y_min), ff(y_max) lines.append(' (polygon {} (layer top_documentation)'.format(lead_uuid)) lines.append(' (width 0.0) (fill true) (grab_area false)') lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min_str, y_max_str)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_max_str, y_max_str)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_max_str, y_min_str)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min_str, y_min_str)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min_str, y_max_str)) lines.append(' )') # Create exposed pad on docu if make_exposed: uuid_docu_exposed = _uuid('lead-exposed') x_min, x_max = - config.exposed_length / 2, config.exposed_length / 2 y_min, y_max = - config.exposed_width / 2, config.exposed_width / 2 lines.append(' (polygon {} (layer top_documentation)'.format(uuid_docu_exposed)) lines.append(' (width 0.0) (fill true) (grab_area false)') lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min, y_max)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_max, y_max)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_max, y_min)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min, y_min)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(x_min, y_max)) lines.append(' )') # Create body outline on docu uuid_body_outline = _uuid('body-outline') outline_line_width = 0.2 dx = config.width / 2 - outline_line_width / 2 dy = config.length / 2 - outline_line_width / 2 lines.append(' (polygon {} (layer top_documentation)'.format(uuid_body_outline)) lines.append(' (width {}) (fill false) (grab_area false)'.format(outline_line_width)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(-dx, dy)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(dx, dy)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(dx, -dy)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(-dx, -dy)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format(-dx, dy)) lines.append(' )') if config.extended_doc_fn: config.extended_doc_fn(config, _uuid, lines) # As discussed in https://github.com/LibrePCB-Libraries/LibrePCB_Base.lplib/pull/16 # the silkscreen circle should have size SILKSCREEN_LINE_WIDTH for small packages, # and twice the size for larger packages. We define small to be either W or L <3.0mm # and large if both W and L >= 3.0mm if config.width >= 3.0 and config.length >= 3.0: silkscreen_circ_dia = 2.0 * SILKSCREEN_LINE_WIDTH else: silkscreen_circ_dia = SILKSCREEN_LINE_WIDTH if silkscreen_circ_dia == SILKSCREEN_LINE_WIDTH: silk_circ_y = config.length / 2 + silkscreen_circ_dia silk_circ_x = -config.width / 2 - SILKSCREEN_LINE_WIDTH else: silk_circ_y = config.length / 2 + SILKSCREEN_LINE_WIDTH / 2 silk_circ_x = -config.width / 2 - silkscreen_circ_dia # Move silkscreen circle upwards if the line is moved too if silk_down < 0: silk_circ_y = silk_circ_y - silk_down uuid_silkscreen_circ = _uuid('circle-silkscreen-{}'.format(key)) lines.append(' (circle {} (layer top_placement)'.format(uuid_silkscreen_circ)) lines.append(' (width 0.0) (fill true) (grab_area false) ' '(diameter {}) (position {} {})'.format( ff(silkscreen_circ_dia), ff(silk_circ_x), ff(silk_circ_y) )) lines.append(' )') # Add name and value labels uuid_text_name = _uuid('text-name-{}'.format(key)) uuid_text_value = _uuid('text-value-{}'.format(key)) lines.append(' (stroke_text {} (layer top_names)'.format(uuid_text_name)) lines.append(' {}'.format(TEXT_ATTRS)) lines.append(' (align center bottom) (position 0.0 {}) (rotation 0.0)'.format( config.length / 2 + LABEL_OFFSET)) lines.append(' (auto_rotate true) (mirror false) (value "{{NAME}}")') lines.append(' )') lines.append(' (stroke_text {} (layer top_values)'.format(uuid_text_value)) lines.append(' {}'.format(TEXT_ATTRS)) lines.append(' (align center top) (position 0.0 {}) (rotation 0.0)'.format( -config.length / 2 - LABEL_OFFSET)) lines.append(' (auto_rotate true) (mirror false) (value "{{VALUE}}")') lines.append(' )') # Closing parenthese for footprint lines.append(' )')
def check_multipath_conf(config): dist = get_os() vfile = CONFS.get(dist) if not vfile: wf("No supported multipath.conf file for: {}".format(dist), "381CE248") mfile = "/etc/multipath.conf" if not os.path.exists(mfile): if not vfile: fix = "copy a multipath.conf file from the Datera Deployment Guide" else: fix = "copy {} to /etc/multipath.conf".format(vfile) return ff("/etc/multipath.conf file not found", "1D506D89", fix=fix) with io.open(mfile, 'r') as f: mconf = parse_mconf(f.read()) # Check defaults section defaults = filter(lambda x: x[0] == 'defaults', mconf) fix = ("check the example multipath.conf file from Datera deployment" "guide") if not defaults: ff("Missing defaults section", "1D8C438C", fix=fix) else: defaults = defaults[0] ct = False for d in defaults[1]: if 'checker_timeout' in d: ct = True if not ct: ff("defaults section missing 'checker_timeout'", "70191A9A", fix=fix) # Check devices section devices = filter(lambda x: x[0] == 'devices', mconf) if not devices: ff("Missing devices section", "797A6031", fix=fix) else: devices = devices[0][1] dat_block = None for _, device in devices: ddict = {} for entry in device: ddict[entry[0]] = entry[1] if ddict['vendor'] == 'DATERA': dat_block = ddict if not dat_block: return ff("No DATERA device section found", "99B9D136", fix=fix) if not dat_block['product'] == "IBLOCK": ff("Datera 'product' entry should be \"IBLOCK\"", "A9DF3F8C", fix=fix) # Blacklist exceptions be = filter(lambda x: x[0] == 'blacklist_exceptions', mconf) if not be: ff("Missing blacklist_exceptions section", "B8C8A19C") else: be = be[0][1] dat_block = None for _, device in be: bdict = {} for entry in device: bdict[entry[0]] = entry[1] if bdict['vendor'] == 'DATERA.*': dat_block = bdict if not dat_block: ff("No Datera blacklist_exceptions section found", "09E37E51", fix=fix) if dat_block['vendor'] != 'DATERA.*': ff("Datera blacklist_exceptions vendor entry malformed", "9990F32F", fix=fix) if dat_block['product'] != 'IBLOCK.*': ff("Datera blacklist_exceptions product entry malformed", "642753A0", fix=fix)
def add_footprint_variant( key: str, name: str, density_level: str, ) -> None: # UUIDs uuid_footprint = _uuid('footprint-{}'.format(key)) uuid_silkscreen = [ _uuid('polygon-silkscreen-{}-{}'.format(quadrant, key)) for quadrant in [1, 2, 3, 4] ] uuid_outline = _uuid('polygon-outline-{}'.format(key)) uuid_courtyard = _uuid('polygon-courtyard-{}'.format(key)) uuid_text_name = _uuid('text-name-{}'.format(key)) uuid_text_value = _uuid('text-value-{}'.format(key)) # Pad excess according to IPC density levels excess = config.excess_by_density(density_level) # Lead contact offsets lead_contact_x_offset = config.lead_span_x / 2 - config.lead_contact_length # this is the inner side of the contact area # Position of the first and last pad pos_first = get_pad_coords(1, config.lead_count, config.pitch, lead_contact_x_offset) pos_last = get_pad_coords(config.lead_count, config.lead_count, config.pitch, lead_contact_x_offset) lines.append(' (footprint {}'.format(uuid_footprint)) lines.append(' (name "{}")'.format(name)) lines.append(' (description "")') # Pads pad_width = config.lead_width + excess.side * 2 pad_length = config.lead_contact_length + excess.heel + excess.toe for p in range(1, config.lead_count + 1): pad_uuid = uuid_pads[p - 1] pad_center_offset_x = config.lead_span_x / 2 - pad_length / 2 + excess.toe pos = get_pad_coords(p, config.lead_count, config.pitch, pad_center_offset_x) pad_rotation = 90.0 if pos.orientation == 'horizontal' else 0.0 lines.append( ' (pad {} (side top) (shape rect)'.format(pad_uuid)) lines.append( ' (position {} {}) (rotation {}) (size {} {}) (drill 0.0)' .format( ff(pos.x), ff(pos.y), ff(pad_rotation), ff(pad_width), ff(pad_length), )) lines.append(' )') # Documentation: Leads for p in range(1, config.lead_count + 1): pad_center_offset_x = config.lead_span_x / 2 - pad_length / 2 pos = get_pad_coords(p, config.lead_count, config.pitch, lead_contact_x_offset) lead_uuid_ctct = uuid_leads1[p - 1] # Contact area lead_uuid_proj = uuid_leads2[p - 1] # Vertical projection # Contact area if pos.orientation == 'horizontal': x1 = ff(pos.x) x2 = ff(pos.x + sign(pos.x) * config.lead_contact_length) y1 = ff(pos.y - config.lead_width / 2) y2 = ff(pos.y + config.lead_width / 2) elif pos.orientation == 'vertical': x1 = ff(pos.x - config.lead_width / 2) x2 = ff(pos.x + config.lead_width / 2) y1 = ff(pos.y) y2 = ff(pos.y + sign(pos.y) * config.lead_contact_length) lines.append(' (polygon {} (layer top_documentation)'.format( lead_uuid_ctct)) lines.append(' (width 0.0) (fill true) (grab_area false)') lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y1)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x2, y1)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x2, y2)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y2)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y1)) lines.append(' )') # Vertical projection, between contact area and body if pos.orientation == 'horizontal': x1 = ff(sign(pos.x) * config.body_size_x / 2) x2 = ff(pos.x) y1 = ff(pos.y - config.lead_width / 2) y2 = ff(pos.y + config.lead_width / 2) elif pos.orientation == 'vertical': x1 = x2 = y1 = y2 = ff(0) x1 = ff(pos.x - config.lead_width / 2) x2 = ff(pos.x + config.lead_width / 2) y1 = ff(sign(pos.y) * config.body_size_y / 2) y2 = ff(pos.y) lines.append(' (polygon {} (layer top_documentation)'.format( lead_uuid_proj)) lines.append(' (width 0.0) (fill true) (grab_area false)') lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y1)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x2, y1)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x2, y2)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y2)) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( x1, y1)) lines.append(' )') # Silkscreen: 1 per quadrant # (Quadrant 1 is at the top right, the rest follows CCW) for quadrant in [1, 2, 3, 4]: uuid = uuid_silkscreen[quadrant - 1] x_min = abs( pos_last.x ) + config.lead_width / 2 + excess.side + silkscreen_offset + line_width / 2 x_max = config.body_size_x / 2 + line_width / 2 y_min = abs( pos_first.y ) + config.lead_width / 2 + excess.side + silkscreen_offset + line_width / 2 y_max = config.body_size_y / 2 + line_width / 2 vertices = [(x_min, y_max), (x_max, y_max), (x_max, y_min)] # Pin 1 marking line if quadrant == 2: vertices.append(( config.lead_span_x / 2 + excess.toe - line_width / 2, y_min, )) lines.append( ' (polygon {} (layer top_placement)'.format(uuid)) lines.append( ' (width {}) (fill false) (grab_area false)'.format( line_width)) sign_x = 1 if quadrant in [1, 4] else -1 sign_y = 1 if quadrant in [1, 2] else -1 for (x, y) in vertices: xx = ff(sign_x * x) yy = ff(sign_y * y) lines.append( ' (vertex (position {} {}) (angle 0.0))'.format( xx, yy)) lines.append(' )') # Documentation outline (fully inside body) outline_x_offset = config.body_size_x / 2 - line_width / 2 outline_y_offset = config.body_size_y / 2 - line_width / 2 lines.append( ' (polygon {} (layer top_documentation)'.format(uuid_outline)) lines.append(' (width {}) (fill false) (grab_area false)'.format( line_width)) oxo = ff(outline_x_offset) # Used for shorter code lines below :) oyo = ff(outline_y_offset) # Used for shorter code lines below :) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( oxo, oyo)) # NE lines.append(' (vertex (position {} -{}) (angle 0.0))'.format( oxo, oyo)) # SE lines.append(' (vertex (position -{} -{}) (angle 0.0))'.format( oxo, oyo)) # SW lines.append(' (vertex (position -{} {}) (angle 0.0))'.format( oxo, oyo)) # NW lines.append(' (vertex (position {} {}) (angle 0.0))'.format( oxo, oyo)) # NE lines.append(' )') # Courtyard x_max = config.lead_span_x / 2 + excess.toe x_mid = config.body_size_x / 2 x_min = abs(pos_last.x) + config.lead_width / 2 + excess.side y_max = config.lead_span_y / 2 + excess.toe y_mid = config.body_size_y / 2 y_min = abs(pos_first.y) + config.lead_width / 2 + excess.side vertices = [ # Starting at top left # Top (-x_min, y_max), (x_min, y_max), (x_min, y_mid), (x_mid, y_mid), (x_mid, y_min), # Right (x_max, y_min), (x_max, -y_min), (x_mid, -y_min), (x_mid, -y_mid), (x_min, -y_mid), # Bottom (x_min, -y_max), (-x_min, -y_max), (-x_min, -y_mid), (-x_mid, -y_mid), (-x_mid, -y_min), # Left (-x_max, -y_min), (-x_max, y_min), (-x_mid, y_min), (-x_mid, y_mid), (-x_min, y_mid), # Back to top (-x_min, y_max), ] lines.append(' (polygon {} (layer {})'.format( uuid_courtyard, 'top_courtyard')) lines.append(' (width {}) (fill false) (grab_area false)'.format( COURTYARD_LINE_WIDTH)) for (x, y) in vertices: xx = ff(x + sign(x) * excess.courtyard) yy = ff(y + sign(y) * excess.courtyard) lines.append(' (vertex (position {} {}) (angle 0.0))'.format( xx, yy)) lines.append(' )') # Labels y_offset = ff(config.lead_span_y / 2 + text_y_offset) text_attrs = '(height {}) (stroke_width 0.2) ' \ '(letter_spacing auto) (line_spacing auto)'.format(pkg_text_height) lines.append( ' (stroke_text {} (layer top_names)'.format(uuid_text_name)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center bottom) (position 0.0 {}) (rotation 0.0)'. format(y_offset)) lines.append( ' (auto_rotate true) (mirror false) (value "{{NAME}}")') lines.append(' )') lines.append( ' (stroke_text {} (layer top_values)'.format(uuid_text_value)) lines.append(' {}'.format(text_attrs)) lines.append( ' (align center top) (position 0.0 -{}) (rotation 0.0)'. format(y_offset)) lines.append( ' (auto_rotate true) (mirror false) (value "{{VALUE}}")') lines.append(' )') lines.append(' )')