Skip to content

Commit

Permalink
Merge pull request #140 from gramaziokohler/planning
Browse files Browse the repository at this point in the history
Planning
  • Loading branch information
chenkasirer committed Nov 30, 2023
2 parents 724891e + c134e84 commit e4c76a0
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/compas_timber/connections/joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __init__(self, frame=None, key=None):

@property
def data(self):
return {"frame": self.frame.data, "key": self.key}
return {"frame": self.frame.data, "key": self.key, "beams": [beam.key for beam in self.beams]}

@property
def beams(self):
Expand Down
6 changes: 2 additions & 4 deletions src/compas_timber/connections/l_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,12 @@ def add_features(self):
"""
if self.features:
self.main_beam.remove_features(self.features)
self.cross_beam.remove_features(self.features)

start_main, end_main = self.main_beam.extension_to_plane(self.cutting_plane_main)
start_cross, end_cross = self.cross_beam.extension_to_plane(self.cutting_plane_cross)
self.main_beam.add_blank_extension(start_main, end_main, self.key)
self.cross_beam.add_blank_extension(start_cross, end_cross, self.key)

f_main, f_cross = CutFeature(self.cutting_plane_main), CutFeature(self.cutting_plane_cross)
f_main = CutFeature(self.cutting_plane_main)
self.main_beam.add_features(f_main)
self.cross_beam.add_features(f_cross)
self.features = [f_main, f_cross]
self.features = [f_main]
2 changes: 1 addition & 1 deletion src/compas_timber/consumers/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def apply_drill_feature(beam_geometry, feature):
frame = Frame.from_plane(feature.plane)
drill_volume = Cylinder(frame, radius=feature.diameter / 2.0, height=feature.length)
drill_volume = Cylinder(frame=frame, radius=feature.diameter / 2.0, height=feature.length)
return beam_geometry - Brep.from_cylinder(drill_volume)


Expand Down
6 changes: 5 additions & 1 deletion src/compas_timber/parts/beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def centerline_end(self):

@property
def aabb(self):
vertices = self.blank.vertices
vertices, _ = self.blank.to_vertices_and_faces()
x = [p.x for p in vertices]
y = [p.y for p in vertices]
z = [p.z for p in vertices]
Expand Down Expand Up @@ -292,6 +292,10 @@ def add_blank_extension(self, start, end, joint_key=None):
this extension will be removed as well.
"""
if joint_key is not None and joint_key in self._blank_extensions:
s, e = self._blank_extensions[joint_key]
start += s
end += e
self._blank_extensions[joint_key] = (start, end)

def remove_blank_extension(self, joint_key):
Expand Down
12 changes: 12 additions & 0 deletions src/compas_timber/planning/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .sequencer import Actor
from .sequencer import BuildingPlan
from .sequencer import Step
from .sequencer import SimpleSequenceGenerator


__all__ = [
"Actor",
"BuildingPlan",
"Step",
"SimpleSequenceGenerator",
]
289 changes: 289 additions & 0 deletions src/compas_timber/planning/sequencer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
from compas.data import Data
from compas.data import json_dump
from compas.data import json_load
from compas.geometry import Frame


class Actor(object):
"""Enum representing the types of actor which could execute an assembly instruction."""

HUMAN = 0
ROBOT = 1

@classmethod
def get_name(cls, value):
"""Returns the string representation of given actor value.
For use in logging.
Parameters
----------
value : int
One of [Actor.HUMAN, Actor.ROBOT]
Returns
-------
str
One of ["HUMAN", "ROBOT"]
"""
try:
return {v: k for k, v in Actor.__dict__.items() if not k.startswith("_")}[value]
except KeyError:
return "UNKNOWN_ACTOR"


class Instruction(Data):
"""Base class for instructions"""

def __init__(self, id, location):
super(Instruction, self).__init__()
self.id = id
self.location = location

@property
def data(self):
return {
"id": self.id,
"location": self.location.data,
}

def transform(self, tranformation):
self.location.transform(tranformation)


class Model3d(Instruction):
"""Instruction which incorporates a 3d model (beam, screw etc.)"""

def __init__(self, id, location, geometry, element_id, obj_filepath):
super(Model3d, self).__init__(id, location)
self.geometry = geometry
self.element_id = element_id
self.obj_filepath = obj_filepath

@property
def data(self):
data_dict = {
"geometry": self.geometry,
"element_id": self.element_id,
"obj_filepath": self.obj_filepath,
}
data_dict.update(super(Model3d, self).data)
return data_dict

def transform(self, tranformation):
super(Model3d, self).transform(tranformation)
self.geometry.transform(tranformation)


class Text3d(Instruction):
"""Text overlay"""

def __init__(self, id, location, text, size):
super(Text3d, self).__init__(id, location)
self.text = text
self.size = size

@property
def data(self):
data_dict = {
"text": self.text,
"size": self.size,
}
data_dict.update(super(Text3d, self).data)
return data_dict


class LinearDimension(Instruction):
"""3d linear dimension"""

def __init__(self, id, location, start, end, char_size, offset):
super(LinearDimension, self).__init__(id, location)
self.start = start
self.end = end
self.char_size = char_size
self.offset = offset

@property
def data(self):
data_dict = {
"start": self.start,
"end": self.end,
"char_size": self.char_size,
"offset": self.offset,
}
data_dict.update(super(LinearDimension, self).data)
return data_dict

def transform(self, tranformation):
super(LinearDimension, self).transform(tranformation)
self.start.transform(tranformation)
self.end.transform(tranformation)


class Step(Data):
"""Container for building instructions which assemble a single element
Attributes
----------
actor : :class:`Actor`
Actor which executes the step. one of [Actor.HUMAN, Actor.ROBOT]
element_ids : list(int)
List of cad element ids which are associated with the step.
elements_held : list(int)
List of cad element ids which are held by the actor (typically a robot) during this step.
location : :class:`compas.geometry.Frame`
Location of the step.
instructions : list(:class:`Instruction`)
List of instructions which support the step.
is_built : bool
Whether the step has been executed.
is_planned : bool
Whether the step has been planned. Allows for incremental planning.
geometry : str
Geometry type of the element to be assembled. One of ["obj", "cylinder", "box"]]. Used for visualization.
priority : int
Priority of the step. Steps within the same priority can be executed in parallel.
"""

def __init__(
self,
element_ids,
actor=None,
location=None,
geometry=None,
instructions=None,
is_built=False,
is_planned=False,
elements_held=None,
priority=0,
):
super(Step, self).__init__()
self.element_ids = element_ids or []
self.location = location or Frame.worldXY()
self.geometry = geometry
self.priority = priority
self.instructions = instructions or []
self.is_built = is_built
self.is_planned = is_planned
self.elements_held = elements_held or []
self._actor = actor

@property
def actor(self):
return self._actor

@actor.setter
def actor(self, value):
if isinstance(value, str):
self._actor = getattr(Actor, value, "UNKNOWN_ACTOR")
else:
self._actor = value

@property
def data(self):
return {
"location": self.location.data,
"geometry": self.geometry,
"priority": self.priority,
"element_ids": self.element_ids,
"instructions": self.instructions,
"is_built": self.is_built,
"is_planned": self.is_planned,
"elements_held": self.elements_held,
"actor": Actor.get_name(self.actor),
}

def transform(self, transformation):
self.location.transform(transformation)


class BuildingPlanParser(object):
"""Provides class methods to parse and serialize building plans from and to json files.
This implementation does it the COMPAS way.
Implemet your own `parse()` and `serialize()` methods if you want to use a different format.
"""

@classmethod
def parse(cls, filepath):
"""Parses building plan from json file.
Parameters
----------
filepath : str
Path to json file
Returns
-------
:class:`BuildingPlan`
"""
return json_load(filepath)

@classmethod
def serialize(cls, building_plan, filepath):
"""Writes building plan to json file.
Parameters
----------
building_plan : :class:`BuildingPlan`
Building plan to be serialized.
filepath : str
Path to json file.
"""
json_dump(building_plan, filepath)


class BuildingPlan(Data):
"""Container for building steps, each steps is a collection of instructions which can be visualized"""

def __init__(self, steps=None):
super(BuildingPlan, self).__init__()
self.steps = steps or []

def __repr__(self):
return "BuildingPlan with {} steps".format(len(self.steps))

def __iter__(self):
return iter(self.steps)

def __len__(self):
return len(self.steps)

@property
def data(self):
return {"steps": self.steps}

def add_step(self, step):
self.steps.append(step)


class SimpleSequenceGenerator(object):
"""Generates a simple sequence of steps, one step per element.
Order of steps is the same as order of elements in assembly.
Parameters
----------
assembly : :class:`compas_timber.assembly.TimberAssembly`
Assembly to be sequenced.
Attributes
----------
result : :class:`BuildingPlan`
Resulting building plan.
"""

def __init__(self, assembly):
self.assembly = assembly

@property
def result(self):
plan = BuildingPlan()
for beam in self.assembly.beams:
plan.add_step(Step(element_ids=[beam.key], actor=Actor.HUMAN, location=beam.frame))
return plan
Loading

0 comments on commit e4c76a0

Please sign in to comment.