Skip to content

Commit

Permalink
Modifications + dimension offset (#26)
Browse files Browse the repository at this point in the history
* removed plugin manager debug setting

* introduced wrapper for cadwork dimensions

* added ElementDelta helper

* added alternative ctors to Dimension

* changelog

* simplified dimension drawing

* updated dependency version

* removed unnecessary docstring

* shorter __str__

* make use of line_offset when drawing linear dimensions
  • Loading branch information
chenkasirer authored Jun 17, 2024
1 parent 0dbc342 commit 0d4998f
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added new types `Dimension` and `AnchorPoint` in `compas_cadwork.datamodel`.
* Added `from_element` and `from_id` to `Dimension`.

### Changed

* Exporting now with ifc4 and bimwood property set (true).
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
compas>=2.0, <3.0
cwapi3d>=30.319.1
cwapi3d>=30.498.0
2 changes: 2 additions & 0 deletions src/compas_cadwork/datamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .element import ElementGroup
from .element import ElementGroupingType
from .element import ATTR_INSTRUCTION_ID
from .dimension import Dimension


__all__ = [
Expand All @@ -11,4 +12,5 @@
"ElementGroup",
"ElementGroupingType",
"ATTR_INSTRUCTION_ID",
"Dimension",
]
145 changes: 145 additions & 0 deletions src/compas_cadwork/datamodel/dimension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from __future__ import annotations
from dataclasses import dataclass

from dimension_controller import get_dimension_points
from dimension_controller import get_plane_normal
from dimension_controller import get_plane_xl
from dimension_controller import get_segment_distance
from dimension_controller import get_segment_direction

from compas.geometry import Frame
from compas.geometry import Point
from compas.geometry import Vector
from compas.tolerance import Tolerance

from compas_cadwork.conversions import point_to_compas
from compas_cadwork.conversions import vector_to_compas
from .element import Element
from .element import ElementType


TOL = Tolerance(unit="MM", absolute=1e-3, relative=1e-3)


@dataclass
class AnchorPoint:
"""Anchor point of a cadwork measurement. There may me 2 or more anchor points in a measurement.
Attributes
----------
location : Point
The location of the anchor point in 3d space.
distance : float
The distance of the anchor point from the measurement line.
direction : Vector
The direction of the anchor point from the measurement line.
"""

location: Point
distance: float
direction: Vector

def __eq__(self, other: AnchorPoint) -> bool:
if not isinstance(other, AnchorPoint):
return False

if not TOL.is_allclose([*self.location], [*other.location]):
return False

if not TOL.is_allclose([*self.direction], [*other.direction]):
return False

return TOL.is_close(self.distance, other.distance)


class Dimension(Element):
"""Represents a cadwork dimension"""

def __init__(self, id):
super().__init__(id, ElementType.DIMENSION)
self._frame = None
# not lazy-instantiating this so that it can be used to compare the modified instances of the same dimension
# otherwise, the anchors values that are compared depend on the time `anchors` was first accessed
self.anchors = self._init_anchors()

def __str__(self) -> str:
return f"Dimension id:{self.id} length:{self.length:.0f} anchors:{len(self.anchors)}"

def __hash__(self):
return hash(self.cadwork_guid)

def __eq__(self, other: Dimension):
if not isinstance(other, Dimension):
return False

if self.cadwork_guid != other.cadwork_guid:
return False

for point_self, point_other in zip(self.anchors, other.anchors):
if point_self != point_other:
return False
return True

@property
def frame(self):
if not self._frame:
zaxis = -self.text_normal
xaxis = vector_to_compas(get_plane_xl(self.id))
yaxis = xaxis.cross(zaxis).unitized()
self._frame = Frame(self.anchors[0].location, xaxis, yaxis)
return self._frame

@property
def text_normal(self):
return vector_to_compas(get_plane_normal(self.id))

@property
def length(self):
start: Point = self.anchors[0].location
end: Point = self.anchors[-1].location
return start.distance_to_point(end)

def _init_anchors(self):
anchors = []
for index, point in enumerate(get_dimension_points(self.id)):
distance = get_segment_distance(self.id, index)
direction = get_segment_direction(self.id, index)
anchors.append(AnchorPoint(point_to_compas(point), distance, vector_to_compas(direction)))
return tuple(anchors)

@classmethod
def from_id(cls, element_id: int) -> Dimension:
"""Creates a dimension object from an element id.
This is an override of :method:`Element.from_id`.
Parameters
----------
element_id : int
The id of the element to create the dimension from.
Returns
-------
:class:`Dimension`
The dimension object created from the element id.
"""
return cls(id=element_id)

@classmethod
def from_element(cls, element: Element) -> Dimension:
"""Creates a dimension object from an element.
Parameters
----------
element : :class:`Element`
The element to create the dimension from.
Returns
-------
:class:`Dimension`
The dimension object created from the element.
"""
return cls(id=element.id)
11 changes: 4 additions & 7 deletions src/compas_cadwork/datamodel/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@
ATTR_INSTRUCTION_ID = 666


class StrEnum(str, Enum):
"""Why do *I* have to do this?"""

pass


class ElementGroupingType(IntEnum):
"""CADWork Element Grouping Type"""

Expand All @@ -60,7 +54,7 @@ def to_cadwork(self):
return cadwork.element_grouping_type(self.value)


class ElementType(StrEnum):
class ElementType(str, Enum):
"""CADWork Element type"""

BEAM = auto() # Stab
Expand All @@ -70,6 +64,7 @@ class ElementType(StrEnum):
LINE = auto()
INSTALLATION_ROUND = auto()
INSTALLATION_STRAIGHT = auto()
DIMENSION = auto()
OTHER = auto()


Expand All @@ -82,10 +77,12 @@ class ElementType(StrEnum):
"Installation rechteckig": ElementType.INSTALLATION_STRAIGHT,
"Fläche": ElementType.SURFACE,
"Installation rund": ElementType.INSTALLATION_ROUND,
"Measurement": ElementType.DIMENSION,
},
"en": {
"Beam": ElementType.BEAM,
"Plate": ElementType.PLATE,
"Bemassung": ElementType.DIMENSION,
},
}

Expand Down
4 changes: 0 additions & 4 deletions src/compas_cadwork/scene/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from compas.plugins import plugin
from compas.plugins import PluginManager
from compas.scene import register

from compas_monosashi.sequencer import Text3d
Expand All @@ -21,9 +20,6 @@

CONTEXT = "cadwork"

# TODO: remove
PluginManager.DEBUG = True


@plugin(category="drawing-utils", requires=[CONTEXT])
def clear(*args, **kwargs):
Expand Down
14 changes: 4 additions & 10 deletions src/compas_cadwork/scene/instructionobject.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import cadwork
from compas.geometry import Vector
from compas_monosashi.sequencer import LinearDimension
from compas_monosashi.sequencer import Model3d
from compas_monosashi.sequencer import Text3d
Expand Down Expand Up @@ -127,18 +126,13 @@ def draw(self, *args, **kwargs):
cadwork element ID of the added dimension.
"""

direction = Vector.from_start_end(
self.linear_dimension.start, self.linear_dimension.end
).unitized() # why is this even needed?

text_plane_normal = self.linear_dimension.location.normal * -1.0
text_plane_origin = self.linear_dimension.location.point.copy()
# text_plane_origin.z += self.linear_dimension.offset
inst_frame = self.linear_dimension.location
distance_vector = inst_frame.point + self.linear_dimension.line_offset
element_id = create_dimension(
vector_to_cadwork(direction),
vector_to_cadwork(inst_frame.xaxis),
vector_to_cadwork(text_plane_normal),
point_to_cadwork(text_plane_origin),
vector_to_cadwork(distance_vector),
[point_to_cadwork(point) for point in self.linear_dimension.points],
)
element = self.add_element(element_id)
Expand Down
13 changes: 11 additions & 2 deletions src/compas_cadwork/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Generator

from enum import auto
from enum import Enum

import cadwork
import utility_controller as uc
Expand All @@ -14,15 +15,15 @@
from compas.geometry import Point
from compas_cadwork.datamodel import Element
from compas_cadwork.datamodel import ElementGroup
from compas_cadwork.datamodel.element import StrEnum
from compas_cadwork.datamodel import Dimension
from compas_cadwork.conversions import point_to_compas

from .ifc_export import IFCExporter
from .ifc_export import IFCExportSettings
from .dimensions import get_dimension_data


class CameraView(StrEnum):
class CameraView(str, Enum):
"""The view direction to which cadwork camera should be set in viewport.
These coinside with the standard axes.
Expand Down Expand Up @@ -166,6 +167,13 @@ def get_filename() -> str:
return uc.get_3d_file_name()


def get_dimensions():
result = []
for dim in filter(lambda element: element.is_linear_dimension, get_all_elements(include_instructions=True)):
result.append(Dimension(dim.id))
return result


def get_element_groups(is_wall_frame: bool = True) -> Dict[str, ElementGroup]:
"""Returns a dictionary mapping names of the available building subgroups to their elements.
Expand Down Expand Up @@ -393,4 +401,5 @@ def save_project_file():
"zoom_active_elements",
"get_dimension_data",
"get_bounding_box_from_cadwork_object",
"get_dimensions",
]
1 change: 1 addition & 0 deletions src/compas_cadwork/utilities/dimensions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# TODO: Once shifting anchor points is no longer required, remove this module as replaced by datamodel.dimension
from typing import List
from typing import Tuple
from typing import Union
Expand Down
35 changes: 34 additions & 1 deletion src/compas_cadwork/utilities/events.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from compas_cadwork.datamodel import Element

from . import get_all_element_ids
from . import get_dimensions


class ElementDelta:
"""Helper for detecting changes in the available element collection"""

def __init__(self):
self._known_element_ids = set(get_all_element_ids())
self._known_element_ids = None
self.reset()

def check_for_changed_elements(self):
"""Returns a list of element ids added to the file database since the last call.
Expand All @@ -26,3 +28,34 @@ def check_for_changed_elements(self):
def reset(self):
"""Reset the known element ids"""
self._known_element_ids = set(get_all_element_ids())


class DimensionsDelta:
"""Helper for detecting edits to the dimensions in the document
TODO: check if and how this can be merged with ElementDelta, seems this could do both jobs, question is just the reset point.
"""

def __init__(self):
self._known_dimensions = None
self.reset()

def check_for_changed_dimensions(self):
"""Returns a list of dimensions that existed but were modified since the last call to :method:`reset`.
Returns
-------
list(:class:`compas_cadwork.datamodel.Dimension`)
List of modified dimensions.
"""
# Changes will contain additions as well, since the objects are compared as a whole..
# However, addtions need to be handled separately. Therefore, new ids are filtered out.
current_dimensions = get_dimensions()
changes = set(current_dimensions) - self._known_dimensions
additions = set([m.id for m in current_dimensions]) - set([m.id for m in self._known_dimensions])
return list(filter(lambda m: m.id not in additions, changes))

def reset(self):
"""Reset the known dimensions. Any changed dimensions after this call will be considered modifications."""
self._known_dimensions = set(get_dimensions())

0 comments on commit 0d4998f

Please sign in to comment.