compas-masonry 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compas_masonry/__init__.py +3 -0
- compas_masonry/__main__.py +2 -0
- compas_masonry/forms/__init__.py +0 -0
- compas_masonry/forms/settings.py +203 -0
- compas_masonry/scene/__init__.py +13 -0
- compas_masonry/scene/envelopeobject.py +94 -0
- compas_masonry/scene/formobject.py +509 -0
- compas_masonry/session.py +10 -0
- compas_masonry/settings.py +53 -0
- compas_masonry/splash.py +44 -0
- compas_masonry-0.2.1.dist-info/METADATA +135 -0
- compas_masonry-0.2.1.dist-info/RECORD +15 -0
- compas_masonry-0.2.1.dist-info/WHEEL +5 -0
- compas_masonry-0.2.1.dist-info/licenses/LICENSE +21 -0
- compas_masonry-0.2.1.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#! python3
|
|
2
|
+
# venv: brg-csd
|
|
3
|
+
# r: compas_masonry
|
|
4
|
+
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
import Eto.Drawing as drawing # type: ignore
|
|
8
|
+
import Eto.Forms as forms # type: ignore
|
|
9
|
+
import Rhino # type: ignore
|
|
10
|
+
import Rhino.UI # type: ignore
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from compas.colors import Color
|
|
15
|
+
from compas_session.settings import Settings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def field_ge(field):
|
|
19
|
+
"""Get the 'greater than or equal to' constraint from a Pydantic field.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
field : ModelField
|
|
24
|
+
The Pydantic model field.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
float or int or None
|
|
29
|
+
The 'greater than or equal to' constraint if it exists, otherwise None.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
ge = None
|
|
33
|
+
for m in field.metadata:
|
|
34
|
+
if hasattr(m, "ge"):
|
|
35
|
+
ge = m.ge
|
|
36
|
+
break
|
|
37
|
+
return ge
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def field_le(field):
|
|
41
|
+
"""Get the 'less than or equal to' constraint from a Pydantic field.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
field : ModelField
|
|
46
|
+
The Pydantic model field.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
float or int or None
|
|
51
|
+
The 'less than or equal to' constraint if it exists, otherwise None.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
le = None
|
|
55
|
+
for m in field.metadata:
|
|
56
|
+
if hasattr(m, "le"):
|
|
57
|
+
le = m.le
|
|
58
|
+
break
|
|
59
|
+
return le
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SettingsForm(forms.Dialog[bool]):
|
|
63
|
+
"""A dynamic form for editing Pydantic models.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
model_cls : Type[BaseModel]
|
|
68
|
+
The Pydantic model class to create the form for.
|
|
69
|
+
width : int, optional
|
|
70
|
+
The width of the form, by default 400.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, model: Union[Settings, BaseModel], title: str = "Settings", width: int = 400):
|
|
75
|
+
super().__init__()
|
|
76
|
+
self.ClientSize = drawing.Size(width, -1)
|
|
77
|
+
self.model = model
|
|
78
|
+
self.model_cls = type(model)
|
|
79
|
+
self.controls = {}
|
|
80
|
+
self.Title = title
|
|
81
|
+
|
|
82
|
+
self.Content = self._build_layout()
|
|
83
|
+
|
|
84
|
+
self.DefaultButton = self.ok_button
|
|
85
|
+
self.AbortButton = self.cancel_button
|
|
86
|
+
|
|
87
|
+
def _build_layout(self):
|
|
88
|
+
layout = forms.DynamicLayout()
|
|
89
|
+
layout.Padding = drawing.Padding(10)
|
|
90
|
+
layout.Spacing = drawing.Size(10, 10)
|
|
91
|
+
|
|
92
|
+
for name, field in self.model_cls.model_fields.items():
|
|
93
|
+
if issubclass(field.annotation, BaseModel): # type: ignore
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
text = field.title or name.replace("_", " ").capitalize()
|
|
97
|
+
label = forms.Label()
|
|
98
|
+
label.Text = text
|
|
99
|
+
control = self._create_control(name, field, field.default)
|
|
100
|
+
self.controls[name] = control
|
|
101
|
+
layout.AddRow(label, control)
|
|
102
|
+
|
|
103
|
+
layout.AddRow(None)
|
|
104
|
+
|
|
105
|
+
self.ok_button = forms.Button()
|
|
106
|
+
self.ok_button.Text = "OK"
|
|
107
|
+
self.ok_button.Click += self._on_ok
|
|
108
|
+
|
|
109
|
+
self.cancel_button = forms.Button()
|
|
110
|
+
self.cancel_button.Text = "Cancel"
|
|
111
|
+
self.cancel_button.Click += self._on_cancel
|
|
112
|
+
|
|
113
|
+
layout.AddRow(None, self.ok_button, self.cancel_button)
|
|
114
|
+
return layout
|
|
115
|
+
|
|
116
|
+
def _create_control(self, name, field, default):
|
|
117
|
+
if field.annotation is str:
|
|
118
|
+
value = getattr(self.model, name, default) or ""
|
|
119
|
+
control = forms.TextBox()
|
|
120
|
+
control.Text = value
|
|
121
|
+
|
|
122
|
+
elif field.annotation is int:
|
|
123
|
+
value = getattr(self.model, name, default) or 0
|
|
124
|
+
control = forms.NumericUpDown()
|
|
125
|
+
control.Value = value
|
|
126
|
+
control.MinValue = field_ge(field)
|
|
127
|
+
control.MaxValue = field_le(field)
|
|
128
|
+
|
|
129
|
+
elif field.annotation is float:
|
|
130
|
+
value = getattr(self.model, name, default) or 0.0
|
|
131
|
+
control = forms.NumericUpDown()
|
|
132
|
+
control.Value = value
|
|
133
|
+
control.DecimalPlaces = 3
|
|
134
|
+
control.MinValue = field_ge(field)
|
|
135
|
+
control.MaxValue = field_le(field)
|
|
136
|
+
|
|
137
|
+
elif field.annotation is bool:
|
|
138
|
+
value = getattr(self.model, name, default) or False
|
|
139
|
+
control = forms.CheckBox()
|
|
140
|
+
control.Checked = value
|
|
141
|
+
|
|
142
|
+
elif (
|
|
143
|
+
field.annotation is tuple
|
|
144
|
+
and hasattr(field, "default")
|
|
145
|
+
and isinstance(field.default, tuple)
|
|
146
|
+
and len(field.default) == 4
|
|
147
|
+
and all(isinstance(x, int) for x in field.default)
|
|
148
|
+
):
|
|
149
|
+
value = getattr(self.model, name, default) or (255, 255, 255, 255)
|
|
150
|
+
color = drawing.Color.FromArgb(*value)
|
|
151
|
+
control = forms.ColorPicker()
|
|
152
|
+
control.Value = color
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
value = getattr(self.model, name, default) or ""
|
|
156
|
+
control = forms.TextBox()
|
|
157
|
+
control.Text = str(value)
|
|
158
|
+
|
|
159
|
+
return control
|
|
160
|
+
|
|
161
|
+
def _on_ok(self, sender, e):
|
|
162
|
+
values = {}
|
|
163
|
+
for name, control in self.controls.items():
|
|
164
|
+
if isinstance(control, forms.TextBox):
|
|
165
|
+
values[name] = control.Text
|
|
166
|
+
elif isinstance(control, forms.NumericUpDown):
|
|
167
|
+
values[name] = control.Value
|
|
168
|
+
elif isinstance(control, forms.CheckBox):
|
|
169
|
+
values[name] = control.Checked
|
|
170
|
+
elif isinstance(control, forms.ColorPicker):
|
|
171
|
+
c = control.Value
|
|
172
|
+
values[name] = (c.R, c.G, c.B, c.A)
|
|
173
|
+
try:
|
|
174
|
+
for key, value in values.items():
|
|
175
|
+
setattr(self.model, key, value)
|
|
176
|
+
self.Close(True)
|
|
177
|
+
except Exception as ex:
|
|
178
|
+
forms.MessageBox.Show(str(ex), "Validation Error")
|
|
179
|
+
|
|
180
|
+
def _on_cancel(self, sender, e):
|
|
181
|
+
self.Close(False)
|
|
182
|
+
|
|
183
|
+
def show(self):
|
|
184
|
+
return self.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# =============================================================================
|
|
188
|
+
# Run as main
|
|
189
|
+
# =============================================================================
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
|
|
193
|
+
class TestSettings(BaseModel):
|
|
194
|
+
name: str = Field("Default", title="Name", description="Enter your name")
|
|
195
|
+
age: int = Field(30, title="Age", ge=0, le=120)
|
|
196
|
+
is_active: bool = Field(True, title="Active")
|
|
197
|
+
color: tuple = Field(Color.white().rgba255, title="Color", description="Pick a color")
|
|
198
|
+
|
|
199
|
+
settings = TestSettings(name="Alice", age=28, is_active=True, color=Color.red().rgba255)
|
|
200
|
+
dialog = SettingsForm(settings) # type: ignore
|
|
201
|
+
|
|
202
|
+
if dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow):
|
|
203
|
+
print(dialog.model)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from compas.plugins import plugin
|
|
2
|
+
from compas.scene.context import register
|
|
3
|
+
|
|
4
|
+
from compas_tna.diagrams import FormDiagram
|
|
5
|
+
from .formobject import RhinoFormDiagramObject
|
|
6
|
+
|
|
7
|
+
# this clashes with TNA and/or RV
|
|
8
|
+
# we need a strategy for this
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@plugin(category="factories", pluggable_name="register_scene_objects", requires=["Rhino"])
|
|
12
|
+
def register_scene_objects_rhino():
|
|
13
|
+
register(FormDiagram, RhinoFormDiagramObject, context="Rhino")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import scriptcontext as sc # type: ignore
|
|
4
|
+
|
|
5
|
+
from compas.colors import Color
|
|
6
|
+
from compas.datastructures import Mesh
|
|
7
|
+
from compas.scene.descriptors.color import ColorAttribute
|
|
8
|
+
from compas_masonry.session import MasonrySession as Session
|
|
9
|
+
from compas_rhino.conversions import transformation_to_rhino
|
|
10
|
+
from compas_rhino.conversions import vertices_and_faces_to_rhino
|
|
11
|
+
from compas_rhino.scene import RhinoSceneObject
|
|
12
|
+
from compas_tna.envelope import Envelope
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RhinoEnvelopeObject(RhinoSceneObject):
|
|
16
|
+
session: Session = Session()
|
|
17
|
+
|
|
18
|
+
color = ColorAttribute(default=Color.black())
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
color: Optional[Color] = None,
|
|
23
|
+
show_intrados=True,
|
|
24
|
+
show_middle=False,
|
|
25
|
+
show_extrados=True,
|
|
26
|
+
**kwargs,
|
|
27
|
+
):
|
|
28
|
+
super().__init__(**kwargs)
|
|
29
|
+
self.color = color or self.color
|
|
30
|
+
self.show_intrados = show_intrados
|
|
31
|
+
self.show_middle = show_middle
|
|
32
|
+
self.show_extrados = show_extrados
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def settings(self) -> dict:
|
|
36
|
+
settings = super().settings
|
|
37
|
+
settings["color"] = self.color
|
|
38
|
+
settings["show_intrados"] = self.show_intrados
|
|
39
|
+
settings["show_middle"] = self.show_middle
|
|
40
|
+
settings["show_extrados"] = self.show_extrados
|
|
41
|
+
return settings
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def envelope(self) -> Envelope:
|
|
45
|
+
return self.item # type: ignore
|
|
46
|
+
|
|
47
|
+
@envelope.setter
|
|
48
|
+
def envelope(self, envelope: Envelope) -> None:
|
|
49
|
+
self._item = envelope
|
|
50
|
+
self._transformation = None
|
|
51
|
+
|
|
52
|
+
def draw(self):
|
|
53
|
+
self._guids = []
|
|
54
|
+
|
|
55
|
+
if self.show_intrados:
|
|
56
|
+
self.draw_intrados()
|
|
57
|
+
|
|
58
|
+
if self.show_middle:
|
|
59
|
+
self.draw_middle()
|
|
60
|
+
|
|
61
|
+
if self.show_extrados:
|
|
62
|
+
self.draw_extrados()
|
|
63
|
+
|
|
64
|
+
return self._guids
|
|
65
|
+
|
|
66
|
+
def _draw_mesh(self, mesh: Mesh, name: str) -> None:
|
|
67
|
+
attr = self.compile_attributes(name=name, color=self.color)
|
|
68
|
+
vertex_index = mesh.vertex_index()
|
|
69
|
+
vertices = [mesh.vertex_attributes(vertex, "xyz") for vertex in mesh.vertices()]
|
|
70
|
+
faces = [[vertex_index[vertex] for vertex in mesh.face_vertices(face)] for face in mesh.faces()]
|
|
71
|
+
geometry = vertices_and_faces_to_rhino(vertices, faces, color=self.color, disjoint=True)
|
|
72
|
+
geometry.Transform(transformation_to_rhino(self.worldtransformation))
|
|
73
|
+
guid = sc.doc.Objects.AddMesh(geometry, attr)
|
|
74
|
+
if self.group:
|
|
75
|
+
self.add_to_group(self.group, [guid])
|
|
76
|
+
return guid
|
|
77
|
+
|
|
78
|
+
def draw_intrados(self):
|
|
79
|
+
if not self.envelope.intrados: # type: ignore
|
|
80
|
+
return
|
|
81
|
+
guid = self._draw_mesh(self.envelope.intrados, name="Intrados") # type: ignore
|
|
82
|
+
self._guids.append(guid)
|
|
83
|
+
|
|
84
|
+
def draw_middle(self):
|
|
85
|
+
if not self.envelope.middle: # type: ignore
|
|
86
|
+
return
|
|
87
|
+
guid = self._draw_mesh(self.envelope.middle, name="Middle") # type: ignore
|
|
88
|
+
self._guids.append(guid)
|
|
89
|
+
|
|
90
|
+
def draw_extrados(self):
|
|
91
|
+
if not self.envelope.extrados: # type: ignore
|
|
92
|
+
return
|
|
93
|
+
guid = self._draw_mesh(self.envelope.extrados, name="Extrados") # type: ignore
|
|
94
|
+
self._guids.append(guid)
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
from math import sqrt
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import rhinoscriptsyntax as rs # type: ignore
|
|
5
|
+
import scriptcontext as sc # type: ignore
|
|
6
|
+
|
|
7
|
+
import compas_rhino.conversions
|
|
8
|
+
from compas.colors import Color
|
|
9
|
+
from compas.geometry import Cylinder
|
|
10
|
+
from compas.geometry import Line
|
|
11
|
+
from compas.geometry import Sphere
|
|
12
|
+
from compas.geometry import Vector
|
|
13
|
+
from compas.scene.descriptors.color import ColorAttribute
|
|
14
|
+
from compas.scene.descriptors.colordict import ColorDictAttribute
|
|
15
|
+
from compas_masonry.session import MasonrySession as Session
|
|
16
|
+
from compas_rui.scene import RUIMeshObject
|
|
17
|
+
from compas_tna.diagrams import FormDiagram
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RhinoFormDiagramObject(RUIMeshObject):
|
|
21
|
+
session: Session = Session()
|
|
22
|
+
|
|
23
|
+
vertexcolor = ColorDictAttribute(default=Color.white())
|
|
24
|
+
edgecolor = ColorDictAttribute(default=Color.black())
|
|
25
|
+
facecolor = ColorDictAttribute(default=Color.white())
|
|
26
|
+
|
|
27
|
+
freecolor = ColorAttribute(default=Color.white())
|
|
28
|
+
supportcolor = ColorAttribute(default=Color.red())
|
|
29
|
+
fixedcolor = ColorAttribute(default=Color.cyan())
|
|
30
|
+
|
|
31
|
+
residualcolor = ColorAttribute(default=Color.cyan())
|
|
32
|
+
reactioncolor = ColorAttribute(default=Color.green())
|
|
33
|
+
|
|
34
|
+
loadcolor = ColorAttribute(default=Color.green().darkened(50))
|
|
35
|
+
compressioncolor = ColorAttribute(default=Color.blue())
|
|
36
|
+
tensioncolor = ColorAttribute(default=Color.red())
|
|
37
|
+
|
|
38
|
+
ecrackcolor = ColorAttribute(default=Color.green())
|
|
39
|
+
icrackcolor = ColorAttribute(default=Color.blue())
|
|
40
|
+
|
|
41
|
+
boundscolor = ColorAttribute(default=Color.magenta())
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
disjoint=True,
|
|
46
|
+
show_vertices=True,
|
|
47
|
+
show_edges=True,
|
|
48
|
+
show_faces=False,
|
|
49
|
+
vertexgroup="RhinoVAULT::FormDiagram::Vertices",
|
|
50
|
+
edgegroup="RhinoVAULT::FormDiagram::Edges",
|
|
51
|
+
facegroup="RhinoVAULT::FormDiagram::Faces",
|
|
52
|
+
layer="RhinoVAULT::FormDiagram",
|
|
53
|
+
show_supports=True,
|
|
54
|
+
show_fixed=True,
|
|
55
|
+
show_free=False,
|
|
56
|
+
loadgroup="RhinoVAULT::FormDiagram::Loads",
|
|
57
|
+
forcegroup="RhinoVAULT::FormDiagram::Forces",
|
|
58
|
+
reactiongroup="RhinoVAULT::FormDiagram::Reactions",
|
|
59
|
+
residualgroup="RhinoVAULT::FormDiagram::Residuals",
|
|
60
|
+
crackgroup="RhinoVAULT::FormDiagram::Cracks",
|
|
61
|
+
boundsgroup="RhinoVAULT::FormDiagram::Bounds",
|
|
62
|
+
**kwargs,
|
|
63
|
+
):
|
|
64
|
+
super().__init__(
|
|
65
|
+
disjoint=disjoint,
|
|
66
|
+
show_vertices=show_vertices,
|
|
67
|
+
show_edges=show_edges,
|
|
68
|
+
show_faces=show_faces,
|
|
69
|
+
vertexgroup=vertexgroup,
|
|
70
|
+
edgegroup=edgegroup,
|
|
71
|
+
facegroup=facegroup,
|
|
72
|
+
layer=layer,
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.show_supports = show_supports
|
|
77
|
+
self.show_fixed = show_fixed
|
|
78
|
+
self.show_free = show_free
|
|
79
|
+
|
|
80
|
+
self.loadgroup = loadgroup
|
|
81
|
+
self.forcegroup = forcegroup
|
|
82
|
+
self.reactiongroup = reactiongroup
|
|
83
|
+
self.residualgroup = residualgroup
|
|
84
|
+
self.crackgroup = crackgroup
|
|
85
|
+
self.boundsgroup = boundsgroup
|
|
86
|
+
|
|
87
|
+
# =============================================================================
|
|
88
|
+
# Properties
|
|
89
|
+
# =============================================================================
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def settings(self):
|
|
93
|
+
settings = super().settings
|
|
94
|
+
settings["show_supports"] = self.show_supports
|
|
95
|
+
settings["show_fixed"] = self.show_fixed
|
|
96
|
+
settings["show_free"] = self.show_free
|
|
97
|
+
return settings
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def diagram(self) -> FormDiagram:
|
|
101
|
+
return self.mesh # type: ignore
|
|
102
|
+
|
|
103
|
+
@diagram.setter
|
|
104
|
+
def diagram(self, diagram: FormDiagram) -> None:
|
|
105
|
+
self.mesh = diagram
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# Helpers
|
|
109
|
+
# =============================================================================
|
|
110
|
+
|
|
111
|
+
def vertices(self, **kwargs) -> list[int]:
|
|
112
|
+
return list(self.diagram.vertices()) # type: ignore
|
|
113
|
+
|
|
114
|
+
def edges(self, **kwargs) -> list[tuple[int, int]]:
|
|
115
|
+
return list(self.diagram.edges_where(_is_edge=True)) # type: ignore
|
|
116
|
+
|
|
117
|
+
def faces(self, **kwargs) -> list[int]:
|
|
118
|
+
return list(self.diagram.faces_where(_is_loaded=True)) # type: ignore
|
|
119
|
+
|
|
120
|
+
def supports(self) -> list[int]:
|
|
121
|
+
return list(self.diagram.vertices_where(is_support=True)) # type: ignore
|
|
122
|
+
|
|
123
|
+
def vertex_is_support(self, vertex) -> bool:
|
|
124
|
+
return bool(self.diagram.vertex_attribute(vertex, "is_support"))
|
|
125
|
+
|
|
126
|
+
def vertex_is_fixed(self, vertex) -> bool:
|
|
127
|
+
return bool(self.diagram.vertex_attribute(vertex, "is_fixed"))
|
|
128
|
+
|
|
129
|
+
def vertex_residual(self, vertex) -> Vector:
|
|
130
|
+
return Vector(*self.diagram.vertex_attributes(vertex, ["_rx", "_ry", "_rz"])) # type: ignore
|
|
131
|
+
|
|
132
|
+
def vertex_weight(self, vertex) -> float:
|
|
133
|
+
weight = 0
|
|
134
|
+
thickness = self.diagram.vertex_attribute(vertex, "t")
|
|
135
|
+
if thickness:
|
|
136
|
+
area = self.diagram.vertex_area(vertex)
|
|
137
|
+
weight = area * thickness
|
|
138
|
+
return weight
|
|
139
|
+
|
|
140
|
+
def vertex_load(self, vertex) -> Vector:
|
|
141
|
+
return Vector(*self.diagram.vertex_attributes(vertex, ["px", "py", "pz"])) # type: ignore
|
|
142
|
+
|
|
143
|
+
def vertex_bound(self, vertex) -> Optional[Line]:
|
|
144
|
+
ub = self.diagram.vertex_attribute(vertex, "ub")
|
|
145
|
+
lb = self.diagram.vertex_attribute(vertex, "lb")
|
|
146
|
+
if ub and lb:
|
|
147
|
+
point = self.diagram.vertex_point(vertex)
|
|
148
|
+
a = point.copy()
|
|
149
|
+
a.z = ub
|
|
150
|
+
b = point.copy()
|
|
151
|
+
b.z = lb
|
|
152
|
+
return Line(a, b)
|
|
153
|
+
|
|
154
|
+
def vertex_is_on_upper_bound(self, vertex, tol=1e-6) -> bool:
|
|
155
|
+
ub = self.diagram.vertex_attribute(vertex, "ub")
|
|
156
|
+
if ub is None:
|
|
157
|
+
return False
|
|
158
|
+
point = self.diagram.vertex_point(vertex)
|
|
159
|
+
return abs(point.z - ub) < tol
|
|
160
|
+
|
|
161
|
+
def vertex_is_on_lower_bound(self, vertex, tol=1e-6) -> bool:
|
|
162
|
+
lb = self.diagram.vertex_attribute(vertex, "lb")
|
|
163
|
+
if lb is None:
|
|
164
|
+
return False
|
|
165
|
+
point = self.diagram.vertex_point(vertex)
|
|
166
|
+
return abs(point.z - lb) < tol
|
|
167
|
+
|
|
168
|
+
def forces(self) -> list[float]:
|
|
169
|
+
Q = [self.diagram.edge_attribute(edge, "q") or 0.0 for edge in self.edges()]
|
|
170
|
+
L = [self.diagram.edge_length(edge) or 0.0 for edge in self.edges()]
|
|
171
|
+
return [q * l for q, l in zip(Q, L)] # noqa: E741
|
|
172
|
+
|
|
173
|
+
def edge_force(self, edge) -> float:
|
|
174
|
+
q = self.diagram.edge_attribute(edge, "q") or 0.0
|
|
175
|
+
l = self.diagram.edge_length(edge) or 0.0 # noqa: E741
|
|
176
|
+
return q * l
|
|
177
|
+
|
|
178
|
+
def compute_vertex_color(self, vertex) -> Color:
|
|
179
|
+
if self.vertex_is_support(vertex):
|
|
180
|
+
color = self.supportcolor
|
|
181
|
+
elif self.vertex_is_fixed(vertex):
|
|
182
|
+
color = self.fixedcolor
|
|
183
|
+
else:
|
|
184
|
+
color = self.freecolor
|
|
185
|
+
return color # type: ignore
|
|
186
|
+
|
|
187
|
+
def compute_visible_vertices(self) -> list[int]:
|
|
188
|
+
vertices = []
|
|
189
|
+
if self.show_free:
|
|
190
|
+
vertices += list(self.diagram.vertices_where(is_support=False, is_fixed=False))
|
|
191
|
+
if self.show_fixed:
|
|
192
|
+
vertices += list(self.diagram.vertices_where(is_fixed=True))
|
|
193
|
+
if self.show_supports:
|
|
194
|
+
vertices += list(self.diagram.vertices_where(is_support=True))
|
|
195
|
+
return vertices
|
|
196
|
+
|
|
197
|
+
def compute_edge_colors(self, tol=1e-3) -> list[Color]:
|
|
198
|
+
forces = self.forces()
|
|
199
|
+
magnitudes = [abs(f) for f in forces]
|
|
200
|
+
fmin = min(magnitudes)
|
|
201
|
+
fmax = max(magnitudes)
|
|
202
|
+
|
|
203
|
+
colors = []
|
|
204
|
+
|
|
205
|
+
if fmax - fmin >= tol:
|
|
206
|
+
for magnitude in magnitudes:
|
|
207
|
+
colors.append(Color.from_i((magnitude - fmin) / (fmax - fmin)))
|
|
208
|
+
|
|
209
|
+
return colors
|
|
210
|
+
|
|
211
|
+
def compute_pipe_colors(self, tol=1e-3) -> dict[tuple[int, int], Color]:
|
|
212
|
+
edges = self.edges()
|
|
213
|
+
forces = [self.edge_force(edge) for edge in edges]
|
|
214
|
+
magnitudes = [abs(f) for f in forces]
|
|
215
|
+
fmin = min(magnitudes)
|
|
216
|
+
fmax = max(magnitudes)
|
|
217
|
+
|
|
218
|
+
edge_color = {}
|
|
219
|
+
|
|
220
|
+
if fmax - fmin >= tol:
|
|
221
|
+
for edge, force, magnitude in zip(edges, forces, magnitudes):
|
|
222
|
+
edge_color[edge] = Color.from_i((magnitude - fmin) / (fmax - fmin))
|
|
223
|
+
|
|
224
|
+
return edge_color
|
|
225
|
+
|
|
226
|
+
# =============================================================================
|
|
227
|
+
# Names
|
|
228
|
+
# =============================================================================
|
|
229
|
+
|
|
230
|
+
def vertex_load_name(self, vertex) -> str:
|
|
231
|
+
return f"{self.diagram.name}.vertex.{vertex}.load"
|
|
232
|
+
|
|
233
|
+
def vertex_selfweight_name(self, vertex) -> str:
|
|
234
|
+
return f"{self.diagram.name}.vertex.{vertex}.selfweight"
|
|
235
|
+
|
|
236
|
+
def vertex_reaction_name(self, vertex) -> str:
|
|
237
|
+
return f"{self.diagram.name}.vertex.{vertex}.reaction"
|
|
238
|
+
|
|
239
|
+
def vertex_residual_name(self, vertex) -> str:
|
|
240
|
+
return f"{self.diagram.name}.vertex.{vertex}.residual"
|
|
241
|
+
|
|
242
|
+
def vertex_bound_name(self, vertex) -> str:
|
|
243
|
+
return f"{self.diagram.name}.vertex.{vertex}.bound"
|
|
244
|
+
|
|
245
|
+
def vertex_crack_name(self, vertex) -> str:
|
|
246
|
+
return f"{self.diagram.name}.vertex.{vertex}.crack"
|
|
247
|
+
|
|
248
|
+
def edge_pipe_name(self, edge) -> str:
|
|
249
|
+
return f"{self.diagram.name}.edge.{edge}.pipe"
|
|
250
|
+
|
|
251
|
+
# =============================================================================
|
|
252
|
+
# Clear
|
|
253
|
+
# =============================================================================
|
|
254
|
+
|
|
255
|
+
# =============================================================================
|
|
256
|
+
# Draw
|
|
257
|
+
# =============================================================================
|
|
258
|
+
|
|
259
|
+
def draw(self):
|
|
260
|
+
faces = []
|
|
261
|
+
if self.show_faces:
|
|
262
|
+
faces += list(self.faces())
|
|
263
|
+
if faces:
|
|
264
|
+
self.show_faces = faces
|
|
265
|
+
|
|
266
|
+
for vertex in self.vertices():
|
|
267
|
+
self.vertexcolor[vertex] = self.compute_vertex_color(vertex)
|
|
268
|
+
|
|
269
|
+
super().draw()
|
|
270
|
+
|
|
271
|
+
if self.session.settings.formdiagram.show_reactions:
|
|
272
|
+
self.draw_reactions()
|
|
273
|
+
|
|
274
|
+
if self.session.settings.formdiagram.show_residuals:
|
|
275
|
+
self.draw_residuals()
|
|
276
|
+
|
|
277
|
+
if self.session.settings.formdiagram.show_pipes:
|
|
278
|
+
self.draw_pipes()
|
|
279
|
+
|
|
280
|
+
if self.session.settings.formdiagram.show_loads:
|
|
281
|
+
self.draw_loads()
|
|
282
|
+
|
|
283
|
+
if self.session.settings.formdiagram.show_bounds:
|
|
284
|
+
self.draw_bounds()
|
|
285
|
+
|
|
286
|
+
if self.session.settings.formdiagram.show_cracks:
|
|
287
|
+
self.draw_cracks()
|
|
288
|
+
|
|
289
|
+
return self.guids
|
|
290
|
+
|
|
291
|
+
def draw_vertices(self):
|
|
292
|
+
if self.show_vertices is True:
|
|
293
|
+
self.show_vertices = self.compute_visible_vertices()
|
|
294
|
+
|
|
295
|
+
for vertex in self.vertices():
|
|
296
|
+
self.vertexcolor[vertex] = self.compute_vertex_color(vertex)
|
|
297
|
+
|
|
298
|
+
return super().draw_vertices()
|
|
299
|
+
|
|
300
|
+
def draw_edges(self):
|
|
301
|
+
if self.show_edges is True:
|
|
302
|
+
self.show_edges = self.edges()
|
|
303
|
+
|
|
304
|
+
return super().draw_edges()
|
|
305
|
+
|
|
306
|
+
def draw_faces(self):
|
|
307
|
+
if self.show_faces:
|
|
308
|
+
self.show_faces = self.faces()
|
|
309
|
+
|
|
310
|
+
return super().draw_faces()
|
|
311
|
+
|
|
312
|
+
# =============================================================================
|
|
313
|
+
# Redraw
|
|
314
|
+
# =============================================================================
|
|
315
|
+
|
|
316
|
+
def redraw_vertices(self):
|
|
317
|
+
rs.EnableRedraw(False)
|
|
318
|
+
self.clear_vertices()
|
|
319
|
+
self.draw_vertices()
|
|
320
|
+
rs.EnableRedraw(True)
|
|
321
|
+
rs.Redraw()
|
|
322
|
+
|
|
323
|
+
def redraw_edges(self):
|
|
324
|
+
rs.EnableRedraw(False)
|
|
325
|
+
self.clear_edges()
|
|
326
|
+
self.draw_edges()
|
|
327
|
+
rs.EnableRedraw(True)
|
|
328
|
+
rs.Redraw()
|
|
329
|
+
|
|
330
|
+
def redraw_faces(self):
|
|
331
|
+
rs.EnableRedraw(False)
|
|
332
|
+
self.clear_faces()
|
|
333
|
+
self.draw_faces()
|
|
334
|
+
rs.EnableRedraw(True)
|
|
335
|
+
rs.Redraw()
|
|
336
|
+
|
|
337
|
+
def redraw(self):
|
|
338
|
+
rs.EnableRedraw(False)
|
|
339
|
+
self.clear()
|
|
340
|
+
self.draw()
|
|
341
|
+
rs.EnableRedraw(True)
|
|
342
|
+
rs.Redraw()
|
|
343
|
+
|
|
344
|
+
# =============================================================================
|
|
345
|
+
# Structural
|
|
346
|
+
# =============================================================================
|
|
347
|
+
|
|
348
|
+
def draw_loads(self):
|
|
349
|
+
guids = []
|
|
350
|
+
color = self.loadcolor
|
|
351
|
+
scale = self.session.settings.formdiagram.scale_loads
|
|
352
|
+
tol = self.session.settings.formdiagram.tol_vectors
|
|
353
|
+
|
|
354
|
+
for vertex in self.diagram.vertices_where(is_support=False):
|
|
355
|
+
load = self.vertex_load(vertex)
|
|
356
|
+
if load is not None:
|
|
357
|
+
vector = load * scale
|
|
358
|
+
if vector.length > tol:
|
|
359
|
+
name = self.vertex_load_name(vertex)
|
|
360
|
+
attr = self.compile_attributes(name=name, color=color, arrow="end")
|
|
361
|
+
point = self.diagram.vertex_point(vertex)
|
|
362
|
+
line = Line.from_point_and_vector(point + vector, point)
|
|
363
|
+
guid = sc.doc.Objects.AddLine(compas_rhino.conversions.line_to_rhino(line), attr)
|
|
364
|
+
guids.append(guid)
|
|
365
|
+
|
|
366
|
+
if guids:
|
|
367
|
+
if self.loadgroup:
|
|
368
|
+
self.add_to_group(self.loadgroup, guids)
|
|
369
|
+
elif self.group:
|
|
370
|
+
self.add_to_group(self.group, guids)
|
|
371
|
+
|
|
372
|
+
self._guids += guids
|
|
373
|
+
return guids
|
|
374
|
+
|
|
375
|
+
def draw_pipes(self):
|
|
376
|
+
guids = []
|
|
377
|
+
scale = self.session.settings.formdiagram.scale_pipes
|
|
378
|
+
tol = self.session.settings.formdiagram.tol_pipes
|
|
379
|
+
|
|
380
|
+
for edge in self.edges():
|
|
381
|
+
force = self.edge_force(edge)
|
|
382
|
+
if force:
|
|
383
|
+
line = self.diagram.edge_line(edge)
|
|
384
|
+
radius = sqrt(abs(force)) * scale
|
|
385
|
+
color = self.compressioncolor
|
|
386
|
+
if radius > tol:
|
|
387
|
+
pipe = Cylinder.from_line_and_radius(line, radius)
|
|
388
|
+
name = self.edge_pipe_name(edge)
|
|
389
|
+
attr = self.compile_attributes(name=name, color=color)
|
|
390
|
+
guid = sc.doc.Objects.AddBrep(compas_rhino.conversions.cylinder_to_rhino_brep(pipe), attr)
|
|
391
|
+
guids.append(guid)
|
|
392
|
+
|
|
393
|
+
if guids:
|
|
394
|
+
if self.forcegroup:
|
|
395
|
+
self.add_to_group(self.forcegroup, guids)
|
|
396
|
+
elif self.group:
|
|
397
|
+
self.add_to_group(self.group, guids)
|
|
398
|
+
|
|
399
|
+
self._guids += guids
|
|
400
|
+
return guids
|
|
401
|
+
|
|
402
|
+
def draw_reactions(self):
|
|
403
|
+
guids = []
|
|
404
|
+
scale = self.session.settings.formdiagram.scale_reactions
|
|
405
|
+
tol = self.session.settings.formdiagram.tol_vectors
|
|
406
|
+
|
|
407
|
+
for vertex in self.supports():
|
|
408
|
+
residual = self.vertex_residual(vertex)
|
|
409
|
+
vector = residual * -scale
|
|
410
|
+
if vector.length > tol:
|
|
411
|
+
name = self.vertex_reaction_name(vertex)
|
|
412
|
+
attr = self.compile_attributes(name=name, color=self.reactioncolor, arrow="end")
|
|
413
|
+
point = self.diagram.vertex_point(vertex)
|
|
414
|
+
line = Line.from_point_and_vector(point, vector)
|
|
415
|
+
guid = sc.doc.Objects.AddLine(compas_rhino.conversions.line_to_rhino(line), attr)
|
|
416
|
+
guids.append(guid)
|
|
417
|
+
|
|
418
|
+
if guids:
|
|
419
|
+
if self.reactiongroup:
|
|
420
|
+
self.add_to_group(self.reactiongroup, guids)
|
|
421
|
+
elif self.group:
|
|
422
|
+
self.add_to_group(self.group, guids)
|
|
423
|
+
|
|
424
|
+
self._guids += guids
|
|
425
|
+
return guids
|
|
426
|
+
|
|
427
|
+
def draw_residuals(self):
|
|
428
|
+
guids = []
|
|
429
|
+
scale = self.session.settings.formdiagram.scale_residuals
|
|
430
|
+
tol = self.session.settings.formdiagram.tol_vectors
|
|
431
|
+
|
|
432
|
+
for vertex in self.diagram.vertices_where(is_support=False):
|
|
433
|
+
residual = self.vertex_residual(vertex)
|
|
434
|
+
vector = residual * scale
|
|
435
|
+
if vector.length > tol:
|
|
436
|
+
name = self.vertex_residual_name(vertex)
|
|
437
|
+
attr = self.compile_attributes(name=name, color=self.residualcolor, arrow="end")
|
|
438
|
+
point = self.diagram.vertex_point(vertex)
|
|
439
|
+
line = Line.from_point_and_vector(point, vector)
|
|
440
|
+
guid = sc.doc.Objects.AddLine(compas_rhino.conversions.line_to_rhino(line), attr)
|
|
441
|
+
guids.append(guid)
|
|
442
|
+
|
|
443
|
+
if guids:
|
|
444
|
+
if self.residualgroup:
|
|
445
|
+
self.add_to_group(self.residualgroup, guids)
|
|
446
|
+
elif self.group:
|
|
447
|
+
self.add_to_group(self.group, guids)
|
|
448
|
+
|
|
449
|
+
self._guids += guids
|
|
450
|
+
return guids
|
|
451
|
+
|
|
452
|
+
# =============================================================================
|
|
453
|
+
# Envelope
|
|
454
|
+
# =============================================================================
|
|
455
|
+
|
|
456
|
+
def draw_bounds(self):
|
|
457
|
+
guids = []
|
|
458
|
+
|
|
459
|
+
for vertex in self.vertices():
|
|
460
|
+
bound = self.vertex_bound(vertex)
|
|
461
|
+
if bound:
|
|
462
|
+
name = self.vertex_bound_name(vertex)
|
|
463
|
+
attr = self.compile_attributes(name=name, color=self.boundscolor)
|
|
464
|
+
|
|
465
|
+
guid = sc.doc.Objects.AddLine(compas_rhino.conversions.line_to_rhino(bound), attr)
|
|
466
|
+
guids.append(guid)
|
|
467
|
+
|
|
468
|
+
guid = sc.doc.Objects.AddPoint(compas_rhino.conversions.point_to_rhino(bound.start), attr)
|
|
469
|
+
guids.append(guid)
|
|
470
|
+
|
|
471
|
+
guid = sc.doc.Objects.AddPoint(compas_rhino.conversions.point_to_rhino(bound.end), attr)
|
|
472
|
+
guids.append(guid)
|
|
473
|
+
|
|
474
|
+
if guids:
|
|
475
|
+
if self.boundsgroup:
|
|
476
|
+
self.add_to_group(self.boundsgroup, guids)
|
|
477
|
+
elif self.group:
|
|
478
|
+
self.add_to_group(self.group, guids)
|
|
479
|
+
|
|
480
|
+
self._guids += guids
|
|
481
|
+
return guids
|
|
482
|
+
|
|
483
|
+
def draw_cracks(self):
|
|
484
|
+
guids = []
|
|
485
|
+
|
|
486
|
+
for vertex in self.vertices():
|
|
487
|
+
if self.vertex_is_on_lower_bound(vertex):
|
|
488
|
+
name = self.vertex_crack_name(vertex)
|
|
489
|
+
attr = self.compile_attributes(name=name, color=self.icrackcolor)
|
|
490
|
+
elif self.vertex_is_on_upper_bound(vertex):
|
|
491
|
+
name = self.vertex_crack_name(vertex)
|
|
492
|
+
attr = self.compile_attributes(name=name, color=self.ecrackcolor)
|
|
493
|
+
else:
|
|
494
|
+
continue
|
|
495
|
+
|
|
496
|
+
point = self.diagram.vertex_point(vertex)
|
|
497
|
+
radius = self.session.settings.formdiagram.crack_radius
|
|
498
|
+
sphere = Sphere(radius, point=point)
|
|
499
|
+
guid = sc.doc.Objects.AddSphere(compas_rhino.conversions.sphere_to_rhino(sphere), attr)
|
|
500
|
+
guids.append(guid)
|
|
501
|
+
|
|
502
|
+
if guids:
|
|
503
|
+
if self.crackgroup:
|
|
504
|
+
self.add_to_group(self.crackgroup, guids)
|
|
505
|
+
elif self.group:
|
|
506
|
+
self.add_to_group(self.group, guids)
|
|
507
|
+
|
|
508
|
+
self._guids += guids
|
|
509
|
+
return guids
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from pydantic import Field
|
|
3
|
+
|
|
4
|
+
from compas_session.settings import Settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FormDiagramSettings(BaseModel):
|
|
8
|
+
show_reactions: bool = Field(default=True, title="Show Reactions")
|
|
9
|
+
show_residuals: bool = Field(default=False, title="Show Residuals")
|
|
10
|
+
show_pipes: bool = Field(default=False, title="Show Pipes")
|
|
11
|
+
show_loads: bool = Field(default=False, title="Show Loads")
|
|
12
|
+
show_bounds: bool = Field(default=False, title="Show Bounds")
|
|
13
|
+
show_cracks: bool = Field(default=False, title="Show Cracks")
|
|
14
|
+
|
|
15
|
+
scale_reactions: float = Field(default=0.01, ge=1e-6, le=1e3, title="Scale Reactions")
|
|
16
|
+
scale_residuals: float = Field(default=1.0, ge=1e-6, le=1e3, title="Scale Residuals")
|
|
17
|
+
scale_pipes: float = Field(default=0.01, ge=1e-6, le=1e3, title="Scale Pipes")
|
|
18
|
+
scale_loads: float = Field(default=1.0, ge=1e-6, le=1e3, title="Scale Loads")
|
|
19
|
+
|
|
20
|
+
tol_vectors: float = Field(default=1e-3, ge=1e-6, le=1e3, title="Tolerance Vectors")
|
|
21
|
+
tol_pipes: float = Field(default=1e-2, ge=1e-6, le=1e3, title="Tolerance Pipes")
|
|
22
|
+
|
|
23
|
+
crack_radius: float = Field(default=0.1, ge=0.01, le=100, title="Crack Radius")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EnvelopeSettings(BaseModel):
|
|
27
|
+
show_intrados: bool = Field(default=True, title="Show Intrados")
|
|
28
|
+
show_middle: bool = Field(default=False, title="Show Middle")
|
|
29
|
+
show_extrados: bool = Field(default=True, title="Show Extrados")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BlockModelSettings(BaseModel):
|
|
33
|
+
tol_contacts: float = Field(default=1e-3, ge=1e-6, le=1e3, title="Tolerance Contacts")
|
|
34
|
+
amin_contacts: float = Field(default=1e-2, ge=1e-6, le=90, title="Minimum Angle Contacts")
|
|
35
|
+
|
|
36
|
+
show_blocks: bool = Field(default=True, title="Show Blocks")
|
|
37
|
+
show_supports: bool = Field(default=True, title="Show Supports")
|
|
38
|
+
show_contacts: bool = Field(default=False, title="Show Contacts")
|
|
39
|
+
show_interactions: bool = Field(default=False, title="Show Interactions")
|
|
40
|
+
show_selfweight: bool = Field(default=False, title="Show Selfweight")
|
|
41
|
+
show_reactions: bool = Field(default=False, title="Show Reactions")
|
|
42
|
+
|
|
43
|
+
scale_selfweight: float = Field(default=1.0, ge=1e-6, le=1e3, title="Scale Selfweight")
|
|
44
|
+
scale_reactions: float = Field(default=0.01, ge=1e-6, le=1e3, title="Scale Reactions")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MasonrySettings(Settings):
|
|
48
|
+
autoupdate: bool = Field(default=True, title="Auto Update")
|
|
49
|
+
autosave: bool = Field(default=False, title="Auto Save")
|
|
50
|
+
|
|
51
|
+
formdiagram: FormDiagramSettings = FormDiagramSettings()
|
|
52
|
+
envelope: EnvelopeSettings = EnvelopeSettings()
|
|
53
|
+
blockmodel: BlockModelSettings = BlockModelSettings()
|
compas_masonry/splash.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#! python3
|
|
2
|
+
# venv: brg-csd
|
|
3
|
+
# r: compas_masonry
|
|
4
|
+
|
|
5
|
+
import pathlib
|
|
6
|
+
|
|
7
|
+
import Eto.Drawing # type: ignore
|
|
8
|
+
import Eto.Forms # type: ignore
|
|
9
|
+
import Rhino # type: ignore
|
|
10
|
+
import Rhino.UI # type: ignore
|
|
11
|
+
import System # type: ignore
|
|
12
|
+
|
|
13
|
+
pluginfile = Rhino.PlugIns.PlugIn.PathFromId(System.Guid("4384e04f-2997-429b-b3fc-53e7fe78703e"))
|
|
14
|
+
shared = pathlib.Path(str(pluginfile)).parent / "shared"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SplashForm(Eto.Forms.Dialog[bool]):
|
|
18
|
+
def __init__(self, title, url, width=800, height=450):
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
self.Title = title
|
|
22
|
+
self.Padding = Eto.Drawing.Padding(0)
|
|
23
|
+
self.Resizable = False
|
|
24
|
+
self.ClientSize = Eto.Drawing.Size(width, height)
|
|
25
|
+
self.WindowStyle = Eto.Forms.WindowStyle.NONE # type: ignore
|
|
26
|
+
|
|
27
|
+
webview = Eto.Forms.WebView()
|
|
28
|
+
webview.Size = Eto.Drawing.Size(width, height)
|
|
29
|
+
webview.Url = System.Uri(url)
|
|
30
|
+
webview.BrowserContextMenuEnabled = False
|
|
31
|
+
webview.DocumentLoading += self.action
|
|
32
|
+
|
|
33
|
+
layout = Eto.Forms.DynamicLayout()
|
|
34
|
+
layout.BeginVertical()
|
|
35
|
+
layout.AddRow(webview)
|
|
36
|
+
layout.EndVertical()
|
|
37
|
+
self.Content = layout
|
|
38
|
+
|
|
39
|
+
def action(self, sender, e):
|
|
40
|
+
if e.Uri.Scheme == "action" and e.Uri.Host == "close":
|
|
41
|
+
self.Close()
|
|
42
|
+
|
|
43
|
+
def show(self):
|
|
44
|
+
return self.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compas_masonry
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: COMPAS Masonry is a masonry assessment toolbox for Rhino based on the COMPAS framework.
|
|
5
|
+
Author-email: tom van mele <tom.v.mele@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
ETH Zurich - Block Research Group
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://blockresearchgroup.github.io/compas_masonry
|
|
29
|
+
Project-URL: Documentation, https://blockresearchgroup.github.io/compas_masonry
|
|
30
|
+
Project-URL: Repository, https://github.com/blockresearchgroup/compas_masonry
|
|
31
|
+
Project-URL: Changelog, https://github.com/blockresearchgroup/compas_masonry/blob/main/CHANGELOG.md
|
|
32
|
+
Project-URL: Issues, https://github.com/blockresearchgroup/compas_masonry/issues
|
|
33
|
+
Project-URL: Forum, https://forum.compas-framework.org/
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Topic :: Scientific/Engineering
|
|
36
|
+
Classifier: Operating System :: Unix
|
|
37
|
+
Classifier: Operating System :: POSIX
|
|
38
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
39
|
+
Classifier: Programming Language :: Python
|
|
40
|
+
Classifier: Programming Language :: Python :: 3
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: cvxpy
|
|
48
|
+
Requires-Dist: compas>=2.14.1
|
|
49
|
+
Requires-Dist: compas_cgal>=0.9.1
|
|
50
|
+
Requires-Dist: compas_dem>=0.4
|
|
51
|
+
Requires-Dist: compas_libigl>=0.7.6
|
|
52
|
+
Requires-Dist: compas_rui>=0.6
|
|
53
|
+
Requires-Dist: compas_session>=0.7
|
|
54
|
+
Requires-Dist: compas_tna>=0.7
|
|
55
|
+
Requires-Dist: compas_tno>=0.3
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: bump-my-version; extra == "dev"
|
|
58
|
+
Requires-Dist: compas_invocations2; extra == "dev"
|
|
59
|
+
Requires-Dist: invoke>=0.14; extra == "dev"
|
|
60
|
+
Requires-Dist: ruff; extra == "dev"
|
|
61
|
+
Requires-Dist: sphinx_compas2_theme; extra == "dev"
|
|
62
|
+
Requires-Dist: twine; extra == "dev"
|
|
63
|
+
Requires-Dist: wheel; extra == "dev"
|
|
64
|
+
Dynamic: license-file
|
|
65
|
+
|
|
66
|
+
# COMPAS Masonry
|
|
67
|
+
|
|
68
|
+
> [!WARNING]
|
|
69
|
+
> This plugin is under active development,
|
|
70
|
+
> and its functionality will frequently change.
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
COMPAS Masonry is a plugin for Rhino for the assessment of masonry structures,
|
|
75
|
+
and for the stability analsis of Discrete Element Models in general.
|
|
76
|
+
|
|
77
|
+
The current version of this plugin is based on COMPAS 2 and is available for Rhino 8.
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
To install COMPAS-Masonry, use Rhino's package manager Yak.
|
|
82
|
+
To open Yak, type `PackageManager` on the Rhino command line.
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
## COMPAS Packages
|
|
87
|
+
|
|
88
|
+
COMPAS-Masonry uses the following COMPAS packages:
|
|
89
|
+
|
|
90
|
+
* [compas](https://github.com/compas-dev/compas)
|
|
91
|
+
* [compas_assembly](https://github.com/blockresearchgroup/compas_assembly)
|
|
92
|
+
* [compas_model](https://github.com/blockresearchgroup/compas_model)
|
|
93
|
+
* [compas_rui](https://github.com/blockresearchgroup/compas_rui)
|
|
94
|
+
* [compas_session](https://github.com/blockresearchgroup/compas_session)
|
|
95
|
+
|
|
96
|
+
After installing RhinoVAULT with Yak, these requirements will be installed automatically if necessary.
|
|
97
|
+
The tool might be unresponsive during this process, which might take up to 1 or 2 mins.
|
|
98
|
+
The packages are installed in a separate virtual environment named `COMPAS-Masonry`.
|
|
99
|
+
|
|
100
|
+
> [!NOTE]
|
|
101
|
+
> Note that COMPAS-Masonry currently doesn't include any solvers
|
|
102
|
+
> (such as [compas_cra](https://github.com/blockresearchgroup/compas_cra)) yet.
|
|
103
|
+
> This is because they have dependencies that are still difficult to install in Rhino.
|
|
104
|
+
|
|
105
|
+
## User Interface
|
|
106
|
+
|
|
107
|
+
COMPAS-Masonry defines the following Rhino commands:
|
|
108
|
+
|
|
109
|
+
* `Masonry`
|
|
110
|
+
* `Masonry_session_undo`
|
|
111
|
+
* `Masonry_session_redo`
|
|
112
|
+
* `Masonry_session_open`
|
|
113
|
+
* `Masonry_session_save`
|
|
114
|
+
* `Masonry_scene_clear`
|
|
115
|
+
* `Masonry_scene_redraw`
|
|
116
|
+
* `Masonry_settings`
|
|
117
|
+
|
|
118
|
+
These commands can be executed using the Rhino command line (simply start typing the command name),
|
|
119
|
+
or with the corresponding buttons of the COMPAS-Masonry toolbar.
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
If the toolbar is not visible after installing COMPAS-Masonry,
|
|
124
|
+
you can load it from the "Toolbars" page.
|
|
125
|
+
To open the "Toolbars" page, type `Toolbar` on the Rhino command line...
|
|
126
|
+
|
|
127
|
+

|
|
128
|
+
|
|
129
|
+
## Documentation
|
|
130
|
+
|
|
131
|
+
For further "getting started" instructions, a tutorial, examples, and an detailed description of the individual commands and the user interface, please check out the online documentation here: [COMPAS-Masonry Gitbook](https://blockresearchgroup.gitbook.io/COMPAS-Masonry)
|
|
132
|
+
|
|
133
|
+
## Issues
|
|
134
|
+
|
|
135
|
+
Please report problems using the issue tracker of the github repo: <https://github.com/blockresearchgroup/compas-Masonry/issues>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
compas_masonry/__init__.py,sha256=6rlt9_Slk91nZSCwRLS2Fq-S-jeMbC69Mo8xhW-JG-A,66
|
|
2
|
+
compas_masonry/__main__.py,sha256=-GqoftNECZBtC8F82KWJrdSD05BcKBrrL0ElHEEUHyk,36
|
|
3
|
+
compas_masonry/session.py,sha256=qTzZTLZA2pnLPvYeLPxefwlCgp8dhn4JAiL9prF3Sbo,240
|
|
4
|
+
compas_masonry/settings.py,sha256=RRVsZ4ZgV6Sinr2tHQ5FeV5IIqK7S1hwW7aDgsFWiFs,2565
|
|
5
|
+
compas_masonry/splash.py,sha256=rQSOXMcHguoAT7eqRN_edO0ctN0i95tzYX7PgLKJWT4,1342
|
|
6
|
+
compas_masonry/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
compas_masonry/forms/settings.py,sha256=m6tgvhYnOF-Nv-GvfDlCmThC7Yf0FMsXpBp2c6mRJPM,6288
|
|
8
|
+
compas_masonry/scene/__init__.py,sha256=5mfXQp2zkLD45WN69RzDs0aHunMmTwbBQiz4cF9VvvM,429
|
|
9
|
+
compas_masonry/scene/envelopeobject.py,sha256=bGsuB8NEm4KoMDtqneE5XcmjUX9BqaBNvISVow7qA8A,3146
|
|
10
|
+
compas_masonry/scene/formobject.py,sha256=_Jf58QRI3liGv6T3fmTDEb_sp3cd1E3UrVu-B2BfgjI,18164
|
|
11
|
+
compas_masonry-0.2.1.dist-info/licenses/LICENSE,sha256=wLowriMQkZN59QuARxuHKSyxeWroT2yQwAHRItCs2yM,1071
|
|
12
|
+
compas_masonry-0.2.1.dist-info/METADATA,sha256=SrNN11LtEDWuOZe8ozd5gl7PDIqIqT7hRFzfOrWVHDU,5822
|
|
13
|
+
compas_masonry-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
compas_masonry-0.2.1.dist-info/top_level.txt,sha256=iQC5Th2Uypnt4aDzfH4p37BbV1mL-okJfjoHZKz_3do,15
|
|
15
|
+
compas_masonry-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
ETH Zurich - Block Research Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
compas_masonry
|