Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix-decorations #372

Merged
merged 4 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion capellambse/aird/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ def _topsection_size(self) -> diagram.Vector2D:
width = max(map(operator.attrgetter("size.x"), self.children))
except ValueError:
width = 0
# pylint: disable=unpacking-non-sequence # false-positive
pad_x, pad_y = self.padding * 2
if isinstance(self.label, str):
label_extent = helpers.get_text_extent(
Expand Down
1 change: 0 additions & 1 deletion capellambse/diagram/_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,6 @@ def size(self) -> diagram.Vector2D:
return self._size

if isinstance(self.label, str):
# pylint: disable=unpacking-non-sequence # false-positive
pad_w, pad_h = self.padding * 2 # Pad on all four sides

# Fill in missing box size fields based on label text extent
Expand Down
22 changes: 14 additions & 8 deletions capellambse/diagram/capstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,19 +543,22 @@ class in the form::
},
"Missions Capabilities Blank": {
"Box.SystemComponent": {
"fill": [COLORS["_CAP_Actor_Blue_min"], COLORS["_CAP_Actor_Blue"]],
"stroke": COLORS["_CAP_Actor_Border_Blue"],
"text_fill": COLORS["_CAP_Actor_Blue_label"],
"fill": [
COLORS["_CAP_Component_Blue_min"],
COLORS["_CAP_Component_Blue"],
],
"stroke": COLORS["_CAP_Component_Border_Blue"],
"text_fill": COLORS["black"],
},
"Box.SystemActor": {
"fill": [COLORS["_CAP_Actor_Blue_min"], COLORS["_CAP_Actor_Blue"]],
"stroke": COLORS["_CAP_Actor_Border_Blue"],
"text_fill": COLORS["_CAP_Actor_Blue_label"],
"text_fill": COLORS["black"],
},
"Box.SystemHumanActor": {
"fill": [COLORS["_CAP_Actor_Blue_min"], COLORS["_CAP_Actor_Blue"]],
"stroke": COLORS["_CAP_Actor_Border_Blue"],
"text_fill": COLORS["_CAP_Actor_Blue_label"],
"text_fill": COLORS["black"],
},
"Edge.AbstractCapabilityExtend": {
"marker-end": "FineArrowMark",
Expand Down Expand Up @@ -857,9 +860,12 @@ class in the form::
"text_fill": COLORS["_CAP_Actor_Blue_label"],
},
"Box.SystemComponent": {
"fill": [COLORS["_CAP_Actor_Blue_min"], COLORS["_CAP_Actor_Blue"]],
"stroke": COLORS["_CAP_Actor_Border_Blue"],
"text_fill": COLORS["_CAP_Actor_Blue_label"],
"fill": [
COLORS["_CAP_Component_Blue_min"],
COLORS["_CAP_Component_Blue"],
],
"stroke": COLORS["_CAP_Component_Border_Blue"],
"text_fill": COLORS["black"],
},
"Box.SystemHumanActor": {
"fill": [COLORS["_CAP_Actor_Blue_min"], COLORS["_CAP_Actor_Blue"]],
Expand Down
2 changes: 1 addition & 1 deletion capellambse/model/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def set_self_references(*args: tuple[type[ModelObject], str]) -> None:
set_accessor(cls, attr, DirectProxyAccessor(cls, aslist=ElementList))


def xtype_handler( # pylint: disable=keyword-arg-before-vararg # PEP-570
def xtype_handler(
arch: str | None = None, /, *xtypes: str
) -> cabc.Callable[[type[T]], type[T]]:
"""Register a class as handler for a specific ``xsi:type``.
Expand Down
1 change: 0 additions & 1 deletion capellambse/model/common/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,6 @@ def __init__(
mapkey: str | None = None,
mapvalue: str | None = None,
) -> None:
# pylint: disable=assigning-non-slot # false-positive
self._model = model
self._elements = elements
if elemclass is not None:
Expand Down
37 changes: 26 additions & 11 deletions capellambse/svg/decorations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from __future__ import annotations

import collections.abc as cabc
import dataclasses
import logging
import re
import typing as t

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -55,17 +55,32 @@
}


class DecoFactories(t.Dict[str, cabc.Callable]):
def __call__(self, func: cabc.Callable) -> cabc.Callable:
symbol_name = re.sub(
"(?:^|_)([a-z])",
lambda m: m.group(1).capitalize(),
func.__name__,
)
self[symbol_name] = func
return func
@dataclasses.dataclass
class DecoFactory:
function: cabc.Callable
dependencies: tuple[str, ...]

def __missing__(self, class_: str) -> cabc.Callable:

class DecoFactories(dict[str, DecoFactory]):
def __call__(
self,
func: cabc.Callable | None = None,
dependencies: cabc.Iterable[str] = (),
) -> cabc.Callable:
def decorator(func: cabc.Callable) -> cabc.Callable:
symbol_name = re.sub(
"(?:^|_)([a-z])",
lambda m: m.group(1).capitalize(),
func.__name__,
)
self[symbol_name] = DecoFactory(func, tuple(dependencies))
return func

if func is None:
return decorator
return decorator(func)

def __missing__(self, class_: str) -> DecoFactory:
logger.error("%s wasn't found in factories.", class_)
assert "ErrorSymbol" in self
return self["ErrorSymbol"]
Expand Down
40 changes: 18 additions & 22 deletions capellambse/svg/drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, metadata: generate.DiagramMetadata):

self.__drawing = drawing.Drawing(**superparams)
self.diagram_class = metadata.class_
self.deco_cache: list[str] = []
self.deco_cache: set[str] = set()
self.add_backdrop(pos=metadata.pos, size=metadata.size)

self.obj_cache: dict[str | None, t.Any] = {}
Expand Down Expand Up @@ -350,17 +350,8 @@ def add_label_image(
if builder.class_ is None:
builder.class_ = "Error"

if "Human" in builder.class_ and "StickFigure" not in self.deco_cache:
self.__drawing.defs.add(
decorations.deco_factories["StickFigureSymbol"]()
)
self.deco_cache.append("StickFigure")

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_)
self._add_decofactory(builder.class_)

builder.group.add(
self.__drawing.use(
Expand Down Expand Up @@ -407,19 +398,18 @@ 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 = "Error"
if class_ in decorations.function_ports:
port_id = "PortSymbol"
port_id = "Port"
elif class_ in decorations.component_ports:
port_id = "ComponentPortSymbol"
port_id = "ComponentPort"

if port_id not in self.deco_cache:
self.__drawing.defs.add(decorations.deco_factories[port_id]())
self.deco_cache.append(port_id)
self._add_decofactory(port_id)

grp.add(
self.__drawing.use(
href=f"#{port_id}",
href=f"#{port_id}Symbol",
insert=pos,
size=size,
transform=self.get_port_transformation(
Expand Down Expand Up @@ -550,7 +540,7 @@ def getstyleattr(sobj: object, attr: str) -> t.Any:
marker_id = styling._generate_id(marker, [stroke])
if marker_id not in defs_ids:
self.__drawing.defs.add(
decorations.deco_factories[marker](
decorations.deco_factories[marker].function(
marker_id,
style=style.Styling(
self.diagram_class,
Expand Down Expand Up @@ -588,10 +578,7 @@ def _draw_symbol(
class_ not in self.deco_cache
and class_ not in decorations.all_ports
):
self.__drawing.defs.add(
decorations.deco_factories[f"{class_}Symbol"]()
)
self.deco_cache.append(class_)
self._add_decofactory(class_)

if class_ in decorations.all_ports:
grp = self.add_port(
Expand Down Expand Up @@ -854,6 +841,15 @@ def _draw_rect_helping_lines(
)
return lines

def _add_decofactory(self, name: str) -> None:
factory = decorations.deco_factories[f"{name}Symbol"]
self.__drawing.defs.add(factory.function())
for dep in factory.dependencies:
if dep not in self.deco_cache:
self._add_decofactory(dep)

self.deco_cache.add(name)


@dataclasses.dataclass
class LabelBuilder:
Expand Down
13 changes: 12 additions & 1 deletion capellambse/svg/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,18 @@ def draw_object(self, obj: ContentsDict) -> None:
"""Draw the given ``obj`` on the underlaying ``Drawing``."""
self.drawing.draw_object(obj)

def save_drawing(
def save_drawing(self, *args, **kwargs) -> None:
import warnings

warnings.warn(
"'save_drawing' is deprecated, use 'save' instead",
DeprecationWarning,
stacklevel=2,
)
del warnings
self.save(*args, **kwargs)

def save(
self,
filename: str | None = None,
pretty: bool = False,
Expand Down
10 changes: 5 additions & 5 deletions capellambse/svg/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def stick_figure_symbol(
return symb


@decorations.deco_factories
@decorations.deco_factories(dependencies=("StickFigure",))
def standalone_stick_figure_symbol(
id_: str = "StandaloneStickFigureSymbol",
) -> container.Symbol:
Expand Down Expand Up @@ -427,7 +427,7 @@ def logical_actor_symbol(id_: str = "LogicalActorSymbol") -> container.Symbol:
return symb


@decorations.deco_factories
@decorations.deco_factories(dependencies=("StickFigure",))
def logical_human_component_symbol(
id_: str = "LogicalHumanComponentSymbol",
) -> container.Symbol:
Expand Down Expand Up @@ -1146,7 +1146,7 @@ def _brown_oval(id_: str) -> container.Symbol:
return symb


@decorations.deco_factories
@decorations.deco_factories(dependencies=("StickFigure",))
def system_human_actor_symbol(
id_: str = "SystemHumanActorSymbol",
) -> container.Symbol:
Expand Down Expand Up @@ -1231,7 +1231,7 @@ def physical_behavior_component_symbol(
return _make_physical_component_symbol(id_, color="#a5bde7")


@decorations.deco_factories
@decorations.deco_factories(dependencies=("StickFigure",))
def physical_behavior_human_component_symbol(
id_: str = "PhysicalBehaviorHumanComponentSymbol",
) -> container.Symbol:
Expand Down Expand Up @@ -1299,7 +1299,7 @@ def physical_component_symbol(
return _make_physical_component_symbol(id_, color="#dbe6f4")


@decorations.deco_factories
@decorations.deco_factories(dependencies=("StickFigure",))
def physical_node_human_component_symbol(
id_: str = "PhysicalNodeHumanComponentSymbol",
) -> container.Symbol:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def tmp_svg(self, tmp_path: pathlib.Path) -> SVGDiagram:
return svg

def test_diagram_saves(self, tmp_svg: SVGDiagram) -> None:
tmp_svg.save_drawing()
tmp_svg.save()
assert pathlib.Path(tmp_svg.drawing.filename).is_file()

def test_base_css_styles(self, tmp_json: pathlib.Path) -> None:
Expand Down Expand Up @@ -146,7 +146,9 @@ def test_deco_factory_contains_styling_for_given_styleclass(
assert class_ in decorations.deco_factories

def test_deco_factory_returns_symbol_factory_for_given_styleclass(self):
assert decorations.deco_factories["PortSymbol"] is symbols.port_symbol
deco_factory = decorations.DecoFactory(symbols.port_symbol, ())

assert decorations.deco_factories["PortSymbol"] == deco_factory

@pytest.mark.parametrize(
"class_", ["ImaginaryClassSymbol", "NothingSymbol"]
Expand Down