From d3100e5bc836b6ef583be763f6dd0391d7e343fb Mon Sep 17 00:00:00 2001 From: dahbar Date: Mon, 6 Nov 2023 16:06:07 +0100 Subject: [PATCH] refactor: Dynamically create decorations --- capellambse/svg/drawing.py | 37 +++++----- capellambse/svg/style.py | 135 ------------------------------------- tests/test_svg.py | 10 +-- 3 files changed, 22 insertions(+), 160 deletions(-) diff --git a/capellambse/svg/drawing.py b/capellambse/svg/drawing.py index 37db32cb3..dd38dca2f 100644 --- a/capellambse/svg/drawing.py +++ b/capellambse/svg/drawing.py @@ -62,7 +62,7 @@ def __init__(self, metadata: generate.DiagramMetadata): self.__drawing = drawing.Drawing(**superparams) self.diagram_class = metadata.class_ - self.add_static_decorations() + self.deco_cache: list[str] = [] self.add_backdrop(pos=metadata.pos, size=metadata.size) self.obj_cache: dict[str | None, t.Any] = {} @@ -101,14 +101,6 @@ def add_backdrop( ) self.__drawing.add(self.__backdrop) - def add_static_decorations(self) -> None: - static_deco = style.STATIC_DECORATIONS[ - "__GLOBAL__" - ] + style.STATIC_DECORATIONS.get(self.diagram_class or "", ()) - - for name in static_deco: - self.__drawing.defs.add(decorations.deco_factories[name]()) - def __repr__(self) -> str: return self.__drawing._repr_svg_() @@ -338,6 +330,12 @@ def add_label_image( if builder.class_ is None: builder.class_ = "Error" + if builder.class_ not in self.deco_cache: + self.__drawing.defs.add( + decorations.deco_factories[f"{builder.class_}Symbol"]() + ) + self.deco_cache.append(builder.class_) + builder.group.add( self.__drawing.use( href=f"#{builder.class_}Symbol", @@ -383,15 +381,19 @@ def add_port( ) -> container.Group: grp = self.__drawing.g(class_=f"Box {class_}", id_=id_) if class_ in decorations.all_directed_ports: - port_id = "#ErrorSymbol" + port_id = "ErrorSymbol" if class_ in decorations.function_ports: - port_id = "#PortSymbol" + port_id = "PortSymbol" elif class_ in decorations.component_ports: - port_id = "#ComponentPortSymbol" + port_id = "ComponentPortSymbol" + + if port_id not in self.deco_cache: + self.__drawing.defs.add(decorations.deco_factories[port_id]()) + self.deco_cache.append(port_id) grp.add( self.__drawing.use( - href=port_id, + href=f"#{port_id}", insert=pos, size=size, transform=self.get_port_transformation( @@ -461,6 +463,7 @@ def draw_object(self, obj: cabc.Mapping[str, t.Any]) -> None: class_: str = style_type + ( f".{mobj['class']}" if "class" in mobj else "" ) + my_styles: dict[str, t.Any] = { **capstyle.get_style(self.diagram_class, class_), **mobj.get("style", {}), @@ -555,6 +558,11 @@ def _draw_symbol( assert isinstance(label_, (dict, type(None))) pos = (x_ + 0.5, y_ + 0.5) size = (width_, height_) + if class_ not in self.deco_cache: + self.__drawing.defs.add( + decorations.deco_factories[f"{class_}Symbol"]() + ) + self.deco_cache.append(class_) if class_ in decorations.all_ports: grp = self.add_port( @@ -742,9 +750,6 @@ def _draw_edge_label( text_anchor: str = "start", y_margin: int | float, ) -> container.Group: - class_ = ( - style.get_symbol_styleclass(class_, self.diagram_class) or class_ - ) if f"{class_}Symbol" in decorations.deco_factories: additional_space = ( decorations.icon_size + 2 * decorations.icon_padding diff --git a/capellambse/svg/style.py b/capellambse/svg/style.py index 82321c74b..5629a6de4 100644 --- a/capellambse/svg/style.py +++ b/capellambse/svg/style.py @@ -16,141 +16,6 @@ RE_ELMCLASS = re.compile(r"^([A-Z][a-z_]*)(\.[A-Za-z][A-Za-z0-9_]*)?(:.+)?$") CUSTOM_STYLE_ATTRS = {"marker-fill"} -# TODO refactor to dynamically determine needed decorations -STATIC_DECORATIONS: dict[str, tuple[str, ...]] = { - "__GLOBAL__": ( - "ErrorSymbol", - "RequirementSymbol", - "RepresentationLinkSymbol", - "StickFigureSymbol", - ), - "Error": (), - "Class Diagram Blank": ("ClassSymbol",), - "Functional Chain Description": ( - "AndControlNodeSymbol", - "IterateControlNodeSymbol", - "OrControlNodeSymbol", - "FunctionalExchangeSymbol", - "FunctionSymbol", - ), - "Logical Architecture Blank": ( - "ComponentPortSymbol", - "LogicalActorSymbol", - "LogicalComponentSymbol", - "LogicalFunctionSymbol", - "LogicalHumanActorSymbol", - "LogicalHumanComponentSymbol", - "PortSymbol", - "FunctionalExchangeSymbol", - "ComponentExchangeSymbol", - ), - "Logical Data Flow Blank": ( - "LogicalFunctionSymbol", - "PortSymbol", - "FunctionalExchangeSymbol", - ), - "Missions Capabilities Blank": ( - "CapabilitySymbol", - "MissionSymbol", - "SystemActorSymbol", - "SystemComponentSymbol", - "SystemHumanActorSymbol", - ), - "Mode State Machine": ( - "FinalStateSymbol", - "InitialPseudoStateSymbol", - "ModeSymbol", - "StateSymbol", - "TerminatePseudoStateSymbol", - ), - "Operational Capabilities Blank": ( - "EntitySymbol", - "OperationalActorBoxSymbol", - "OperationalCapabilitySymbol", - ), - "Operational Entity Blank": ( - "EntitySymbol", - "OperationalActivitySymbol", - "OperationalActorBoxSymbol", - "OperationalExchangeSymbol", - ), - "Operational Entity Breakdown": ("OperationalActorSymbol", "EntitySymbol"), - "Operational Process Description": ( - "AndControlNodeSymbol", - "IterateControlNodeSymbol", - "OperationalActivitySymbol", - "OrControlNodeSymbol", - "OperationalExchangeSymbol", - ), - "Operational Activity Interaction Blank": ( - "OperationalActivitySymbol", - "OperationalExchangeSymbol", - ), - "Physical Architecture Blank": ( - "PhysicalLinkSymbol", - "PhysicalBehaviorComponentSymbol", - "PhysicalBehaviorHumanComponentSymbol", - "PhysicalBehaviorActorSymbol", - "PhysicalBehaviorHumanActorSymbol", - "PhysicalNodeComponentSymbol", - "PhysicalNodeHumanComponentSymbol", - "PhysicalNodeActorSymbol", - "PhysicalNodeHumanActorSymbol", - "ComponentExchangeSymbol", - "ComponentPortSymbol", - "PortSymbol", - ), - "Physical Data Flow Blank": ( - "FunctionalExchangeSymbol", - "PhysicalFunctionSymbol", - "PortSymbol", - ), - "System Architecture Blank": ( - "ComponentExchangeSymbol", - "ComponentPortSymbol", - "FunctionalExchangeSymbol", - "PhysicalLinkSymbol", - "SystemActorSymbol", - "SystemComponentSymbol", - "SystemFunctionSymbol", - "SystemHumanActorSymbol", - "PortSymbol", - ), - "System Data Flow Blank": ( - "FunctionalExchangeSymbol", - "PortSymbol", - "SystemFunctionSymbol", - ), - "Contextual Capability": ( - "CapabilitySymbol", - "MissionSymbol", - "SystemActorSymbol", - "SystemHumanActorSymbol", - ), -} -MODIFY_STYLECLASS = {"FunctionalExchange"} - - -def get_symbol_styleclass(style: str | None, dstyle: str | None) -> str | None: - if ( - style not in MODIFY_STYLECLASS - or dstyle not in STATIC_DECORATIONS - or style in STATIC_DECORATIONS[dstyle] - ): - return None - - capitals = (dstyle or "").split(" ", maxsplit=1) - assert capitals - layer = capitals[0] - if style.startswith(layer): - return None - - scapitals = re.findall(r"[A-Z][^A-Z]*", style) - symbol = f'{layer}{"".join(scapitals[1:])}' - if f"{symbol}Symbol" in STATIC_DECORATIONS[dstyle]: - return symbol - return None - class Styling: """Container for style attributes of svg objects. diff --git a/tests/test_svg.py b/tests/test_svg.py index 533531bad..0e7f79747 100644 --- a/tests/test_svg.py +++ b/tests/test_svg.py @@ -11,14 +11,7 @@ import capellambse import capellambse.diagram -from capellambse.svg import ( - SVGDiagram, - decorations, - generate, - helpers, - style, - symbols, -) +from capellambse.svg import SVGDiagram, decorations, generate, helpers, symbols TEST_LAB = "[LAB] Wizzard Education" TEST_DIAGS = [ @@ -34,7 +27,6 @@ "[CC] Capability", "[PAB] A sample vehicle arch", ] -TEST_DECO = set(style.STATIC_DECORATIONS.keys()) - {"__GLOBAL__"} FREE_SYMBOLS = { "OperationalCapabilitySymbol", "AndControlNodeSymbol",