Add Jumper code
This commit is contained in:
parent
206be6bbed
commit
3e4353e62a
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from enum import Enum
|
||||
from itertools import zip_longest
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
@ -263,10 +263,14 @@ class AdditionalComponent(GraphicalComponent):
|
||||
explicit_qty: bool = True
|
||||
amount_computed: Optional[NumberAndUnit] = None
|
||||
note: str = None
|
||||
color: Optional[MultiColor] = None
|
||||
references: Optional[List[str]] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
|
||||
self.color = MultiColor(self.color)
|
||||
|
||||
if isinstance(self.qty_multiplier, float) or isinstance(
|
||||
self.qty_multiplier, int
|
||||
):
|
||||
@ -307,7 +311,9 @@ class TopLevelGraphicalComponent(GraphicalComponent): # abstract class
|
||||
class Connector(TopLevelGraphicalComponent):
|
||||
# connector-specific properties
|
||||
style: Optional[str] = None
|
||||
loops: List[List[Pin]] = field(default_factory=list)
|
||||
# TODO: Move shorts and loops to PinClass
|
||||
loops: Dict[str, List[int]] = field(default_factory=dict)
|
||||
shorts: Dict[str, List[int]] = field(default_factory=dict)
|
||||
# pin information in particular
|
||||
pincount: Optional[int] = None
|
||||
pins: List[Pin] = field(default_factory=list) # legacy
|
||||
@ -412,20 +418,41 @@ class Connector(TopLevelGraphicalComponent):
|
||||
# hide pincount for simple (1 pin) connectors by default
|
||||
self.show_pincount = self.style != "simple"
|
||||
|
||||
for loop in self.loops:
|
||||
# TODO: allow using pin labels in addition to pin numbers,
|
||||
# just like when defining regular connections
|
||||
# TODO: include properties of wire used to create the loop
|
||||
if len(loop) != 2:
|
||||
raise Exception("Loops must be between exactly two pins!")
|
||||
for pin in loop:
|
||||
# TODO: allow using pin labels in addition to pin numbers,
|
||||
# just like when defining regular connections
|
||||
# TODO: include properties of wire used to create the loop
|
||||
for loopName in self.loops:
|
||||
for pin in self.loops[loopName]:
|
||||
if pin not in self.pins:
|
||||
raise Exception(
|
||||
f'Unknown loop pin "{pin}" for connector "{self.name}"!'
|
||||
f'Unknown loop pin "{pin}" for connector "{self.designator}"!'
|
||||
)
|
||||
# Make sure loop connected pins are not hidden.
|
||||
# side=None, determine side to show loops during rendering
|
||||
self.activate_pin(pin, side=None, is_connection=True)
|
||||
self.activate_pin(pin, None)
|
||||
for short in self.shorts:
|
||||
for pin in self.shorts[short]:
|
||||
if pin not in self.pins:
|
||||
raise Exception(
|
||||
f'Unknown loop pin "{pin}" for connector "{self.designator}"!'
|
||||
)
|
||||
# Make sure loop connected pins are not hidden.
|
||||
self.activate_pin(pin, None)
|
||||
|
||||
# TODO: Remove the outcommented code here if it is no longer needed as reference
|
||||
# for loop in self.loops:
|
||||
# # TODO: allow using pin labels in addition to pin numbers,
|
||||
# # just like when defining regular connections
|
||||
# # TODO: include properties of wire used to create the loop
|
||||
# if len(loop) != 2:
|
||||
# raise Exception("Loops must be between exactly two pins!")
|
||||
# for pin in loop:
|
||||
# if pin not in self.pins:
|
||||
# raise Exception(
|
||||
# f'Unknown loop pin "{pin}" for connector "{self.name}"!'
|
||||
# )
|
||||
# # Make sure loop connected pins are not hidden.
|
||||
# # side=None, determine side to show loops during rendering
|
||||
# self.activate_pin(pin, side=None, is_connection=True)
|
||||
|
||||
for i, item in enumerate(self.additional_components):
|
||||
if isinstance(item, dict):
|
||||
|
||||
@ -22,7 +22,7 @@ from wireviz.wv_dataclasses import (
|
||||
WireClass,
|
||||
)
|
||||
from wireviz.wv_html import Img, Table, Td, Tr
|
||||
from wireviz.wv_utils import html_line_breaks, remove_links
|
||||
from wireviz.wv_utils import html_line_breaks, remove_links, getAddCompFromRef
|
||||
|
||||
|
||||
def gv_node_component(component: Component) -> Table:
|
||||
@ -259,8 +259,27 @@ def nested_table_dict(d: dict) -> Table:
|
||||
return Table(rows, border=0, cellborder=1, cellpadding=3, cellspacing=0)
|
||||
|
||||
|
||||
|
||||
def gv_shorts_info_row(component) -> Tr:
|
||||
shorts_info = []
|
||||
if component.ports_left:
|
||||
shorts_info.append(Td(f''))
|
||||
if component.pinlabels:
|
||||
shorts_info.append(Td(f''))
|
||||
|
||||
for short in component.shorts:
|
||||
shorts_info.append(Td(f'{short}'))
|
||||
|
||||
if component.ports_right:
|
||||
shorts_info.append(Td(f''))
|
||||
return Tr(shorts_info)
|
||||
|
||||
def gv_pin_table(component) -> Table:
|
||||
pin_rows = []
|
||||
|
||||
if len(component.shorts) > 0:
|
||||
pin_rows.append(gv_shorts_info_row(component))
|
||||
|
||||
for pin in component.pin_objects.values():
|
||||
if component.should_show_pin(pin.id):
|
||||
pin_rows.append(gv_pin_row(pin, component))
|
||||
@ -271,6 +290,16 @@ def gv_pin_table(component) -> Table:
|
||||
return tbl
|
||||
|
||||
|
||||
def gv_short_row_part(pin, connector) -> List:
|
||||
short_row = []# Td("ADA"), Td("DAD")
|
||||
for short, shPins in connector.shorts.items():
|
||||
if pin.index+1 in shPins:
|
||||
short_row.append(Td("", port=f"p{pin.index+1}j"))
|
||||
else:
|
||||
short_row.append(Td(""))
|
||||
return short_row
|
||||
|
||||
|
||||
def gv_pin_row(pin, connector) -> Tr:
|
||||
# ports in GraphViz are 1-indexed for more natural maping to pin/wire numbers
|
||||
has_pincolors = any([_pin.color for _pin in connector.pin_objects.values()])
|
||||
@ -279,6 +308,7 @@ def gv_pin_row(pin, connector) -> Tr:
|
||||
Td(pin.label, delete_if_empty=True),
|
||||
Td(str(pin.color) if pin.color else "", sides="TBL") if has_pincolors else None,
|
||||
Td(color_minitable(pin.color), sides="TBR") if has_pincolors else None,
|
||||
gv_short_row_part(pin, connector),
|
||||
Td(pin.id, port=f"p{pin.index+1}r") if connector.ports_right else None,
|
||||
]
|
||||
return Tr(cells)
|
||||
@ -294,13 +324,37 @@ def gv_connector_loops(connector: Connector) -> List:
|
||||
loop_dir = "e"
|
||||
else:
|
||||
raise Exception("No side for loops")
|
||||
for loop in connector.loops:
|
||||
head = f"{connector.designator}:p{loop[0]}{loop_side}:{loop_dir}"
|
||||
tail = f"{connector.designator}:p{loop[1]}{loop_side}:{loop_dir}"
|
||||
loop_edges.append((head, tail))
|
||||
|
||||
for loop, loPins in connector.loops.items():
|
||||
comp = getAddCompFromRef(loop, connector)
|
||||
loColor = "#000000"
|
||||
if comp != None and comp.color != None:
|
||||
loColor = comp.color.html
|
||||
|
||||
for i in range(1, len(loPins)):
|
||||
head = f"{connector.designator}:p{loPins[i - 1]}{loop_side}:{loop_dir}"
|
||||
tail = f"{connector.designator}:p{loPins[i]}{loop_side}:{loop_dir}"
|
||||
loop_edges.append((head, tail, loColor))
|
||||
return loop_edges
|
||||
|
||||
|
||||
def gv_connector_shorts(connector: Connector) -> List:
|
||||
short_edges = []
|
||||
|
||||
for short, shPins in connector.shorts.items():
|
||||
comp = getAddCompFromRef(short, connector)
|
||||
shColor = "#000000"
|
||||
if comp != None and comp.color != None:
|
||||
shColor = comp.color.html
|
||||
|
||||
for i in range(1, len(shPins)):
|
||||
head = f"{connector.designator}:p{shPins[i - 1]}j:c"
|
||||
tail = f"{connector.designator}:p{shPins[i]}j:c"
|
||||
short_edges.append((head, tail, shColor))
|
||||
return short_edges
|
||||
|
||||
|
||||
|
||||
def gv_conductor_table(cable) -> Table:
|
||||
rows = []
|
||||
rows.append(Tr(Td(" "))) # spacer row on top
|
||||
@ -371,7 +425,7 @@ def gv_wire_cell(wire: Union[WireClass, ShieldClass], colspan: int) -> Td:
|
||||
wire_inner_rows = []
|
||||
for j, bgcolor in enumerate(color_list[::-1]):
|
||||
wire_inner_cell_attribs = {
|
||||
"bgcolor": bgcolor if bgcolor != "" else "#000000",
|
||||
"bgcolor": "#FFFFFF", # bgcolor if bgcolor != "" else "#000000", # TODO: More elegent solution for making black/whit space needed, since the wire is drawn as an actual edge
|
||||
"border": 0,
|
||||
"cellpadding": 0,
|
||||
"colspan": colspan,
|
||||
@ -392,8 +446,10 @@ def gv_wire_cell(wire: Union[WireClass, ShieldClass], colspan: int) -> Td:
|
||||
|
||||
return wire_outer_cell
|
||||
|
||||
dot.attr("edge", headclip="true", tailclip="true", style="bold") # TODO: ?
|
||||
|
||||
def gv_edge_wire(harness, cable, connection) -> Tuple[str, str, str, str, str]:
|
||||
# color, l1, l2, r1, r2
|
||||
def gv_edge_wire(harness, cable, connection) -> Tuple[str, str, str, str, str]:
|
||||
if connection.via.color:
|
||||
# check if it's an actual wire and not a shield
|
||||
color = f"#000000:{connection.via.color.html_padded}:#000000"
|
||||
@ -425,6 +481,24 @@ def gv_edge_wire(harness, cable, connection) -> Tuple[str, str, str, str, str]:
|
||||
|
||||
return color, code_left_1, code_left_2, code_right_1, code_right_2
|
||||
|
||||
# color, we, ww,
|
||||
def gv_edge_wire_inside(cable) -> List[Tuple[str, str, str]]:
|
||||
wires = []
|
||||
# print(cable.wire_objects)
|
||||
for wire in cable.wire_objects.values():
|
||||
color = "#000000"
|
||||
if wire.color:
|
||||
# check if it's an actual wire and not a shield
|
||||
color = f"#000000:{wire.color.html_padded}:#000000"
|
||||
else: # it's a shield connection
|
||||
color = "#000000"
|
||||
|
||||
we = f"{wire.parent}:w{wire.index+1}:e"
|
||||
ww = f"{wire.parent}:w{wire.index+1}:w"
|
||||
|
||||
wires.append([color, we, ww])
|
||||
return wires
|
||||
|
||||
|
||||
def parse_arrow_str(inp: str) -> ArrowDirection:
|
||||
if inp[0] == "<" and inp[-1] == ">":
|
||||
|
||||
64
src/wireviz/wv_gvpr.gvpr
Normal file
64
src/wireviz/wv_gvpr.gvpr
Normal file
@ -0,0 +1,64 @@
|
||||
/*******************************************************************
|
||||
|
||||
see https://forum.graphviz.org/t/straitening-one-line-throu-a-table/2196 and https://forum.graphviz.org/t/way-of-drawing-a-black-circle-inside-a-table-field/2273/12
|
||||
input must include pos values (must be output from one of the engines w/ -Tdot)#
|
||||
Thanks to steveroush and FeRDNYC
|
||||
|
||||
*******************************************************************/
|
||||
BEG_G{
|
||||
double x1,y1,x2,y2,x3,y3,x4,y4;
|
||||
string ptSize, tok[int], pt[];
|
||||
int cnt, circ, i;
|
||||
node_t aNode;
|
||||
|
||||
circ=0;
|
||||
|
||||
/***************************************
|
||||
$G.bb="";
|
||||
$G.nodesep="";
|
||||
$G.ranksep="";
|
||||
$G.splines="true";
|
||||
****************************************/
|
||||
}
|
||||
|
||||
// This removes the label text but keeps the position
|
||||
E[noLabel] {
|
||||
$.label=""; // remove pesky label
|
||||
// $.lp=""; // remove peskier label pos
|
||||
}
|
||||
|
||||
E[straight] {
|
||||
cnt=tokens($.pos,tok," ");
|
||||
$.oldpos=$.pos;
|
||||
x1 = xOf(tok[0]);
|
||||
y1 = yOf(tok[0]);
|
||||
x4 = xOf(tok[cnt-1]);
|
||||
y4 = yOf(tok[cnt-1]);
|
||||
x2 = x1 + (x4-x1)/3.;
|
||||
y2 = y1 + (y4-y1)/3.;
|
||||
x3 = x1 + 2.*(x4-x1)/3.;
|
||||
y3 = y1 + 2.*(y4-y1)/3.;
|
||||
pos=sprintf("%.3f,%.3f %.3f,%.3f %.3f,%.3f %.3f,%.3f", x1,y1, x2,y2, x3,y3, x4,y4);
|
||||
$.label=""; // remove pesky label
|
||||
$.lp=""; // remove peskier label pos
|
||||
|
||||
if (hasAttr($, "addPTS") && $.addPTS!="" && $.colorPTS!=""){
|
||||
// now we place point nodes at the edge ends
|
||||
pt[1] = tok[0];
|
||||
pt[2] = tok[cnt-1];
|
||||
ptSize=$.addPTS;
|
||||
for (pt[i]) {
|
||||
if (i==2 && pt[1]==pt[2])
|
||||
continue;
|
||||
aNode=node($G, "__CIRCLE__" + (string)++circ);
|
||||
aNode.pos=pt[i];
|
||||
aNode.shape="point";
|
||||
aNode.width=ptSize;
|
||||
aNode.height=ptSize;
|
||||
aNode.style="filled";
|
||||
aNode.fillcolor=$.colorPTS;
|
||||
aNode.color=$.colorPTS;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from graphviz import Graph
|
||||
|
||||
@ -24,13 +27,16 @@ from wireviz.wv_dataclasses import (
|
||||
Side,
|
||||
TopLevelGraphicalComponent,
|
||||
Tweak,
|
||||
Image,
|
||||
)
|
||||
from wireviz.wv_graphviz import (
|
||||
apply_dot_tweaks,
|
||||
calculate_node_bgcolor,
|
||||
gv_connector_loops,
|
||||
gv_connector_shorts,
|
||||
gv_edge_mate,
|
||||
gv_edge_wire,
|
||||
gv_edge_wire_inside,
|
||||
gv_node_component,
|
||||
parse_arrow_str,
|
||||
set_dot_basics,
|
||||
@ -40,8 +46,7 @@ from wireviz.wv_output import (
|
||||
embed_svg_images_file,
|
||||
generate_html_output,
|
||||
)
|
||||
from wireviz.wv_utils import OLD_CONNECTOR_ATTR, bom2tsv, check_old, file_write_text
|
||||
|
||||
from wireviz.wv_utils import bom2tsv, open_file_write, getAddCompFromRef
|
||||
|
||||
@dataclass
|
||||
class Harness:
|
||||
@ -322,9 +327,20 @@ class Harness:
|
||||
if len(connector.loops) > 0:
|
||||
dot.attr("edge", color="#000000")
|
||||
loops = gv_connector_loops(connector)
|
||||
for head, tail in loops:
|
||||
dot.edge(head, tail, label=" ")
|
||||
# ^ workaround to avoid oversized loops
|
||||
for head, tail, color in loops:
|
||||
dot.edge(head, tail, color = color, label = " ", noLabel="noLabel")
|
||||
|
||||
# generate edges for connector shorts
|
||||
if len(connector.shorts) > 0:
|
||||
dot.attr("edge", color="#000000")
|
||||
shorts = gv_connector_shorts(connector)
|
||||
for head, tail, color in shorts:
|
||||
dot.edge(head, tail,
|
||||
color=color,
|
||||
straight="straight",
|
||||
addPTS=".18", # Size of the point at the end of the straight line/edge, it also enables the drawing of it
|
||||
colorPTS=color,
|
||||
headclip="false", tailclip="false")
|
||||
|
||||
# determine if there are double- or triple-colored wires in the harness;
|
||||
# if so, pad single-color wires to make all wires of equal thickness
|
||||
@ -360,6 +376,9 @@ class Harness:
|
||||
if not (r1, r2) == (None, None):
|
||||
dot.edge(r1, r2)
|
||||
|
||||
for color, we, ww in gv_edge_wire_inside(cable):
|
||||
if not (we, ww) == (None, None):
|
||||
dot.edge(we, ww, color=color, straight="straight")
|
||||
for mate in self.mates:
|
||||
color, dir, code_from, code_to = gv_edge_mate(mate)
|
||||
|
||||
@ -395,6 +414,22 @@ class Harness:
|
||||
graph = self.graph
|
||||
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
|
||||
|
||||
def graphRender(self, type, filename, graph):
|
||||
# Chack if the needed commands are existing
|
||||
if find_executable("dot") and find_executable("gvpr") and find_executable("neato"):
|
||||
# Set enviorments variable to path of this file
|
||||
os.environ['GVPRPATH'] = str(Path(__file__).parent)
|
||||
# Export the gv output to a temporay file
|
||||
graph.save(filename=f"{filename}_tmp.gv")
|
||||
# Run the vomand and generait the output
|
||||
os.system(f"dot {filename}_tmp.gv | gvpr -q -cf wv_gvpr.gvpr | neato -n2 -T{type} -o {filename}.{type}")
|
||||
# Remove the temporary file
|
||||
os.remove(f"{filename}_tmp.gv")
|
||||
else:
|
||||
print('The "dot", "gvpr" and "neato" comand where not found on the system, use old methode of generaiton, this may lead to not wanted output.')
|
||||
graph.render(filename=filename) # old rendering methode, befor jumper implementations
|
||||
|
||||
|
||||
def output(
|
||||
self,
|
||||
filename: Union[str, Path],
|
||||
@ -412,13 +447,17 @@ class Harness:
|
||||
_filename = f"{filename}.tmp" if f == "svg" else filename
|
||||
# TODO: prevent rendering SVG twice when both SVG and HTML are specified
|
||||
graph.format = f
|
||||
graph.render(filename=_filename, view=view, cleanup=cleanup)
|
||||
self.graphRender(f, _filename, graph)
|
||||
# embed images into SVG output
|
||||
if "svg" in fmt or "html" in fmt:
|
||||
embed_svg_images_file(f"{filename}.tmp.svg")
|
||||
# GraphViz output
|
||||
if "gv" in fmt:
|
||||
graph.save(filename=f"{filename}.gv")
|
||||
# Print the needed comand for generaitong an output
|
||||
filename_str = str(filename)
|
||||
shutil.copyfile(str(Path(__file__).parent).replace('\\', '/') + "/wv_gvpr.gvpr", filename_str + "_wv_gvpr.gvpr")
|
||||
print(f"Use: dot {filename_str}.gv | gvpr -q -cf {filename_str}_wv_gvpr.gvpr | neato -n2 -T<type> -o {filename_str}.<type>")
|
||||
# BOM output
|
||||
bomlist = bom_list(self.bom)
|
||||
# bomlist = [[]]
|
||||
|
||||
@ -241,3 +241,10 @@ def check_old(node: str, old_attr: dict, args: dict) -> None:
|
||||
for attr, descr in old_attr.items():
|
||||
if attr in args:
|
||||
raise ValueError(f"'{attr}' in {node}: '{attr}' {descr}")
|
||||
|
||||
# Returns a Additional Component from <part> with the given <reference>
|
||||
def getAddCompFromRef(reference, part):
|
||||
#print(part.additional_components)
|
||||
for comp in part.additional_components:
|
||||
if reference in comp.references:
|
||||
return comp;
|
||||
Loading…
x
Reference in New Issue
Block a user