Skip to content

kumiki.identity

Flat import path

kumiki/__init__.py re-exports everything on this page via from kumiki import *, so every name below is also available directly as kumiki.DerivedFeaturePath -- you do not need to import from the submodule path shown in the heading above.

kumiki.identity

How things are named, and how much a name can be trusted.

There are three grades of stability worth keeping apart:

  1. the same code produces the same name on every run;
  2. an unrelated edit somewhere else leaves the name alone;
  3. editing the thing itself leaves the name alone.

An authored name reaches 2 and often 3. A position reaches only 1: insert something above it and every reference below moves. That is why the types below keep the two apart instead of blending them into one opaque string.

DerivedFeaturePath dataclass

DerivedFeaturePath(timber: ResolvedTimberPath, a: FeatureRef = FeatureRef(), b: FeatureRef = FeatureRef(), kind: str = 'EDGE')

Bases: FeaturePath

An edge or a point, named by the two features that form it.

A derived feature is built on demand from a pair of hits and is not among any node's declared features, so it cannot be looked up by name: resolving one means resolving both parents and deriving again. That also sidesteps names not being unique -- two tenons on one timber declare the same face names, so their edges share a name while being different edges.

ALWAYS TWO PARENTS, whichever kind it is. An edge is two faces; a point is an edge and a face. Three faces also meet at a point and two edges crossing do too, and neither is written here: the group rules keep derivation to one route per piece of geometry, so a point arrives as one declared edge against one face and never as a set of planes. That is what lets this stay a pair rather than becoming a list.

One timber, not one per parent. Both are always in the same timber's tree, and holding a timber on each would allow writing a pair that could never resolve.

a and b are sorted at construction, because deriving sorts its parents too: the same feature written either way round is the same reference.

timber instance-attribute

a class-attribute instance-attribute

a: FeatureRef = field(default_factory=FeatureRef)

b class-attribute instance-attribute

b: FeatureRef = field(default_factory=FeatureRef)

kind class-attribute instance-attribute

kind: str = 'EDGE'

feature_type property

feature_type: str

sort_key property

sort_key: Tuple[str, ...]

__post_init__

__post_init__()
Source code in kumiki/identity.py
def __post_init__(self):
    first, second = sorted((self.a, self.b), key=lambda ref: ref.identity())
    object.__setattr__(self, 'a', first)
    object.__setattr__(self, 'b', second)

identity

identity() -> Tuple[Any, ...]
Source code in kumiki/identity.py
def identity(self) -> Tuple[Any, ...]:
    return (str(self.timber), self.a.identity(), self.b.identity(), self.kind)

describe

describe() -> str
Source code in kumiki/identity.py
def describe(self) -> str:
    return f"{self.timber} > {self.a.describe()} x {self.b.describe()}"

DrawingId dataclass

DrawingId(value: str)

Which drawing. What an override in the drawings file names.

value instance-attribute

value: str

__str__

__str__() -> str
Source code in kumiki/identity.py
def __str__(self) -> str:
    return self.value

__bool__

__bool__() -> bool
Source code in kumiki/identity.py
def __bool__(self) -> bool:
    return bool(self.value)

FeaturePath

Bases: ABC

A reference to something measurable on one timber.

Semi-stable on purpose. Nothing here is a position except where it has to be: it is the timber, then the labels of the CSG nodes stepped through, then the feature on the last of them. Rename any of those and the reference breaks, which is the honest outcome; add or reorder around them and it still finds what it meant.

csg_path holds labels only, which is what the viewer navigates by and what skips the unlabelled intermediates -- the nodes most likely to move.

Two shapes, because a derived edge is not a feature anyone declared: it is the pair of faces that form it (see cutcsg.DerivedEdgeFeature). Keeping them apart in the type is what stops a face carrying a second parent, or an edge carrying none.

timber instance-attribute

sort_key abstractmethod property

sort_key: Tuple[str, ...]

identity abstractmethod

identity() -> Tuple[Any, ...]

A comparable form, for deciding whether two references are the same.

A tuple rather than a joined string: a timber path may itself contain any separator that might be chosen, and two different references must never collapse into one.

Source code in kumiki/identity.py
@abstractmethod
def identity(self) -> Tuple[Any, ...]:
    """A comparable form, for deciding whether two references are the same.

    A tuple rather than a joined string: a timber path may itself contain
    any separator that might be chosen, and two different references must
    never collapse into one.
    """
    ...

describe abstractmethod

describe() -> str

For a person to read -- a log line, or a broken reference in a list.

Source code in kumiki/identity.py
@abstractmethod
def describe(self) -> str:
    """For a person to read -- a log line, or a broken reference in a list."""
    ...

FeatureRef dataclass

FeatureRef(csg_path: Sequence[str] = (), feature: Optional[str] = None)

A reference to specific feature on a CSG tree. Must be resolved against an actual CSG tree to find the feature. Does not guarantee that the feature exists.

csg_path class-attribute instance-attribute

csg_path: Sequence[str] = ()

feature class-attribute instance-attribute

feature: Optional[str] = None

sort_key property

sort_key: Tuple[str, ...]

The path's length, then the path, then the feature.

__post_init__

__post_init__()
Source code in kumiki/identity.py
def __post_init__(self):
    if isinstance(self.csg_path, list):
        object.__setattr__(self, 'csg_path', tuple(self.csg_path))

identity

identity() -> Tuple[Tuple[str, ...], str]
Source code in kumiki/identity.py
def identity(self) -> Tuple[Tuple[str, ...], str]:
    return (tuple(self.csg_path), self.feature or "")

describe

describe() -> str
Source code in kumiki/identity.py
def describe(self) -> str:
    trail = " > ".join(self.csg_path)
    return f"{trail} > {self.feature}" if self.feature else trail

MeasurementId dataclass

MeasurementId(value: str)

Which of several measurements between the same two features.

value instance-attribute

value: str

__str__

__str__() -> str
Source code in kumiki/identity.py
def __str__(self) -> str:
    return self.value

__bool__

__bool__() -> bool
Source code in kumiki/identity.py
def __bool__(self) -> bool:
    return bool(self.value)

ResolvedTimberPath dataclass

ResolvedTimberPath(path: str, occurrence: int = 0)

One particular timber in one particular frame. Attained by resolving a TimberPath against a frame.

Disambiguates duplicate path names using occurrence

path instance-attribute

path: str

occurrence class-attribute instance-attribute

occurrence: int = 0

timber_path property

timber_path: TimberPath

The name, without the frame-dependent part.

__str__

__str__() -> str

The form the viewer has always used as a member key.

Source code in kumiki/identity.py
def __str__(self) -> str:
    """The form the viewer has always used as a member key."""
    return f"{self.path}#{self.occurrence}"

parse classmethod

parse(text: str) -> ResolvedTimberPath

Read one back from the viewer's member key.

A path may itself contain anything except the final '#n', so the split is from the right.

Source code in kumiki/identity.py
@classmethod
def parse(cls, text: str) -> 'ResolvedTimberPath':
    """Read one back from the viewer's member key.

    A path may itself contain anything except the final '#n', so the split
    is from the right.
    """
    name, separator, occurrence = str(text).rpartition("#")
    if separator and occurrence.isdigit():
        return cls(path=name, occurrence=int(occurrence))
    return cls(path=str(text))

SingleFeaturePath dataclass

SingleFeaturePath(timber: ResolvedTimberPath, ref: FeatureRef = FeatureRef(), feature_type: Optional[str] = None)

Bases: FeaturePath

One declared feature: a face, a point, or an edge a primitive names.

feature_type is kept because one label can name both a face and an edge, and a measurement to the wrong one does not look wrong on screen. EDGE belongs here as well as in DerivedFeaturePath -- a feature can declare itself an edge rather than being derived from two faces.

timber instance-attribute

ref class-attribute instance-attribute

ref: FeatureRef = field(default_factory=FeatureRef)

feature_type class-attribute instance-attribute

feature_type: Optional[str] = None

csg_path property

csg_path: Tuple[str, ...]

feature property

feature: Optional[str]

sort_key property

sort_key: Tuple[str, ...]

identity

identity() -> Tuple[Any, ...]
Source code in kumiki/identity.py
def identity(self) -> Tuple[Any, ...]:
    csg_path, feature = self.ref.identity()
    return (str(self.timber), csg_path, feature, self.feature_type or "")

describe

describe() -> str
Source code in kumiki/identity.py
def describe(self) -> str:
    trail = self.ref.describe()
    return f"{self.timber} > {trail}" if trail else str(self.timber)

TimberPath dataclass

TimberPath(path: str)

Timbers are named by path so that they can be stored hierarchically. The hierchy is purely for organization on the user's end. No mechanisms to prevent name conflicts. Use ResolvedTimberPath to refer to timbers on a frame unambiguously. There should not be duplicate names but we can't really trust the user not to do so.

path instance-attribute

path: str

__str__

__str__() -> str
Source code in kumiki/identity.py
def __str__(self) -> str:
    return self.path

ViewportId dataclass

ViewportId(value: str)

Which viewport of a drawing -- 'front', 'top', the ones a layout produces.

value instance-attribute

value: str

__str__

__str__() -> str
Source code in kumiki/identity.py
def __str__(self) -> str:
    return self.value

__bool__

__bool__() -> bool
Source code in kumiki/identity.py
def __bool__(self) -> bool:
    return bool(self.value)