From f92fbe9c496d12fa38753ecac40d0c78bda193b0 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:17:13 -0800 Subject: [PATCH 01/16] missing pytest in automated test --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e3fdf3..ab39928 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,7 @@ jobs: python -m pip install --upgrade pip python -m pip install flit flit install --deps production + pip install pytest - name: Test with pytest run: | pytest . From c6f3b00c00e1234c098c41b4d00da6f1ff6629d7 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:43:29 -0800 Subject: [PATCH 02/16] removed unused func --- src/tests/test_credentials.py | 40 ----------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 src/tests/test_credentials.py diff --git a/src/tests/test_credentials.py b/src/tests/test_credentials.py deleted file mode 100644 index bbda24b..0000000 --- a/src/tests/test_credentials.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from unittest import mock -import getpass -import sys - -from ak_sap.utils.credentials import getpwd - -@pytest.fixture(autouse=True) -def mock_sys_platform(request): - """Mock sys.platform to 'win32' for Windows tests""" - if request.node.get_closest_marker("windows"): - with mock.patch.object(sys, 'platform', "win32"): - yield - else: - yield - -@pytest.fixture(autouse=True) -def mock_keyring(request): - """Mock keyring module for Windows tests""" - if request.node.get_closest_marker("windows"): - with mock.patch("credentials.keyring") as mock_keyring: - yield mock_keyring - else: - yield None - -def test_getpwd_linux(monkeypatch): - """Test getpwd function on Linux""" - item = "example" - username = "testuser" - expected_password = "testpass" - - # Mock the getpass.getpass function - monkeypatch.setattr(getpass, 'getpass', lambda prompt: expected_password) - - # Call the function - password = getpwd(item, username) - - # Assert the password - assert password == expected_password From 71efdd15eff8cf802fe6e8a679b4653a49f2da38 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:53:41 -0800 Subject: [PATCH 03/16] separated gui installation to be optional --- README.md | 15 ++++++++++++++- pyproject.toml | 5 ++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b7834e4..a6d5b6d 100644 --- a/README.md +++ b/README.md @@ -81,15 +81,28 @@ Clone repo and Install with flit git clone https://github.com/rpakishore/ak_sap.git cd ak_sap pip install flit -flit install --deps production ``` +- If you want just the base package: + + ```bash + flit install --deps production + ``` + +- Alternatively, if you also want to include the optional streamlit gui: + + ```bash + flit install --deps production --extras gui + ``` + ##### 2.2.1.3. Install from Pypi release ```bash pip install ak_sap ``` +Note: The Pypi version does not ship with the optional streamlit gui + #### 2.2.2. Development Download the git and install via flit diff --git a/pyproject.toml b/pyproject.toml index 4a2ef0c..0673776 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,10 @@ dev = [ "ipywidgets", "pandasgui", "line-profiler", - "pytest==7.4.3", + "pytest==7.4.3" +] + +gui = [ "streamlit==1.31.0", "hilti_profis==0.0.3" ] From 144a4aaa642ae7c6a90809624ea2b8016068c7b2 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:56:53 -0800 Subject: [PATCH 04/16] separated test to be optional for automated testing --- .github/workflows/test.yml | 3 +-- pyproject.toml | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab39928..cb94c70 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,8 +28,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install flit - flit install --deps production - pip install pytest + flit install --deps production --extras test - name: Test with pytest run: | pytest . diff --git a/pyproject.toml b/pyproject.toml index 0673776..31ed417 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,8 +24,7 @@ dev = [ "ipykernel", "ipywidgets", "pandasgui", - "line-profiler", - "pytest==7.4.3" + "line-profiler" ] gui = [ @@ -33,6 +32,10 @@ gui = [ "hilti_profis==0.0.3" ] +test = [ + "pytest==7.4.3" +] + [project.urls] Home = "https://github.com/rpakishore/ak_sap" From 7b186f3313dbe4d9a946e7cfbc21fc473fa9c74b Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:14:44 -0800 Subject: [PATCH 05/16] Element -> Object Changed `Element` references to `Object` references to be consistent with SAP nomenclature --- documentation/Layout.md | 8 ++++---- documentation/Usage.ipynb | 8 ++++---- pages/Export/hilti_export.py | 4 ++-- src/ak_sap/Object/__init__.py | 2 +- src/ak_sap/Object/frame.py | 4 ++-- src/ak_sap/Object/helper.py | 8 ++++---- src/ak_sap/Object/{elem.py => obj.py} | 2 +- src/ak_sap/Object/point.py | 4 ++-- src/ak_sap/wrapper.py | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) rename src/ak_sap/Object/{elem.py => obj.py} (93%) diff --git a/documentation/Layout.md b/documentation/Layout.md index aaed9cc..3a0a3cc 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -65,14 +65,14 @@ Manipulate Point Elements Usage Examples ```python -points = sap.Element.Point +points = sap.Object.Point len(points) #list number of points in model points.add_by_coord((1,2,3)) #Add point to model points.is_selected(name='1') #Check if point is selected points.selected() #Yields selected points points.all() #Lists all defined points points.rename(old_name='1', new_name='1_1') #Rename point -points.check_element_legal(name='1') #Asserts point's existance +points.check_obj_legal(name='1') #Asserts point's existance points.delete(name='1') #Delete point ``` @@ -83,13 +83,13 @@ Manipulate Frame Elements Usage Examples ```python -frames = sap.Element.Frame +frames = sap.Object.Frame len(frames) #list number of frames in model frames.is_selected(name='1') #Check if frame is selected frames.selected() #Yields selected frames frames.all() #Lists all defined frames frames.rename(old_name='1', new_name='1_1') #Rename frame -frames.check_element_legal(name='1') #Asserts frame's existance +frames.check_obj_legal(name='1') #Asserts frame's existance frames.get_section(frame_name='1') #Get the assigned Section name frames.get_points(frame_name='1') #Get points connected to frame frames.delete(name='1') #Delete frame diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index 54421cd..a22d184 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -106,14 +106,14 @@ "metadata": {}, "outputs": [], "source": [ - "points = sap.Element.Point\n", + "points = sap.Object.Point\n", "len(points) #list number of points in model\n", "points.add_by_coord((1,2,3)) #Add point to model\n", "points.is_selected(name='1') #Check if point is selected\n", "points.selected() #Yields selected points\n", "points.all() #Lists all defined points\n", "points.rename(old_name='1', new_name='1_1') #Rename point\n", - "points.check_element_legal(name='1') #Asserts point's existance\n", + "points.check_obj_legal(name='1') #Asserts point's existance\n", "points.delete(name='1') #Delete point" ] }, @@ -130,13 +130,13 @@ "metadata": {}, "outputs": [], "source": [ - "frames = sap.Element.Frame\n", + "frames = sap.Object.Frame\n", "len(frames) #list number of frames in model\n", "frames.is_selected(name='1') #Check if frame is selected\n", "frames.selected() #Yields selected frames\n", "frames.all() #Lists all defined frames\n", "frames.rename(old_name='1', new_name='1_1') #Rename frame\n", - "frames.check_element_legal(name='1') #Asserts frame's existance\n", + "frames.check_obj_legal(name='1') #Asserts frame's existance\n", "frames.get_section(frame_name='1') #Get the assigned Section name\n", "frames.get_points(frame_name='1') #Get points connected to frame\n", "frames.delete(name='1') #Delete frame\n", diff --git a/pages/Export/hilti_export.py b/pages/Export/hilti_export.py index 03c5ced..4c44890 100644 --- a/pages/Export/hilti_export.py +++ b/pages/Export/hilti_export.py @@ -22,7 +22,7 @@ def main(): def _hilti_extract_values(): try: - st.session_state['_hilti_values_extracted'] = sap.Element.Point.selected().__next__() + st.session_state['_hilti_values_extracted'] = sap.Object.Point.selected().__next__() except StopIteration: st.session_state['_hilti_values_extracted'] = None st.warning('Make sure the node is selected.') @@ -35,7 +35,7 @@ def load_hilti_class(basefile): if st.session_state['_hilti_values_extracted']: try: - selected_section = sap.Element.Frame.get_section(frame_name=sap.Element.Frame.selected().__next__()) + selected_section = sap.Object.Frame.get_section(frame_name=sap.Object.Frame.selected().__next__()) except StopIteration: selected_section = None diff --git a/src/ak_sap/Object/__init__.py b/src/ak_sap/Object/__init__.py index 6289242..662cfb5 100644 --- a/src/ak_sap/Object/__init__.py +++ b/src/ak_sap/Object/__init__.py @@ -1 +1 @@ -from .elem import Element \ No newline at end of file +from .obj import Object \ No newline at end of file diff --git a/src/ak_sap/Object/frame.py b/src/ak_sap/Object/frame.py index f0ba9b2..7c2b0dc 100644 --- a/src/ak_sap/Object/frame.py +++ b/src/ak_sap/Object/frame.py @@ -1,8 +1,8 @@ from ak_sap.utils import log from ak_sap.utils.decorators import smooth_sap_do -from .helper import MasterElem +from .helper import MasterObj -class Frame(MasterElem): +class Frame(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.FrameObj) diff --git a/src/ak_sap/Object/helper.py b/src/ak_sap/Object/helper.py index ed7a286..4c18030 100644 --- a/src/ak_sap/Object/helper.py +++ b/src/ak_sap/Object/helper.py @@ -3,7 +3,7 @@ from ak_sap.utils.logger import log from ak_sap.utils.decorators import smooth_sap_do -class MasterElem: +class MasterObj: def __init__(self, mySapObject, ElemObj) -> None: self.mySapObject = mySapObject self.SapModel = self.mySapObject.SapModel @@ -35,7 +35,7 @@ def selected(self) -> Generator[str, Any, None]: yield elem def is_selected(self, name:str) -> bool: - self.check_element_legal(name) + self.check_obj_legal(name) return self.ElemObj.GetSelected(name)[0] @smooth_sap_do @@ -47,10 +47,10 @@ def all(self) -> tuple[str]: @smooth_sap_do def rename(self, old_name: str, new_name: str): """Change the name of the element""" - self.check_element_legal(name=old_name) + self.check_obj_legal(name=old_name) return self.ElemObj.ChangeName(old_name, new_name) - def check_element_legal(self, name: str): + def check_obj_legal(self, name: str): """Confirms specified element exists in the model""" assert name in self.all(), f'`{name}` not found in the current list of elements: {self.all()}' diff --git a/src/ak_sap/Object/elem.py b/src/ak_sap/Object/obj.py similarity index 93% rename from src/ak_sap/Object/elem.py rename to src/ak_sap/Object/obj.py index c1ae5c0..6db0121 100644 --- a/src/ak_sap/Object/elem.py +++ b/src/ak_sap/Object/obj.py @@ -1,7 +1,7 @@ from .point import Point from .frame import Frame -class Element: +class Object: def __init__(self, mySapObject) -> None: self.Point = Point(mySapObject=mySapObject) self.Frame = Frame(mySapObject=mySapObject) \ No newline at end of file diff --git a/src/ak_sap/Object/point.py b/src/ak_sap/Object/point.py index 79c47a1..c313d19 100644 --- a/src/ak_sap/Object/point.py +++ b/src/ak_sap/Object/point.py @@ -1,8 +1,8 @@ from ak_sap.utils import log from ak_sap.utils.decorators import smooth_sap_do -from .helper import MasterElem +from .helper import MasterObj -class Point(MasterElem): +class Point(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.PointObj) diff --git a/src/ak_sap/wrapper.py b/src/ak_sap/wrapper.py index fff770a..2a0764d 100644 --- a/src/ak_sap/wrapper.py +++ b/src/ak_sap/wrapper.py @@ -7,7 +7,7 @@ from ak_sap.Loads import Load from ak_sap.Material.material import Material from ak_sap.Model import Model -from ak_sap.Object import Element +from ak_sap.Object import Object from ak_sap.Results import Results from ak_sap.utils.logger import log @@ -20,7 +20,7 @@ def __init__(self, attach_to_exist: bool = True, program_path: str|Path|None = N #Attach submodules and functions self.Model = Model(mySapObject=self.mySapObject) - self.Element = Element(mySapObject=self.mySapObject) + self.Object = Object(mySapObject=self.mySapObject) self.Table = Table(mySapObject=self.mySapObject, Model=self.Model) self.Load = Load(mySapObject=self.mySapObject) self.Results = Results(mySapObject=self.mySapObject) From fba0b65097130dd8cf914d6f05ad123413cdf800 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:52:40 -0800 Subject: [PATCH 06/16] test --- documentation/Usage.ipynb | 1 + src/ak_sap/Object/frame.py | 11 ++++-- src/tests/utils/test_decorators.py | 54 ++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/tests/utils/test_decorators.py diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index a22d184..cab0f3b 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -141,6 +141,7 @@ "frames.get_points(frame_name='1') #Get points connected to frame\n", "frames.delete(name='1') #Delete frame\n", "\n", + "# Get frame properties\n", "frames.Prop.rename(old_name=\"FSEC1\", new_name=\"MySection\") #Rename frame property\n", "frames.Prop.total() #Total # of defined frame properties" ] diff --git a/src/ak_sap/Object/frame.py b/src/ak_sap/Object/frame.py index 7c2b0dc..7e9636b 100644 --- a/src/ak_sap/Object/frame.py +++ b/src/ak_sap/Object/frame.py @@ -1,3 +1,5 @@ +from typing import Literal + from ak_sap.utils import log from ak_sap.utils.decorators import smooth_sap_do from .helper import MasterObj @@ -6,19 +8,24 @@ class Frame(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.FrameObj) + self.EditFrame = mySapObject.SapModel.EditFrame self.Prop = Prop(mySapObject=mySapObject) @smooth_sap_do def get_section(self, frame_name: str) -> str: - self.check_element_legal(name=frame_name) + self.check_obj_legal(name=frame_name) _ret = self.ElemObj.GetSection(frame_name) return (_ret[0], _ret[-1]) # type: ignore @smooth_sap_do def get_points(self, frame_name: str) -> tuple[str]: """retrieves the names of the point objects at each end of a specified frame object.""" - #self.check_element_legal(name=frame_name) + #self.check_obj_legal(name=frame_name) return self.ElemObj.GetPoints(frame_name) + + @smooth_sap_do + def divide_by_distance(self, frame_name: str, dist: float, Iend: bool=True, num_divisions: int=1): + return self.EditFrame.DivideAtDistance(frame_name, dist, Iend) class Prop: def __init__(self, mySapObject) -> None: diff --git a/src/tests/utils/test_decorators.py b/src/tests/utils/test_decorators.py new file mode 100644 index 0000000..2af8d2b --- /dev/null +++ b/src/tests/utils/test_decorators.py @@ -0,0 +1,54 @@ +from ak_sap.utils.decorators import smooth_sap_do + + +def test_smooth_sap_do(): + + @smooth_sap_do + def return_failed_list(): + return [1,2,3,1] + assert return_failed_list() is None + + @smooth_sap_do + def return_successful_list(): + return [1,2,3,0] + assert return_successful_list() == [1,2,3] + + @smooth_sap_do + def return_successful_tuple(): + return [(1,2,3),0] + assert return_successful_tuple() == (1,2,3) + + @smooth_sap_do + def return_failed_tuple(): + return [(1,2,3),1] + assert return_failed_tuple() is None + + @smooth_sap_do + def return_successful_str(): + return ['alpha',0] + assert return_successful_str() == 'alpha' + + @smooth_sap_do + def return_failed_str(): + return ['alpha',1] + assert return_failed_str() is None + + @smooth_sap_do + def return_successful_float(): + return [3.14,0] + assert return_successful_float() == 3.14 + + @smooth_sap_do + def return_failed_float(): + return [3.14,1] + assert return_failed_float() is None + + @smooth_sap_do + def return_success(): + return 0 + assert return_success() == 0 + + @smooth_sap_do + def return_fail(): + return 1 + assert return_fail() is None \ No newline at end of file From e9b6dca648471cfe567b7d33dcba76c54b711505 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:13:04 -0800 Subject: [PATCH 07/16] frame edit options added --- documentation/Layout.md | 15 ++++++-- documentation/Usage.ipynb | 12 +++++-- src/ak_sap/Object/frame.py | 70 ++++++++++++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/documentation/Layout.md b/documentation/Layout.md index 3a0a3cc..6fb4715 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -90,10 +90,19 @@ frames.selected() #Yields selected frames frames.all() #Lists all defined frames frames.rename(old_name='1', new_name='1_1') #Rename frame frames.check_obj_legal(name='1') #Asserts frame's existance -frames.get_section(frame_name='1') #Get the assigned Section name -frames.get_points(frame_name='1') #Get points connected to frame -frames.delete(name='1') #Delete frame +frames.get_section(name='1') #Get the assigned Section name +frames.get_points(name='1') #Get points connected to frame +#Manipulation +frames.delete(name='1') #Delete frame +frames.divide_by_distance(name='1', + dist=0.5,Iend=True) #Divide frame by distance +frames.divide_by_intersection(name='2') #Divide at selected intersections +frames.divide_by_ratio(name='3',ratio=0.3)#Divide at selected ratio +frames.join('2','3') #Join Colinear frames +frames.change_points(name='1', point1='1', point2='3') #Change connected points of frame + +# Get frame properties frames.Prop.rename(old_name="FSEC1", new_name="MySection") #Rename frame property frames.Prop.total() #Total # of defined frame properties ``` diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index cab0f3b..642b0e9 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -137,9 +137,17 @@ "frames.all() #Lists all defined frames\n", "frames.rename(old_name='1', new_name='1_1') #Rename frame\n", "frames.check_obj_legal(name='1') #Asserts frame's existance\n", - "frames.get_section(frame_name='1') #Get the assigned Section name\n", - "frames.get_points(frame_name='1') #Get points connected to frame\n", + "frames.get_section(name='1') #Get the assigned Section name\n", + "frames.get_points(name='1') #Get points connected to frame\n", + "\n", + "#Manipulation\n", "frames.delete(name='1') #Delete frame\n", + "frames.divide_by_distance(name='1',\n", + " dist=0.5,Iend=True) #Divide frame by distance\n", + "frames.divide_by_intersection(name='2') #Divide at selected intersections\n", + "frames.divide_by_ratio(name='3',ratio=0.3)#Divide at selected ratio\n", + "frames.join('2','3') #Join Colinear frames\n", + "frames.change_points(name='1', point1='1', point2='3') #Change connected points of frame\n", "\n", "# Get frame properties\n", "frames.Prop.rename(old_name=\"FSEC1\", new_name=\"MySection\") #Rename frame property\n", diff --git a/src/ak_sap/Object/frame.py b/src/ak_sap/Object/frame.py index 7e9636b..91dc45b 100644 --- a/src/ak_sap/Object/frame.py +++ b/src/ak_sap/Object/frame.py @@ -1,5 +1,3 @@ -from typing import Literal - from ak_sap.utils import log from ak_sap.utils.decorators import smooth_sap_do from .helper import MasterObj @@ -12,21 +10,73 @@ def __init__(self, mySapObject) -> None: self.Prop = Prop(mySapObject=mySapObject) @smooth_sap_do - def get_section(self, frame_name: str) -> str: - self.check_obj_legal(name=frame_name) - _ret = self.ElemObj.GetSection(frame_name) + def get_section(self, name: str) -> str: + self.check_obj_legal(name=name) + _ret = self.ElemObj.GetSection(name) return (_ret[0], _ret[-1]) # type: ignore @smooth_sap_do - def get_points(self, frame_name: str) -> tuple[str]: + def get_points(self, name: str) -> tuple[str]: """retrieves the names of the point objects at each end of a specified frame object.""" - #self.check_obj_legal(name=frame_name) - return self.ElemObj.GetPoints(frame_name) + #self.check_obj_legal(name=name) + return self.ElemObj.GetPoints(name) + + @smooth_sap_do + def divide_by_distance(self, name: str, dist: float, Iend: bool=True) -> tuple[str]: + """divides straight frame objects into two objects at a location defined by the Dist and IEnd items. + Curved frame objects are not divided. + """ + return self.EditFrame.DivideAtDistance(name, dist, Iend) + + @smooth_sap_do + def divide_by_intersection(self, name: str) -> tuple[str]: + """divides straight frame objects at intersections with selected point objects, line objects, area edges and solid edges. + Curved frame objects are not divided. + + Args: + name (str): Frame Name + + Returns: + tuple[str]: array that includes the names of the new frame objects. + """ + return self.EditFrame.DivideAtIntersections(name)[1:] @smooth_sap_do - def divide_by_distance(self, frame_name: str, dist: float, Iend: bool=True, num_divisions: int=1): - return self.EditFrame.DivideAtDistance(frame_name, dist, Iend) + def divide_by_ratio(self,name: str, ratio: float, num_frames: int=1) -> tuple[str]: + """divides straight frame objects based on a specified Last/First length ratio. + Curved frame objects are not divided. + + Args: + name (str): name of an existing straight frame object. + ratio (float): Last/First length ratio for the new frame objects. + num_frames (int, optional): frame object is divided into this number of new objects. Defaults to 1. + Returns: + tuple[str]: array that includes the names of the new frame objects. + """ + return self.EditFrame.DivideByRatio(name, num_frames, ratio) + + @smooth_sap_do + def join(self, frame1: str, frame2: str) -> bool: + """joins two straight frame objects that have a common end point and are colinear. + + Args: + frame1 (str): name of an existing frame object to be joined. The new, joined frame object keeps this name. + frame2 (_type_): name of an existing frame object to be joined. + """ + return self.EditFrame.Join(frame1, frame2) + + @smooth_sap_do + def change_points(self, name: str, point1: str, point2: str) -> bool: + """modifies the connectivity of a frame object. + + Args: + name (str): name of an existing frame object + point1 (str): name of the point object at the I-End of the frame object. + point2 (str): name of the point object at the J-End of the frame object. + """ + return self.EditFrame.ChangeConnectivity(name, point1, point2) + class Prop: def __init__(self, mySapObject) -> None: self.SapModel=mySapObject.SapModel From c9c68a7eaaca5b4b4a45a7286eb14441921e998f Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:05:20 -0800 Subject: [PATCH 08/16] EditPoint added --- src/ak_sap/Object/point.py | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/ak_sap/Object/point.py b/src/ak_sap/Object/point.py index c313d19..22659c8 100644 --- a/src/ak_sap/Object/point.py +++ b/src/ak_sap/Object/point.py @@ -1,3 +1,5 @@ +from typing import Literal + from ak_sap.utils import log from ak_sap.utils.decorators import smooth_sap_do from .helper import MasterObj @@ -5,6 +7,8 @@ class Point(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.PointObj) + self.EditPoint = mySapObject.SapModel.EditPoint + self.PointObj = mySapObject.SapModel.PointObj @smooth_sap_do def add_by_coord(self, point: tuple[float, float, float], name: str='', coord_sys: str = 'Global') -> str: @@ -15,3 +19,58 @@ def add_by_coord(self, point: tuple[float, float, float], name: str='', coord_sy coord_sys (str, optional): Name of coordinate system. Defaults to 'Global'. """ return self.ElemObj.AddCartesian(*point, '', name, coord_sys) + + @smooth_sap_do + def align(self, axis: Literal['X', 'Y', 'Z'], ordinate: float) -> tuple: + """aligns selected point objects. + + Args: + axis (Literal['X', 'Y', 'Z']): Align points to this ordinate in present coordinate system + ordinate (float): The X, Y or Z ordinate that applies + + Returns: + tuple: (number of point objects that are in a new location after the alignment is complete., + array of the name of each point object that is in a new location after the alignment is complete.) + """ + _axis = ['X','Y','Z'].index(axis.upper().strip()) + 1 + return self.EditPoint.Align(_axis, ordinate) + + @smooth_sap_do + def select(self, name: str) -> bool: + return self.PointObj.SetSelected(name, True) + + @smooth_sap_do + def deselect(self, name: str) -> bool: + return self.PointObj.SetSelected(name, False) + + @smooth_sap_do + def deselect_all(self) -> bool: + return self.PointObj.ClearSelection() + + @smooth_sap_do + def merge(self, tolerance: float) -> tuple: + """merges selected point objects that are within a specified distance of one another. + + Args: + tolerance (float): Point objects within this distance of one another are merged into one point object. + + Returns: + tuple: (number of the selected point objects that still exist after the merge is complete., + array of the name of each selected point object that still exists after the merge is complete.) + """ + return self.EditPoint.Merge(tolerance) + + @smooth_sap_do + def change_coordinate(self, name: str, x: float, y: float, z: float) -> bool: + """changes the coordinates of a specified point object. + + Args: + name (str): name of an existing point object. + x (float): new x coordinate. + y (float): new y coordinate. + z (float): new z coordinate. + + Returns: + bool: Success + """ + return self.EditPoint.ChangeCoordinates_1(name, x, y, z) \ No newline at end of file From 6f1397945c02fe27998daca6614117873c7a2ae0 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:15:13 -0800 Subject: [PATCH 09/16] refactor to hide repeated `SapModel` from user --- src/ak_sap/Object/frame.py | 18 +++++++++--------- src/ak_sap/Object/helper.py | 20 ++++++++++---------- src/ak_sap/Object/obj.py | 17 +++++++++++++++-- src/ak_sap/Object/point.py | 16 ++++++++-------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/ak_sap/Object/frame.py b/src/ak_sap/Object/frame.py index 91dc45b..f9cdee3 100644 --- a/src/ak_sap/Object/frame.py +++ b/src/ak_sap/Object/frame.py @@ -6,7 +6,7 @@ class Frame(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.FrameObj) - self.EditFrame = mySapObject.SapModel.EditFrame + self.__EditFrame = mySapObject.SapModel.EditFrame self.Prop = Prop(mySapObject=mySapObject) @smooth_sap_do @@ -26,7 +26,7 @@ def divide_by_distance(self, name: str, dist: float, Iend: bool=True) -> tuple[s """divides straight frame objects into two objects at a location defined by the Dist and IEnd items. Curved frame objects are not divided. """ - return self.EditFrame.DivideAtDistance(name, dist, Iend) + return self.__EditFrame.DivideAtDistance(name, dist, Iend) @smooth_sap_do def divide_by_intersection(self, name: str) -> tuple[str]: @@ -39,7 +39,7 @@ def divide_by_intersection(self, name: str) -> tuple[str]: Returns: tuple[str]: array that includes the names of the new frame objects. """ - return self.EditFrame.DivideAtIntersections(name)[1:] + return self.__EditFrame.DivideAtIntersections(name)[1:] @smooth_sap_do def divide_by_ratio(self,name: str, ratio: float, num_frames: int=1) -> tuple[str]: @@ -54,7 +54,7 @@ def divide_by_ratio(self,name: str, ratio: float, num_frames: int=1) -> tuple[st Returns: tuple[str]: array that includes the names of the new frame objects. """ - return self.EditFrame.DivideByRatio(name, num_frames, ratio) + return self.__EditFrame.DivideByRatio(name, num_frames, ratio) @smooth_sap_do def join(self, frame1: str, frame2: str) -> bool: @@ -64,7 +64,7 @@ def join(self, frame1: str, frame2: str) -> bool: frame1 (str): name of an existing frame object to be joined. The new, joined frame object keeps this name. frame2 (_type_): name of an existing frame object to be joined. """ - return self.EditFrame.Join(frame1, frame2) + return self.__EditFrame.Join(frame1, frame2) @smooth_sap_do def change_points(self, name: str, point1: str, point2: str) -> bool: @@ -75,11 +75,11 @@ def change_points(self, name: str, point1: str, point2: str) -> bool: point1 (str): name of the point object at the I-End of the frame object. point2 (str): name of the point object at the J-End of the frame object. """ - return self.EditFrame.ChangeConnectivity(name, point1, point2) + return self.__EditFrame.ChangeConnectivity(name, point1, point2) class Prop: def __init__(self, mySapObject) -> None: - self.SapModel=mySapObject.SapModel + self.__mySapObject=mySapObject.SapModel def __len__(self) -> int: return self.total() @@ -87,9 +87,9 @@ def __len__(self) -> int: @smooth_sap_do def rename(self, old_name: str, new_name: str): """changes the name of an existing frame section property.""" - return self.SapModel.PropFrame.ChangeName(old_name, new_name) + return self.__mySapObject.PropFrame.ChangeName(old_name, new_name) @smooth_sap_do def total(self) -> int: """returns the total number of defined frame section properties in the model""" - return self.SapModel.PropFrame.Count() \ No newline at end of file + return self.__mySapObject.PropFrame.Count() \ No newline at end of file diff --git a/src/ak_sap/Object/helper.py b/src/ak_sap/Object/helper.py index 4c18030..24c5fec 100644 --- a/src/ak_sap/Object/helper.py +++ b/src/ak_sap/Object/helper.py @@ -5,9 +5,9 @@ class MasterObj: def __init__(self, mySapObject, ElemObj) -> None: - self.mySapObject = mySapObject - self.SapModel = self.mySapObject.SapModel - self.ElemObj = ElemObj + self.__mySapObject = mySapObject + self.__SapModel = mySapObject.SapModel + self.__ElemObj = ElemObj print(f'`{self.__class__.__name__}` instance initialized.') def __str__(self) -> str: @@ -18,14 +18,14 @@ def __repr__(self) -> str: def __del__(self) -> None: try: - self.mySapObject = None - self.SapModel = None + self.__mySapObject = None + self.__SapModel = None except Exception as e: log.warning(msg=f'Exception faced when deleting {self.__class__.__name__}\n{e}') def __len__(self) -> int: """returns the total number of point elements in the analysis model.""" - return self.ElemObj.Count() + return self.__ElemObj.Count() def selected(self) -> Generator[str, Any, None]: """Returns the names of selected element objects""" @@ -36,19 +36,19 @@ def selected(self) -> Generator[str, Any, None]: def is_selected(self, name:str) -> bool: self.check_obj_legal(name) - return self.ElemObj.GetSelected(name)[0] + return self.__ElemObj.GetSelected(name)[0] @smooth_sap_do def all(self) -> tuple[str]: """Returns namelist of all element objects""" - _, *elem_list = self.ElemObj.GetNameList() + _, *elem_list = self.__ElemObj.GetNameList() return elem_list # type: ignore @smooth_sap_do def rename(self, old_name: str, new_name: str): """Change the name of the element""" self.check_obj_legal(name=old_name) - return self.ElemObj.ChangeName(old_name, new_name) + return self.__ElemObj.ChangeName(old_name, new_name) def check_obj_legal(self, name: str): """Confirms specified element exists in the model""" @@ -57,4 +57,4 @@ def check_obj_legal(self, name: str): @smooth_sap_do def delete(self, name: str): """Delete element from model""" - return self.ElemObj.Delete(name) \ No newline at end of file + return self.__ElemObj.Delete(name) \ No newline at end of file diff --git a/src/ak_sap/Object/obj.py b/src/ak_sap/Object/obj.py index 6db0121..a7e4722 100644 --- a/src/ak_sap/Object/obj.py +++ b/src/ak_sap/Object/obj.py @@ -1,7 +1,20 @@ from .point import Point from .frame import Frame +from ak_sap.utils.decorators import smooth_sap_do class Object: - def __init__(self, mySapObject) -> None: + def __init__(self, mySapObject) -> None: + self.__mySapObject = mySapObject self.Point = Point(mySapObject=mySapObject) - self.Frame = Frame(mySapObject=mySapObject) \ No newline at end of file + self.Frame = Frame(mySapObject=mySapObject) + + @smooth_sap_do + def move_selected(self, dx: float, dy: float, dz: float) -> bool: + """moves selected point, frame, cable, tendon, area, solid and link objects. + + Args: + dx (float): x offsets + dy (float): y offsets + dz (float): z offsets + """ + self.__mySapObject.SapModel.EditGeneral.Move(dx, dy, dz) \ No newline at end of file diff --git a/src/ak_sap/Object/point.py b/src/ak_sap/Object/point.py index 22659c8..7224176 100644 --- a/src/ak_sap/Object/point.py +++ b/src/ak_sap/Object/point.py @@ -7,8 +7,8 @@ class Point(MasterObj): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject, ElemObj=mySapObject.SapModel.PointObj) - self.EditPoint = mySapObject.SapModel.EditPoint - self.PointObj = mySapObject.SapModel.PointObj + self.__EditPoint = mySapObject.SapModel.EditPoint + self.__PointObj = mySapObject.SapModel.PointObj @smooth_sap_do def add_by_coord(self, point: tuple[float, float, float], name: str='', coord_sys: str = 'Global') -> str: @@ -33,19 +33,19 @@ def align(self, axis: Literal['X', 'Y', 'Z'], ordinate: float) -> tuple: array of the name of each point object that is in a new location after the alignment is complete.) """ _axis = ['X','Y','Z'].index(axis.upper().strip()) + 1 - return self.EditPoint.Align(_axis, ordinate) + return self.__EditPoint.Align(_axis, ordinate) @smooth_sap_do def select(self, name: str) -> bool: - return self.PointObj.SetSelected(name, True) + return self.__PointObj.SetSelected(name, True) @smooth_sap_do def deselect(self, name: str) -> bool: - return self.PointObj.SetSelected(name, False) + return self.__PointObj.SetSelected(name, False) @smooth_sap_do def deselect_all(self) -> bool: - return self.PointObj.ClearSelection() + return self.__PointObj.ClearSelection() @smooth_sap_do def merge(self, tolerance: float) -> tuple: @@ -58,7 +58,7 @@ def merge(self, tolerance: float) -> tuple: tuple: (number of the selected point objects that still exist after the merge is complete., array of the name of each selected point object that still exists after the merge is complete.) """ - return self.EditPoint.Merge(tolerance) + return self.__EditPoint.Merge(tolerance) @smooth_sap_do def change_coordinate(self, name: str, x: float, y: float, z: float) -> bool: @@ -73,4 +73,4 @@ def change_coordinate(self, name: str, x: float, y: float, z: float) -> bool: Returns: bool: Success """ - return self.EditPoint.ChangeCoordinates_1(name, x, y, z) \ No newline at end of file + return self.__EditPoint.ChangeCoordinates_1(name, x, y, z) \ No newline at end of file From a0f541ccf1742474c1665722c4fbbb2ecde56820 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:21:50 -0800 Subject: [PATCH 10/16] refactored to remove useless attributes --- src/ak_sap/Loads/LoadCases.py | 16 ++++++++-------- src/ak_sap/Loads/LoadCombos.py | 4 ++-- src/ak_sap/Loads/LoadPatterns.py | 20 ++++++++++---------- src/ak_sap/Material/material.py | 20 ++++++++++---------- src/ak_sap/Results/Setup.py | 16 ++++++++-------- src/ak_sap/Results/main.py | 4 ++-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ak_sap/Loads/LoadCases.py b/src/ak_sap/Loads/LoadCases.py index 3ff01ea..6f41f4e 100644 --- a/src/ak_sap/Loads/LoadCases.py +++ b/src/ak_sap/Loads/LoadCases.py @@ -7,7 +7,7 @@ class LoadCase(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.LoadCases = mySapObject.SapModel.LoadCases + self.__LoadCases = mySapObject.SapModel.LoadCases def __str__(self) -> str: return 'Instance of `LoadCase`. Holds collection of functions' @@ -21,32 +21,32 @@ def total(self, casetype: LoadCaseType|None = None): """returns the total number of defined load cases in the model. If desired, counts can be returned for all load cases of a specified type in the model.""" if casetype is None: - _ret = self.LoadCases.Count() + _ret = self.__LoadCases.Count() else: _value = typing.get_args(LoadCaseType).index(casetype) + 1 - _ret = self.LoadCases.Count(_value) + _ret = self.__LoadCases.Count(_value) return _ret @smooth_sap_do def rename(self, old_name: str, new_name: str): """changes the name of an existing load case.""" assert old_name in self.list_all(), f'"{old_name}" is not in list of defined load cases {self.list_all()}' - return self.LoadCases.ChangeName(old_name, new_name) + return self.__LoadCases.ChangeName(old_name, new_name) @smooth_sap_do def list_all(self) -> tuple[str]: """retrieves the names of all defined load cases of the specified type.""" - _, loadcases, _ret = self.LoadCases.GetNameList_1() + _, loadcases, _ret = self.__LoadCases.GetNameList_1() return *loadcases, _ret @smooth_sap_do def delete(self, name: str): assert name in self.list_all(), f'"{name}" is not in list of defined load cases {self.list_all()}' - return self.LoadCases.Delete(name) + return self.__LoadCases.Delete(name) @smooth_sap_do def case_info(self, name: str) -> dict: - _ret = self.LoadCases.GetTypeOAPI_2(name) + _ret = self.__LoadCases.GetTypeOAPI_2(name) _value = { 'CaseType': typing.get_args(LoadCaseType)[_ret[0]-1], 'DesignType': typing.get_args(LoadPatternType)[_ret[2]-1], @@ -66,4 +66,4 @@ def case_info(self, name: str) -> dict: def set_type(self, name: str, casetype: LoadCaseType): assert name in self.list_all(), f'"{name}" is not in list of defined load cases {self.list_all()}' _value = typing.get_args(LoadCaseType).index(casetype) + 1 - return self.LoadCases.SetDesignType(name, 1, _value) \ No newline at end of file + return self.__LoadCases.SetDesignType(name, 1, _value) \ No newline at end of file diff --git a/src/ak_sap/Loads/LoadCombos.py b/src/ak_sap/Loads/LoadCombos.py index a3065e0..f44c602 100644 --- a/src/ak_sap/Loads/LoadCombos.py +++ b/src/ak_sap/Loads/LoadCombos.py @@ -4,7 +4,7 @@ class LoadCombo(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.RespCombo = mySapObject.SapModel.RespCombo + self.__RespCombo = mySapObject.SapModel.RespCombo def __str__(self) -> str: return 'Instance of `LoadCombo`' @@ -12,4 +12,4 @@ def __str__(self) -> str: @smooth_sap_do def list_all(self) -> tuple[str]: """retrieves the names of all defined response combinations""" - return self.RespCombo.GetNameList()[1:] \ No newline at end of file + return self.__RespCombo.GetNameList()[1:] \ No newline at end of file diff --git a/src/ak_sap/Loads/LoadPatterns.py b/src/ak_sap/Loads/LoadPatterns.py index d84a5e6..55c4923 100644 --- a/src/ak_sap/Loads/LoadPatterns.py +++ b/src/ak_sap/Loads/LoadPatterns.py @@ -7,7 +7,7 @@ class LoadPattern(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.LoadPatterns = mySapObject.SapModel.LoadPatterns + self.__LoadPatterns = mySapObject.SapModel.LoadPatterns def __str__(self) -> str: return 'Instance of `LoadPattern`. Holds collection of functions' @@ -17,48 +17,48 @@ def __repr__(self) -> str: def __len__(self) -> int: """returns the number of defined load patterns.""" - return self.LoadPatterns.Count() + return self.__LoadPatterns.Count() @smooth_sap_do def add(self, name: str, pattern_type: LoadPatternType, selfwt_multiplier: float=0, add_case: bool=False): """adds a new load pattern.""" chosen_pattern = typing.get_args(LoadPatternType).index(pattern_type) + 1 - return self.LoadPatterns.Add(name, chosen_pattern, + return self.__LoadPatterns.Add(name, chosen_pattern, selfwt_multiplier, add_case) @smooth_sap_do def rename(self, old_name: str, new_name: str): """applies a new name to a load pattern.""" - return self.LoadPatterns.ChangeName(old_name, new_name) + return self.__LoadPatterns.ChangeName(old_name, new_name) @smooth_sap_do def delete(self, name: str): """deletes the specified load pattern.""" - return self.LoadPatterns.Delete(name) + return self.__LoadPatterns.Delete(name) @smooth_sap_do def set_loadtype(self, name: str, pattern_type: LoadPatternType): """assigns a load type to a load pattern.""" chosen_pattern = typing.get_args(LoadPatternType).index(pattern_type) + 1 - return self.LoadPatterns.SetLoadType(name, chosen_pattern) + return self.__LoadPatterns.SetLoadType(name, chosen_pattern) @smooth_sap_do def get_loadtype(self, name: str) -> str: """assigns a load type to a load pattern.""" - value = self.LoadPatterns.GetLoadType(name) + value = self.__LoadPatterns.GetLoadType(name) chosen_pattern = typing.get_args(LoadPatternType)[value[0] - 1] return chosen_pattern, 0 # type: ignore @smooth_sap_do def set_selfwt_multiplier(self, name: str, selfwt_multiplier: float): - return self.LoadPatterns.SetSelfWtMultiplier(name, selfwt_multiplier) + return self.__LoadPatterns.SetSelfWtMultiplier(name, selfwt_multiplier) @smooth_sap_do def get_selfwt_multiplier(self, name: str) -> float: - return self.LoadPatterns.GetSelfWtMultiplier(name) + return self.__LoadPatterns.GetSelfWtMultiplier(name) @smooth_sap_do def list_all(self) -> tuple[str]: - values = self.LoadPatterns.GetNameList() + values = self.__LoadPatterns.GetNameList() return values[1:] \ No newline at end of file diff --git a/src/ak_sap/Material/material.py b/src/ak_sap/Material/material.py index 30dd41a..d6f61bf 100644 --- a/src/ak_sap/Material/material.py +++ b/src/ak_sap/Material/material.py @@ -9,7 +9,7 @@ class Material(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.PropMaterial = mySapObject.SapModel.PropMaterial + self.__PropMaterial = mySapObject.SapModel.PropMaterial #Submodules self.Rebar = Rebar(mySapObject=mySapObject) @@ -20,24 +20,24 @@ def __len__(self) -> int: @smooth_sap_do def rename(self, old: str, new: str): """changes the name of an existing material property.""" - return self.PropMaterial.ChangeName(old, new) + return self.__PropMaterial.ChangeName(old, new) @smooth_sap_do def total(self) -> int: """returns the total number of defined material properties in the model.""" - return self.PropMaterial.Count() + return self.__PropMaterial.Count() @smooth_sap_do def delete(self, name: str): - return self.PropMaterial.Delete(name) + return self.__PropMaterial.Delete(name) @smooth_sap_do def list_all(self) -> list[str]: """retrieves the names of all defined material properties of the specified type""" - return self.PropMaterial.GetNameList()[1:] + return self.__PropMaterial.GetNameList()[1:] def __get_type(self, name: str) -> dict: - *_result, _ret = self.PropMaterial.GetTypeOAPI(name) + *_result, _ret = self.__PropMaterial.GetTypeOAPI(name) assert _ret == 0 return { 'MaterialType': typing.get_args(MaterialTypesStr)[_result[0] - 1], @@ -46,7 +46,7 @@ def __get_type(self, name: str) -> dict: def get_props(self, name: str) -> dict: """retrieves some basic material property data.""" - *_result, _ret = self.PropMaterial.GetMaterial(name) + *_result, _ret = self.__PropMaterial.GetMaterial(name) assert _ret == 0 return { 'MaterialType': typing.get_args(MaterialTypesStr)[_result[0] - 1], @@ -60,14 +60,14 @@ def get_props(self, name: str) -> dict: def add(self, name: str, material_type: MaterialTypesStr): """initializes a material property. If this function is called for an existing material property, all items for the material are reset to their default value.""" - return self.PropMaterial.SetMaterial(name, typing.get_args(MaterialTypesStr).index(material_type) + 1) + return self.__PropMaterial.SetMaterial(name, typing.get_args(MaterialTypesStr).index(material_type) + 1) @smooth_sap_do def set_isotropic(self, name: str, E: float, poisson: float, thermal_coeff: float): """sets the material directional symmetry type to isotropic, and assigns the isotropic mechanical properties.""" - return self.PropMaterial.SetMPIsotropic(name, E, poisson, thermal_coeff) + return self.__PropMaterial.SetMPIsotropic(name, E, poisson, thermal_coeff) @smooth_sap_do def set_density(self, name: str, mass_per_vol: float): """assigns weight per unit volume or mass per unit volume to a material property.""" - return self.PropMaterial.SetWeightAndMass(name, 2, mass_per_vol) + return self.__PropMaterial.SetWeightAndMass(name, 2, mass_per_vol) diff --git a/src/ak_sap/Results/Setup.py b/src/ak_sap/Results/Setup.py index e847ee5..ba18985 100644 --- a/src/ak_sap/Results/Setup.py +++ b/src/ak_sap/Results/Setup.py @@ -5,34 +5,34 @@ class ResultsSetup(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.Setup = mySapObject.SapModel.Results.Setup + self.__Setup = mySapObject.SapModel.Results.Setup @smooth_sap_do def clear_casecombo(self): """deselects all load cases and response combinations for output.""" - return self.Setup.DeselectAllCasesAndCombosForOutput() + return self.__Setup.DeselectAllCasesAndCombosForOutput() def is_selected_case(self, casename: str): """checks if an load case is selected for output.""" - return self.Setup.GetCaseSelectedForOutput(casename) + return self.__Setup.GetCaseSelectedForOutput(casename) def select_case(self, casename: str): """sets an load case selected for output flag.""" - return self.Setup.SetCaseSelectedForOutput(casename) + return self.__Setup.SetCaseSelectedForOutput(casename) def is_selected_combo(self, comboname: str): """checks if an load combo is selected for output.""" - return self.Setup.GetComboSelectedForOutput(comboname) + return self.__Setup.GetComboSelectedForOutput(comboname) def select_combo(self, comboname: str): """sets an load combo selected for output flag.""" - return self.Setup.SetComboSelectedForOutput(comboname) + return self.__Setup.SetComboSelectedForOutput(comboname) def base_rxn_loc_get(self): """retrieves the global coordinates of the location at which the base reactions are reported.""" - *coord, _ret = self.Setup.GetOptionBaseReactLoc() + *coord, _ret = self.__Setup.GetOptionBaseReactLoc() return {'x':coord[0], 'y':coord[1], 'z':coord[2]}, _ret def set_rxn_loc_get(self, x: float, y: float, z: float): """sets the global coordinates of the location at which the base reactions are reported.""" - return self.Setup.SetOptionBaseReactLoc(x, y, z) + return self.__Setup.SetOptionBaseReactLoc(x, y, z) diff --git a/src/ak_sap/Results/main.py b/src/ak_sap/Results/main.py index 204625b..5ff257a 100644 --- a/src/ak_sap/Results/main.py +++ b/src/ak_sap/Results/main.py @@ -7,14 +7,14 @@ class Results(MasterClass): def __init__(self, mySapObject) -> None: super().__init__(mySapObject=mySapObject) - self.Results = mySapObject.SapModel.Results + self.__Results = mySapObject.SapModel.Results self.Setup = ResultsSetup(mySapObject=mySapObject) @smooth_sap_do def joint_reactions(self, jointname:str) -> dict: loads: list[dict] = [] - _, Obj, Elm, LoadCase,StepType, StepNum, F1, F2, F3, M1, M2, M3, ret = self.Results.JointReact(jointname, 1) + _, Obj, Elm, LoadCase,StepType, StepNum, F1, F2, F3, M1, M2, M3, ret = self.__Results.JointReact(jointname, 1) for _Obj, _Elm, _LoadCase, _StepType, _StepNum, _F1, _F2, _F3, _M1, _M2, _M3 in zip(Obj, Elm, LoadCase, StepType, StepNum, F1, F2, F3, M1, M2, M3): loads.append( From 3d8e3527aa851bcc9baf76ada0e1a5273d1c44a9 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:46:59 -0800 Subject: [PATCH 11/16] General Obj Edit Functions --- documentation/Layout.md | 21 +++++++++++++++++++++ documentation/Usage.ipynb | 28 +++++++++++++++++++++++++++- src/ak_sap/Object/obj.py | 29 ++++++++++++++++++++++++++++- src/ak_sap/Object/point.py | 2 +- src/ak_sap/__init__.py | 1 + src/ak_sap/misc/__init__.py | 1 + src/ak_sap/misc/coordinates.py | 10 ++++++++++ 7 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/ak_sap/misc/__init__.py create mode 100644 src/ak_sap/misc/coordinates.py diff --git a/documentation/Layout.md b/documentation/Layout.md index 6fb4715..50e3688 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -58,6 +58,18 @@ sap.Model.set_logs('Add this comment') #Adds user comments/logs Collection of methods and attributes that apply changes to elements in the model +```python +object = sap.Object +object.move_selected(dx=0.5, dy=0, dz=1.0) #Move selected object +object.copy(dx=0.5, dy=0, dz=0, num=10)#copy selected object + +#Mirror and create object +from ak_sap import Coord +pt1 = Coord(x=10, y=20, z=0) +p2 = Coord(x=10, y=30, z=0) +object.mirror(plane='Z', coord1=pt1, coord2=pt2) #Mirror replicate selected obj. +``` + ### 2.2.1. Point Manipulate Point Elements @@ -74,6 +86,15 @@ points.all() #Lists all defined points points.rename(old_name='1', new_name='1_1') #Rename point points.check_obj_legal(name='1') #Asserts point's existance points.delete(name='1') #Delete point + +#Manipilate +points.deselect_all() #Deselect all points +points.select(name='1') #Select a single point +points.align(axis='Z', ordinate = 100) #Align selected points +points.deselect(name='1') #Deselect a single point + +points.merge(tolerance=2) #Merge points that are within tol +points.change_coord(name='1', x=0, y=0, z=0)#Change point coordinate ``` ### 2.2.2. Frames diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index 642b0e9..f38c7e7 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -93,6 +93,23 @@ "### Element" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "object = sap.Object\n", + "object.move_selected(dx=0.5, dy=0, dz=1.0) #Move selected object\n", + "object.copy(dx=0.5, dy=0, dz=0, num=10)#copy selected object\n", + "\n", + "#Mirror and create object\n", + "from ak_sap import Coord\n", + "pt1 = Coord(x=10, y=20, z=0)\n", + "p2 = Coord(x=10, y=30, z=0)\n", + "object.mirror(plane='Z', coord1=pt1, coord2=pt2) #Mirror replicate selected obj." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -114,7 +131,16 @@ "points.all() #Lists all defined points\n", "points.rename(old_name='1', new_name='1_1') #Rename point\n", "points.check_obj_legal(name='1') #Asserts point's existance\n", - "points.delete(name='1') #Delete point" + "points.delete(name='1') #Delete point\n", + "\n", + "#Manipilate\n", + "points.deselect_all() #Deselect all points\n", + "points.select(name='1') #Select a single point\n", + "points.align(axis='Z', ordinate = 100) #Align selected points\n", + "points.deselect(name='1') #Deselect a single point\n", + "\n", + "points.merge(tolerance=2) #Merge points that are within tol\n", + "points.change_coord(name='1', x=0, y=0, z=0)#Change point coordinate" ] }, { diff --git a/src/ak_sap/Object/obj.py b/src/ak_sap/Object/obj.py index a7e4722..a61b9c1 100644 --- a/src/ak_sap/Object/obj.py +++ b/src/ak_sap/Object/obj.py @@ -1,10 +1,14 @@ +from typing import Literal + from .point import Point from .frame import Frame from ak_sap.utils.decorators import smooth_sap_do +from ak_sap.misc import Coord class Object: def __init__(self, mySapObject) -> None: self.__mySapObject = mySapObject + self.__EditGeneral = mySapObject.SapModel.EditGeneral self.Point = Point(mySapObject=mySapObject) self.Frame = Frame(mySapObject=mySapObject) @@ -17,4 +21,27 @@ def move_selected(self, dx: float, dy: float, dz: float) -> bool: dy (float): y offsets dz (float): z offsets """ - self.__mySapObject.SapModel.EditGeneral.Move(dx, dy, dz) \ No newline at end of file + self.__EditGeneral.Move(dx, dy, dz) + + @smooth_sap_do + def copy(self, dx: float, dy: float, dz: float, num: int) -> tuple: + """linearly replicates selected objects. + + Args: + dx (float): x offset + dy (float): y offset + dz (float): z offset + num (int): number of times the selected objects are to be replicated. + """ + return self.__EditGeneral.ReplicateLinear(dx, dy, dz, num) + + @smooth_sap_do + def mirror(self, plane: Literal['X', 'Y', 'Z'], coord1: Coord, coord2: Coord): + """mirror replicates selected objects + + Args: + plane (Literal['X', 'Y', 'Z']): parallel to this plane + coord1 (Coord), coord2 (Coord): define the intersection of the mirror plane with the perp. plane + """ + axis = ['Z', 'X', 'Y'].index(plane.upper().strip()) + 1 + return self.__EditGeneral.ReplicateMirror(axis, *coord1.as_tuple(), *coord2.as_tuple()) \ No newline at end of file diff --git a/src/ak_sap/Object/point.py b/src/ak_sap/Object/point.py index 7224176..1ed1ca6 100644 --- a/src/ak_sap/Object/point.py +++ b/src/ak_sap/Object/point.py @@ -61,7 +61,7 @@ def merge(self, tolerance: float) -> tuple: return self.__EditPoint.Merge(tolerance) @smooth_sap_do - def change_coordinate(self, name: str, x: float, y: float, z: float) -> bool: + def change_coord(self, name: str, x: float, y: float, z: float) -> bool: """changes the coordinates of a specified point object. Args: diff --git a/src/ak_sap/__init__.py b/src/ak_sap/__init__.py index 29d5206..83f19bd 100644 --- a/src/ak_sap/__init__.py +++ b/src/ak_sap/__init__.py @@ -3,6 +3,7 @@ from ak_sap.utils.logger import log from ak_sap.wrapper import Sap2000Wrapper +from ak_sap.misc import Coord #log = Log() diff --git a/src/ak_sap/misc/__init__.py b/src/ak_sap/misc/__init__.py new file mode 100644 index 0000000..063333f --- /dev/null +++ b/src/ak_sap/misc/__init__.py @@ -0,0 +1 @@ +from .coordinates import Coord \ No newline at end of file diff --git a/src/ak_sap/misc/coordinates.py b/src/ak_sap/misc/coordinates.py new file mode 100644 index 0000000..62c275b --- /dev/null +++ b/src/ak_sap/misc/coordinates.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + +@dataclass +class Coord: + x: float + y: float + z: float + + def as_tuple(self) -> tuple[float]: + return (self.x, self.y, self.z) \ No newline at end of file From 11a43995c9eb1714256fd37487dda97e42019b61 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:58:59 -0800 Subject: [PATCH 12/16] `sap.Analyze` Options Added --- documentation/Layout.md | 136 ++++++++++++++++++++++----------- documentation/Usage.ipynb | 67 +++++++++++++--- src/ak_sap/Analyze/__init__.py | 1 + src/ak_sap/Analyze/main.py | 123 +++++++++++++++++++++++++++++ src/ak_sap/Results/main.py | 10 +++ src/ak_sap/wrapper.py | 2 + src/tests/Analyze/test_main.py | 61 +++++++++++++++ 7 files changed, 345 insertions(+), 55 deletions(-) create mode 100644 src/ak_sap/Analyze/__init__.py create mode 100644 src/ak_sap/Analyze/main.py create mode 100644 src/tests/Analyze/test_main.py diff --git a/documentation/Layout.md b/documentation/Layout.md index 50e3688..4ec74c7 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -1,38 +1,66 @@

Layout

-- [1. Roadmap/Checklist](#1-roadmapchecklist) -- [2. Sub-Modules](#2-sub-modules) - - [2.1. Model](#21-model) - - [2.2. Element](#22-element) - - [2.2.1. Point](#221-point) - - [2.2.2. Frames](#222-frames) - - [2.3. Table](#23-table) - - [2.4. Loads](#24-loads) - - [2.4.1. Load Patterns](#241-load-patterns) - - [2.4.2. Load Cases](#242-load-cases) - - [2.4.3. Modal](#243-modal) - - [2.4.3.1. Eigen](#2431-eigen) - - [2.4.3.2. Ritz](#2432-ritz) - - [2.5. Results](#25-results) - - [2.6. Material](#26-material) +- [Layout Map](#layout-map) +- [Initialize](#initialize) +- [Parent Level](#parent-level) +- [Sub-Modules](#sub-modules) + - [Model](#model) + - [Element](#element) + - [Point](#point) + - [Frame](#frame) + - [Database](#database) + - [Loads](#loads) + - [Load Patterns](#load-patterns) + - [Load Cases](#load-cases) + - [Modal](#modal) + - [Eigen](#eigen) + - [Ritz](#ritz) + - [Analyze](#analyze) + - [Results](#results) + - [Material](#material) - [Rebar](#rebar) -# 1. Roadmap/Checklist +# Layout Map -![MindMap](assets/mindmap.png) +![MindMap](assets/mindmap.svg) -# 2. Sub-Modules +# Initialize -![MindMap](assets/mindmap.svg) +Usage Examples: + +```python +from ak_sap import debug, Sap2000Wrapper +debug(status=False) + +#Initialize +sap = Sap2000Wrapper(attach_to_exist=True) #Attach to existing opened model +sap = Sap2000Wrapper(attach_to_exist=False) #Create new blank model from latest SAP2000 +## Create blank model from a custom version of SAP2000 +sap = Sap2000Wrapper(attach_to_exist=False, program_path=r'Path\to\SAP2000.exe') +``` + +# Parent Level + +Usage Examples: + +```python +sap.hide() #Hide the SAP2000 window +sap.unhide() #Unhides SAP2000 window +sap.ishidden #Check if window is hidden +sap.version #Returns SAP2000 version number +sap.api_version #Returns Sap0API version number + +sap.save(r'\Path\to\save\file.sdb') +``` -## 2.1. Model +# Sub-Modules +## Model Collection of methods and attributes that control changes to the model as a whole -Usage Examples +Usage Examples: ```python -#Model sap.Model.units #Returns current model units sap.Model.units_database #Returns Internal Database units sap.Model.set_units(value='N_m_C') #Changes the present units of model @@ -54,10 +82,12 @@ sap.Model.logs #Retrieve user comments and logs sap.Model.set_logs('Add this comment') #Adds user comments/logs ``` -## 2.2. Element +## Element Collection of methods and attributes that apply changes to elements in the model +Usage Examples: + ```python object = sap.Object object.move_selected(dx=0.5, dy=0, dz=1.0) #Move selected object @@ -70,11 +100,11 @@ p2 = Coord(x=10, y=30, z=0) object.mirror(plane='Z', coord1=pt1, coord2=pt2) #Mirror replicate selected obj. ``` -### 2.2.1. Point +### Point Manipulate Point Elements -Usage Examples +Usage Examples: ```python points = sap.Object.Point @@ -97,11 +127,11 @@ points.merge(tolerance=2) #Merge points that are within tol points.change_coord(name='1', x=0, y=0, z=0)#Change point coordinate ``` -### 2.2.2. Frames +### Frame Manipulate Frame Elements -Usage Examples +Usage Examples: ```python frames = sap.Object.Frame @@ -128,11 +158,11 @@ frames.Prop.rename(old_name="FSEC1", new_name="MySection") #Rename frame proper frames.Prop.total() #Total # of defined frame properties ``` -## 2.3. Table +## Database Control the database values -Usage Examples +Usage Examples: ```python tables = sap.Table @@ -147,13 +177,12 @@ df.iloc[0,0] = 'New Value' tables.update(TableKey='Material Properties 01 - General', data=df, apply=True) ``` -## 2.4. Loads +## Loads Control the definition and assignments of loads. +### Load Patterns -### 2.4.1. Load Patterns - -Usage Examples +Usage Examples: ```python pattern = sap.Load.Pattern @@ -173,9 +202,9 @@ pattern.add(name='Custom Live', pattern_type='LIVE', selfwt_multiplier=1.15, add_case=True) ``` -### 2.4.2. Load Cases +### Load Cases -Usage Examples +Usage Examples: ```python cases = sap.Load.Case @@ -187,13 +216,12 @@ cases.case_info(name='DEAD') #Get the Case type information cases.set_type(name='DEAD', casetype='LINEAR_STATIC') #Change the case type of existing load case ``` -### 2.4.3. Modal +### Modal `sap.Load.Modal` +#### Eigen -#### 2.4.3.1. Eigen - -Usage Examples +Usage Examples: ```python eigen = sap.Load.Modal.Eigen @@ -218,9 +246,9 @@ eigen.set_number_modes(case_name='LCASE1', max=10, min=5) #set number of modes eigen.get_number_modes(case_name='LCASE1') #get number of modes ``` -#### 2.4.3.2. Ritz +#### Ritz -Usage Examples +Usage Examples: ```python ritz = sap.Load.Modal.Ritz @@ -235,11 +263,24 @@ ritz.set_number_modes(case_name='LCASE1', max=10, min=5) #set number of modes ritz.get_number_modes(case_name='LCASE1') #get number of modes ``` -## 2.5. Results +## Analyze + +Usage Examples: + +```python +analyze = sap.Analyze +analyze.create_model() #Create analysis model +analyze.run() #Runs the analysis +analyze.case_status() #retrieves the status for all load cases. +analyze.get_run_status() #retrieves the run flags for all cases +analyze.set_run_flag(case='MODAL', status=True) # Set case to run +``` + +## Results Manipulate Results from SAP2000 -Usage Examples +Usage Examples: ```python results = sap.Results @@ -254,11 +295,14 @@ setup.set_rxn_loc_get(x=0.5, y=0.5, z=5) #sets coordinates of the locn at whi setup.base_rxn_loc_get() #retrieves coordinates of the locn at which the base reactions are reported. results.joint_reactions(jointname='1') #Get Joint reactions as dict + +results.delete('MODAL') #Delete results of `MODAL` case +results.delete('All') #Delete results of all cases ``` -## 2.6. Material +## Material -Usage Examples +Usage Examples: ```python material = sap.Material @@ -274,7 +318,7 @@ material.set_density(name='Steel', mass_per_vol=0.00029) #set density ### Rebar -Usage Examples +Usage Examples: ```python rebar = sap.Material.Rebar diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index f38c7e7..fb4723c 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -56,7 +56,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Model" + "### Model\n", + "\n", + "Collection of methods and attributes that control changes to the model as a whole" ] }, { @@ -90,7 +92,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Element" + "### Element\n", + "\n", + "Collection of methods and attributes that apply changes to elements in the model" ] }, { @@ -114,7 +118,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Point" + "#### Point\n", + "\n", + "Manipulate Point Elements" ] }, { @@ -147,7 +153,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Frame" + "#### Frame\n", + "\n", + "Manipulate Frame Elements" ] }, { @@ -184,7 +192,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Database" + "### Database\n", + "\n", + "Control the database values" ] }, { @@ -209,7 +219,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Loads" + "### Loads\n", + "\n", + "Control the definition and assignments of loads." ] }, { @@ -268,7 +280,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Modal" + "#### Modal\n", + "\n", + "`sap.Load.Modal`" ] }, { @@ -335,7 +349,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Results" + "### Analyze" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "analyze = sap.Analyze\n", + "analyze.create_model() #Create analysis model\n", + "analyze.run() #Runs the analysis\n", + "analyze.case_status() #retrieves the status for all load cases.\n", + "analyze.get_run_status() #retrieves the run flags for all cases\n", + "analyze.set_run_flag(case='MODAL', status=True) # Set case to run\n", + "analyze.get_solver() #Get solver info\n", + "\n", + "#Set solver options\n", + "analyze.set_solver(\n", + " SolverType='Standard',\n", + " SolverProcessType='Auto',\n", + " NumberParallelRuns=0,\n", + " StiffCase=''\n", + ") " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Results\n", + "\n", + "Manipulate Results from SAP2000" ] }, { @@ -355,7 +401,10 @@ "setup.set_rxn_loc_get(x=0.5, y=0.5, z=5) #sets coordinates of the locn at which the base reactions are reported. \n", "setup.base_rxn_loc_get() #retrieves coordinates of the locn at which the base reactions are reported.\n", "\n", - "results.joint_reactions(jointname='1') #Get Joint reactions as dict" + "results.joint_reactions(jointname='1') #Get Joint reactions as dict\n", + "\n", + "results.delete('MODAL') #Delete results of `MODAL` case\n", + "results.delete('All') #Delete results of all cases" ] }, { diff --git a/src/ak_sap/Analyze/__init__.py b/src/ak_sap/Analyze/__init__.py new file mode 100644 index 0000000..844c843 --- /dev/null +++ b/src/ak_sap/Analyze/__init__.py @@ -0,0 +1 @@ +from .main import Analyze \ No newline at end of file diff --git a/src/ak_sap/Analyze/main.py b/src/ak_sap/Analyze/main.py new file mode 100644 index 0000000..f63179e --- /dev/null +++ b/src/ak_sap/Analyze/main.py @@ -0,0 +1,123 @@ +from typing import Literal + +from ak_sap.utils import MasterClass, log +from ak_sap.utils.decorators import smooth_sap_do + +class Analyze(MasterClass): + def __init__(self, mySapObject) -> None: + super().__init__(mySapObject=mySapObject) + self.__Analyze = mySapObject.SapModel.Analyze + + @smooth_sap_do + def create_model(self) -> bool: + """creates the analysis model. + If the analysis model is already created and current, nothing is done. + """ + return self.__Analyze.CreateAnalyzeModel() + + @smooth_sap_do + def run(self): + """runs the analysis. + The analysis model is automatically created as part of this function. + """ + return self.__Analyze.RunAnalysis() + + @smooth_sap_do + def case_status(self) -> dict: + """retrieves the status for all load cases. + + Returns: + list[dict]: Cases and their current status. + """ + return case_status(ret=self.__Analyze.GetCaseStatus()), 0 + + @smooth_sap_do + def get_run_flag(self) -> dict: + """retrieves the run flags for all analysis cases. + + Returns: + dict: Loadcases and their run flags + """ + return get_run_flag(ret=self.__Analyze.GetRunCaseFlag()), 0 + + @smooth_sap_do + def set_run_flag(self, case: str, status: bool): + """sets the run flag for load cases + + Args: + case (str): name of an existing load case + status (bool): If this item is True, the specified load case is to be run + """ + return self.__Analyze.SetRunCaseFlag(case, status) + + @smooth_sap_do + def get_solver(self) -> dict: + """retrieves the model solver options + + Returns: + dict: Solver Info + """ + return get_solver(ret = self.__Analyze.GetSolverOption_3()), 0 + + @smooth_sap_do + def set_solver(self, + SolverType: Literal['Standard', 'Advanced', 'Multi-threaded'], + SolverProcessType: Literal['Auto', 'GUI', 'Separate'], + NumberParallelRuns: Literal[0,1,2,3,4,5,6,7,8], + StiffCase: str = '' + ) -> bool: + """sets the model solver options + + Args: + SolverType (Literal['Standard', 'Advanced', 'Multi-threaded']): indicats the solver type. + SolverProcessType (Literal['Auto', 'GUI', 'Separate']): indicats the process the analysis is run + NumberParallelRuns (Literal[0,1,2,3,4,5,6,7,8]): Number of parallel runs + StiffCase (str, optional): name of the load case used when outputting the mass and stiffness matrices to text files. Defaults to ''. + """ + return self.__Analyze.SetSolverOption_3( + ['Standard', 'Advanced', 'Multi-threaded'].index(SolverType), + ['Auto', 'GUI', 'Separate'].index(SolverProcessType), + NumberParallelRuns, + 0,0, + StiffCase + ) + +def get_solver(ret: list) -> dict: + assert ret[-1] == 0 + return { + 'SolverType': ['Standard', 'Advanced', 'Multi-threaded'][ret[0]], + 'SolverProcessType': ['Auto', 'GUI', 'Separate'][ret[1]], + 'NumberParallelRuns': abs(ret[2]), + 'StiffCase': ret[5] + } + +def get_run_flag(ret: list)->dict: + assert ret[-1] == 0 + status: dict = {} + + if ret[0] == 1: + #If cases is not a iterable + return {ret[1]: ret[2]} + + #Else if cases are iterables + for _case, _status in zip(list(ret[1]), list(ret[2])): + status[_case] = _status + return status + +def case_status(ret: list)->dict: + assert ret[-1] == 0 + status: dict = {} + _status_exp = [ + 'Not run', + 'Could not start', + 'Not finished', + 'Finished' + ] + if ret[0] == 1: + #If cases is not a iterable + return {ret[1]: _status_exp[ret[2]-1]} + + #Else if cases are iterables + for _case, _status in zip(list(ret[1]), list(ret[2])): + status[_case] = _status_exp[_status-1] + return status \ No newline at end of file diff --git a/src/ak_sap/Results/main.py b/src/ak_sap/Results/main.py index 5ff257a..0ff0341 100644 --- a/src/ak_sap/Results/main.py +++ b/src/ak_sap/Results/main.py @@ -1,3 +1,4 @@ +from typing import Literal from ak_sap.utils import log, MasterClass from ak_sap.utils.decorators import smooth_sap_do @@ -10,6 +11,15 @@ def __init__(self, mySapObject) -> None: self.__Results = mySapObject.SapModel.Results self.Setup = ResultsSetup(mySapObject=mySapObject) + + @smooth_sap_do + def delete(self, casename: str|Literal['All']) -> bool: + """deletes results for load cases. + + Args: + casename (str | Literal['All']): name of an existing load case that is to have its results deleted. + """ + return self.mySapObject.SapModel.Analyze.DeleteResykts(casename, casename.casefold() == 'all') @smooth_sap_do def joint_reactions(self, jointname:str) -> dict: diff --git a/src/ak_sap/wrapper.py b/src/ak_sap/wrapper.py index 2a0764d..b1f2a30 100644 --- a/src/ak_sap/wrapper.py +++ b/src/ak_sap/wrapper.py @@ -3,6 +3,7 @@ from pathlib import Path import sys +from ak_sap.Analyze import Analyze from ak_sap.Database import Table from ak_sap.Loads import Load from ak_sap.Material.material import Material @@ -19,6 +20,7 @@ def __init__(self, attach_to_exist: bool = True, program_path: str|Path|None = N self.SapModel = self.mySapObject.SapModel #Attach submodules and functions + self.Analyze = Analyze(mySapObject=self.mySapObject) self.Model = Model(mySapObject=self.mySapObject) self.Object = Object(mySapObject=self.mySapObject) self.Table = Table(mySapObject=self.mySapObject, Model=self.Model) diff --git a/src/tests/Analyze/test_main.py b/src/tests/Analyze/test_main.py new file mode 100644 index 0000000..0901c9b --- /dev/null +++ b/src/tests/Analyze/test_main.py @@ -0,0 +1,61 @@ +import pytest + +from ak_sap.Analyze.main import case_status, get_run_flag, get_solver + +def test_case_status(): + ret = (4, ('Dead', 'Live', 'MODAL', 'Snow'), (1, 2, 3, 4), 0) + expected = { + 'Dead': 'Not run', + 'Live': 'Could not start', + 'MODAL': 'Not finished', + 'Snow': 'Finished', + } + assert case_status(ret) == expected + + ret = [1, ('Dead'), (1), 0] + expected = { + 'Dead': 'Not run', + } + assert case_status(ret) == expected + + ret = [1, ('Dead'), (1), 1] + with pytest.raises(AssertionError): + case_status(ret) + +def test_get_run_flag(): + ret = [3, ('DEAD', 'Live', 'EQ'), (True, True, False), 0] + expected= { + 'DEAD': True, + 'Live': True, + 'EQ': False, + } + assert get_run_flag(ret) == expected + + ret = [1, ('DEAD'), (True), 0] + expected= { + 'DEAD': True, + } + assert get_run_flag(ret) == expected + + ret = [1, ('DEAD'), (True), 1] + with pytest.raises(AssertionError): + get_run_flag(ret) + +def test_get_solver(): + ret = [1, 0, 1, -102399, -4, '', 0] + expected = {'SolverType': 'Advanced', + 'SolverProcessType': 'Auto', + 'NumberParallelRuns': 1, + 'StiffCase': ''} + assert get_solver(ret) == expected + + ret = [0, 1, -2, -102399, -4, 'DEAD', 0] + expected = {'SolverType': 'Standard', + 'SolverProcessType': 'GUI', + 'NumberParallelRuns': 2, + 'StiffCase': 'DEAD'} + assert get_solver(ret) == expected + + ret = [0, 1, -2, -102399, -4, 'DEAD', 1] + with pytest.raises(AssertionError): + get_solver(ret) \ No newline at end of file From d3357c8ad3d778417032d5161e64e2d0bdc0ecf5 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:43:51 -0800 Subject: [PATCH 13/16] Added Joint Results Extactions + Tests --- documentation/Layout.md | 14 +- documentation/Usage.ipynb | 5 +- src/ak_sap/Results/main.py | 132 +++++++++++++++--- .../{test_main.py => test_Analyze_main.py} | 0 src/tests/Results/test_Results_main.py | 91 ++++++++++++ 5 files changed, 218 insertions(+), 24 deletions(-) rename src/tests/Analyze/{test_main.py => test_Analyze_main.py} (100%) create mode 100644 src/tests/Results/test_Results_main.py diff --git a/documentation/Layout.md b/documentation/Layout.md index 4ec74c7..53df78c 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -274,6 +274,15 @@ analyze.run() #Runs the analysis analyze.case_status() #retrieves the status for all load cases. analyze.get_run_status() #retrieves the run flags for all cases analyze.set_run_flag(case='MODAL', status=True) # Set case to run +analyze.get_solver() #Get solver info + +#Set solver options +analyze.set_solver( + SolverType='Standard', + SolverProcessType='Auto', + NumberParallelRuns=0, + StiffCase='' +) ``` ## Results @@ -294,7 +303,10 @@ setup.is_selected_combo(comboname='COMB1') #checks if an load combo is selected setup.set_rxn_loc_get(x=0.5, y=0.5, z=5) #sets coordinates of the locn at which the base reactions are reported. setup.base_rxn_loc_get() #retrieves coordinates of the locn at which the base reactions are reported. -results.joint_reactions(jointname='1') #Get Joint reactions as dict +results.joint_reactions(jointname='1') #Get Joint reactions as list of dict +results.joint_displacements(jointname='1') #Get Joint displacements as list of dict +results.joint_accelerations(jointname='1') #Get joint accelerations +results.joint_velocities(jointname='1') #Get joint velocities results.delete('MODAL') #Delete results of `MODAL` case results.delete('All') #Delete results of all cases diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index fb4723c..0ac3932 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -401,7 +401,10 @@ "setup.set_rxn_loc_get(x=0.5, y=0.5, z=5) #sets coordinates of the locn at which the base reactions are reported. \n", "setup.base_rxn_loc_get() #retrieves coordinates of the locn at which the base reactions are reported.\n", "\n", - "results.joint_reactions(jointname='1') #Get Joint reactions as dict\n", + "results.joint_reactions(jointname='1') #Get Joint reactions as list of dict\n", + "results.joint_displacements(jointname='1') #Get Joint displacements as list of dict\n", + "results.joint_accelerations(jointname='1') #Get joint accelerations\n", + "results.joint_velocities(jointname='1') #Get joint velocities\n", "\n", "results.delete('MODAL') #Delete results of `MODAL` case\n", "results.delete('All') #Delete results of all cases" diff --git a/src/ak_sap/Results/main.py b/src/ak_sap/Results/main.py index 0ff0341..52cc1d6 100644 --- a/src/ak_sap/Results/main.py +++ b/src/ak_sap/Results/main.py @@ -2,7 +2,6 @@ from ak_sap.utils import log, MasterClass from ak_sap.utils.decorators import smooth_sap_do - from .Setup import ResultsSetup class Results(MasterClass): @@ -22,25 +21,114 @@ def delete(self, casename: str|Literal['All']) -> bool: return self.mySapObject.SapModel.Analyze.DeleteResykts(casename, casename.casefold() == 'all') @smooth_sap_do - def joint_reactions(self, jointname:str) -> dict: - loads: list[dict] = [] - _, Obj, Elm, LoadCase,StepType, StepNum, F1, F2, F3, M1, M2, M3, ret = self.__Results.JointReact(jointname, 1) - - for _Obj, _Elm, _LoadCase, _StepType, _StepNum, _F1, _F2, _F3, _M1, _M2, _M3 in zip(Obj, Elm, LoadCase, StepType, StepNum, F1, F2, F3, M1, M2, M3): - loads.append( - { - 'ObjectName': _Obj, - 'ElementName': _Elm, - 'LoadCase': _LoadCase, - 'StepType': _StepType, - 'StepNum': _StepNum, - 'F1': _F1, - 'F2': _F2, - 'F3': _F3, - 'M1': _M1, - 'M2': _M2, - 'M3': _M3 - } - ) + def joint_reactions(self, jointname:str) -> list[dict]: + """reports the joint reactions for the specified point elements + + Args: + jointname (str): name of an existing point object + """ + return joint_reactions_parse(ret=self.__Results.JointReact(jointname, 1)), 0 + + + @smooth_sap_do + def joint_displacements(self, jointname:str) -> list[dict]: + """reports the joint displacements for the specified point elements + + Args: + jointname (str): name of an existing point object + """ + return joint_displacements_parse(ret=self.__Results.JointDispl(jointname, 1)), 0 + + @smooth_sap_do + def joint_accelerations(self, jointname:str) -> list[dict]: + """reports the joint accelerations for the specified point elements + + Args: + jointname (str): name of an existing point object + """ + return joint_displacements_parse(ret=self.__Results.JointAcc(jointname, 1)), 0 + + @smooth_sap_do + def joint_velocities(self, jointname:str, format: Literal['Pandas', 'List']= 'List') -> list[dict]: + """reports the joint velocities for the specified point elements + + Args: + jointname (str): name of an existing point object + """ + return joint_displacements_parse(ret=self.__Results.JointVel(jointname, 1)), 0 - return *loads, ret # type: ignore \ No newline at end of file +def joint_displacements_parse(ret: list) -> list[dict]: + assert ret[-1] == 0 + displacements: list[dict] = [] + + if ret[0] == 1: + return [ + { + 'ObjectName': ret[1], #the point object name associated with each result, if any. + 'ElementName': ret[2], #the point element name associated with each result. + 'LoadCase': ret[3], #name of the analysis case or load combination associated with each result. + 'StepType': ret[4], #step type, if any, for each result. + 'StepNum': ret[5], #includes the step number, if any, for each result. + 'U1': ret[6], #displacement in the point element local 1 axis + 'U2': ret[7], #displacement in the point element local 2 axis + 'U3': ret[8], #displacement in the point element local 3 axis + 'R1': ret[9], #rotation about the point element local 1 axis + 'R2': ret[10], #rotation about the point element local 2 axis + 'R3': ret[11], #rotation about the point element local 3 axis + } + ] + for idx in range(ret[0]): + displacements.append( + { + 'ObjectName': ret[1][idx], #the point object name associated with each result, if any. + 'ElementName': ret[2][idx], #the point element name associated with each result. + 'LoadCase': ret[3][idx], #name of the analysis case or load combination associated with each result. + 'StepType': ret[4][idx], #step type, if any, for each result. + 'StepNum': ret[5][idx], #includes the step number, if any, for each result. + 'U1': ret[6][idx], #displacement in the point element local 1 axis + 'U2': ret[7][idx], #displacement in the point element local 2 axis + 'U3': ret[8][idx], #displacement in the point element local 3 axis + 'R1': ret[9][idx], #rotation about the point element local 1 axis + 'R2': ret[10][idx], #rotation about the point element local 2 axis + 'R3': ret[11][idx], #rotation about the point element local 3 axis + } + ) + return displacements + +def joint_reactions_parse(ret: list) -> list[dict]: + assert ret[-1] == 0 + loads: list[dict] = [] + + if ret[0] == 1: + return [ + { + 'ObjectName': ret[1], #the point object name associated with each result, if any. + 'ElementName': ret[2], #the point element name associated with each result. + 'LoadCase': ret[3], #name of the analysis case or load combination associated with each result. + 'StepType': ret[4], #step type, if any, for each result. + 'StepNum': ret[5], #includes the step number, if any, for each result. + 'F1': ret[6], #reaction forces in the point element local 1 axis + 'F2': ret[7], #reaction forces in the point element local 2 axis + 'F3': ret[8], #reaction forces in the point element local 3 axis + 'M1': ret[9], #reaction moments about the point element local 1 axis + 'M2': ret[10], #reaction moments about the point element local 2 axis + 'M3': ret[11], #reaction moments about the point element local 3 axis + } + ] + for idx in range(ret[0]): + loads.append( + { + 'ObjectName': ret[1][idx], #the point object name associated with each result, if any. + 'ElementName': ret[2][idx], #the point element name associated with each result. + 'LoadCase': ret[3][idx], #name of the analysis case or load combination associated with each result. + 'StepType': ret[4][idx], #step type, if any, for each result. + 'StepNum': ret[5][idx], #includes the step number, if any, for each result. + 'F1': ret[6][idx], #reaction forces in the point element local 1 axis + 'F2': ret[7][idx], #reaction forces in the point element local 2 axis + 'F3': ret[8][idx], #reaction forces in the point element local 3 axis + 'M1': ret[9][idx], #reaction moments about the point element local 1 axis + 'M2': ret[10][idx], ##reaction moments about the point element local 2 axis + 'M3': ret[11][idx], ##reaction moments about the point element local 3 axis + } + ) + return loads \ No newline at end of file diff --git a/src/tests/Analyze/test_main.py b/src/tests/Analyze/test_Analyze_main.py similarity index 100% rename from src/tests/Analyze/test_main.py rename to src/tests/Analyze/test_Analyze_main.py diff --git a/src/tests/Results/test_Results_main.py b/src/tests/Results/test_Results_main.py new file mode 100644 index 0000000..db4d9fc --- /dev/null +++ b/src/tests/Results/test_Results_main.py @@ -0,0 +1,91 @@ +from ak_sap.Results.main import joint_reactions_parse, joint_displacements_parse + +def test_joint_reactions(): + ret = [2, ('1','1'), ('1', '1'), ('UDSTL1', 'UDSTL2'), (None, None), (0.0,0.0), (1,1), (2,2), (3,3), (4,4), (5,5), (6,6),0] + expected = [ + { + 'ObjectName': '1', + 'ElementName': '1', + 'LoadCase': 'UDSTL1', + 'StepType': None, + 'StepNum': 0.0, + 'F1': 1, + 'F2': 2, + 'F3': 3, + 'M1': 4, + 'M2': 5, + 'M3': 6}, + { + 'ObjectName': '1', + 'ElementName': '1', + 'LoadCase': 'UDSTL2', + 'StepType': None, + 'StepNum': 0.0, + 'F1': 1, + 'F2': 2, + 'F3': 3, + 'M1': 4, + 'M2': 5, + 'M3': 6}] + assert joint_reactions_parse(ret=ret) == expected + + ret = [1, ('1',), ('1'), ('UDSTL1'), (None), (0.0), (1), (2), (3), (4), (5), (6),0] + + assert joint_reactions_parse(ret = ret) == [ + { + 'ObjectName': ('1',), + 'ElementName': '1', + 'LoadCase': 'UDSTL1', + 'StepType': None, + 'StepNum': 0.0, + 'F1': 1, + 'F2': 2, + 'F3': 3, + 'M1': 4, + 'M2': 5, + 'M3': 6}] + +def test_joint_displacements(): + ret = [2, ('1','1'), ('1', '1'), ('UDSTL1', 'UDSTL2'), (None, None), (0.0,0.0), (1,1), (2,2), (3,3), (4,4), (5,5), (6,6),0] + expected = [ + { + 'ObjectName': '1', + 'ElementName': '1', + 'LoadCase': 'UDSTL1', + 'StepType': None, + 'StepNum': 0.0, + 'U1': 1, + 'U2': 2, + 'U3': 3, + 'R1': 4, + 'R2': 5, + 'R3': 6}, + { + 'ObjectName': '1', + 'ElementName': '1', + 'LoadCase': 'UDSTL2', + 'StepType': None, + 'StepNum': 0.0, + 'U1': 1, + 'U2': 2, + 'U3': 3, + 'R1': 4, + 'R2': 5, + 'R3': 6}] + assert joint_displacements_parse(ret=ret) == expected + + ret = [1, ('1',), ('1'), ('UDSTL1'), (None), (0.0), (1), (2), (3), (4), (5), (6),0] + + assert joint_displacements_parse(ret = ret) == [ + { + 'ObjectName': ('1',), + 'ElementName': '1', + 'LoadCase': 'UDSTL1', + 'StepType': None, + 'StepNum': 0.0, + 'U1': 1, + 'U2': 2, + 'U3': 3, + 'R1': 4, + 'R2': 5, + 'R3': 6}] \ No newline at end of file From a5bb744127a785744380a134a4f9fdb60501d906 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:57:37 -0800 Subject: [PATCH 14/16] `sap.Select` added --- documentation/Usage.ipynb | 37 +++++++++ src/ak_sap/Select/__init__.py | 1 + src/ak_sap/Select/main.py | 114 +++++++++++++++++++++++++++ src/ak_sap/wrapper.py | 2 + src/tests/Select/test_Select_main.py | 18 +++++ 5 files changed, 172 insertions(+) create mode 100644 src/ak_sap/Select/__init__.py create mode 100644 src/ak_sap/Select/main.py create mode 100644 src/tests/Select/test_Select_main.py diff --git a/documentation/Usage.ipynb b/documentation/Usage.ipynb index 0ac3932..06af808 100644 --- a/documentation/Usage.ipynb +++ b/documentation/Usage.ipynb @@ -215,6 +215,43 @@ "tables.update(TableKey='Material Properties 01 - General', data=df, apply=True)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Select" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "select = sap.Select\n", + "\n", + "select.all() #Select all objects\n", + "select.clear() #Deselect all objects\n", + "\n", + "select.constraint(name='Diaph1')#Select points in constraint\n", + "select.constraint(name='Diaph1', reverse=True) #Deselect points in constraint\n", + "\n", + "select.invert() #Invert selections\n", + "select.selected #Returns list of selected objects\n", + "select.previous() #restores the previous selection\n", + "\n", + "#Selection based on plane\n", + "select.in_plane(pointname='1', plane='XY') #Select in XY plane\n", + "select.in_plane(pointname='2', plane='YZ', reverse=False) #Deselect\n", + "\n", + "#Select by property\n", + "select.property(type='Area', name='ASEC1')\n", + "select.property(type='Cable', name='CAB1', reverse=True)\n", + "select.property(type='Frame', name='FSEC1')\n", + "select.property(type='Link', name='GAP1', reverse=True)\n", + "select.property(type='Material', name='A992Fy50')\n", + "select.property(type='Solid', name='SOLID1', reverse=True)\n", + "select.property(type='Tendon', name='TEN1')" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/src/ak_sap/Select/__init__.py b/src/ak_sap/Select/__init__.py new file mode 100644 index 0000000..21d6195 --- /dev/null +++ b/src/ak_sap/Select/__init__.py @@ -0,0 +1 @@ +from .main import Select \ No newline at end of file diff --git a/src/ak_sap/Select/main.py b/src/ak_sap/Select/main.py new file mode 100644 index 0000000..b44d586 --- /dev/null +++ b/src/ak_sap/Select/main.py @@ -0,0 +1,114 @@ +from typing import Literal + +from ak_sap.utils import MasterClass, log +from ak_sap.utils.decorators import smooth_sap_do + +class Select(MasterClass): + def __init__(self, mySapObject) -> None: + super().__init__(mySapObject=mySapObject) + self.__SelectObj = mySapObject.SapModel.SelectObj + + @smooth_sap_do + def all(self) -> bool: + """selects all objects in the model + """ + return self.__SelectObj.All(False) + + @smooth_sap_do + def clear(self) -> bool: + """Deselects all objects in model""" + return self.__SelectObj.ClearSelection() + + @smooth_sap_do + def constraint(self, name: str, reverse: bool=False) -> bool: + """selects or deselects all point objects to which the specified constraint has been assigned + + Args: + name (str): name of an existing joint constraint + reverse (bool): deselect + """ + return self.__SelectObj.Constraint(name, reverse) + + @property + def selected(self) -> list[dict]: + return selected_parse(ret=self.__SelectObj.GetSelected()), 0 + + @smooth_sap_do + def in_plane(self, pointname: str, plane: Literal['XY', 'YZ', 'XZ'], reverse: bool=False) -> bool: + """selects or deselects all objects that are in the same plane as specified point object + + Args: + pointname (str): point name + plane (Literal['XY', 'YZ', 'XZ']): plane to select + reverse (bool, optional): deselect. Defaults to False. + + Raises: + Exception: If invalid plane option chosen + """ + match plane.casefold(): + case 'xy': + return self.__SelectObj.PlaneXY(pointname, reverse) + case 'yz': + return self.__SelectObj.PlaneYZ(pointname, reverse) + case 'xz': + return self.__SelectObj.PlaneXZ(pointname, reverse) + case _: + raise Exception(f'{plane=} is not a valid choice.') + + @smooth_sap_do + def invert(self) -> bool: + """deselects all selected objects and selects all unselected objects + """ + return self.__SelectObj.InvertSelection() + + @smooth_sap_do + def previous(self) -> bool: + """restores the previous selection + """ + return self.__SelectObj.PreviousSelection() + + @smooth_sap_do + def property(self, + type: Literal['Area', 'Cable', 'Frame', 'Link', 'Material', 'Solid', 'Tendon'], + name: str, + reverse: bool=False): + """selects or deselects all objects to which the specified property has been assigned + + Args: + type (Literal['Area', 'Cable', 'Frame', 'Link', 'Material', 'Solid', 'Tendon']): Property Type + name (str): Propertyname + reverse (bool, optional): Deselect. Defaults to False. + + Raises: + Exception: If invalid type option chosen + """ + match type.casefold(): + case 'area': + return self.__SelectObj.PropertyArea(name, ) + case 'cable': + return self.__SelectObj.PropertyCable(name) + case 'frame': + return self.__SelectObj.PropertyFrame(name) + case 'link': + return self.__SelectObj.PropertyLink(name) + case 'material': + return self.__SelectObj.PropertyMaterial(name) + case 'solid': + return self.__SelectObj.PropertySolid(name) + case 'tendon': + return self.__SelectObj.PropertyTendon(name) + case _: + raise Exception(f'{type=} is not a valid choice.') + +def selected_parse(ret: list) -> list[dict]: + assert ret[-1] == 0 + selected: list[dict] = [] + + for idx in range(ret[0]): + selected.append( + { + 'ObjectType': ['Point', 'Frame', 'Cable', 'Tendon', 'Area', 'Solid', 'Link'][ret[1][idx]-1], + 'ObjectName': ret[2][idx] + } + ) + return selected \ No newline at end of file diff --git a/src/ak_sap/wrapper.py b/src/ak_sap/wrapper.py index b1f2a30..a9175c5 100644 --- a/src/ak_sap/wrapper.py +++ b/src/ak_sap/wrapper.py @@ -10,6 +10,7 @@ from ak_sap.Model import Model from ak_sap.Object import Object from ak_sap.Results import Results +from ak_sap.Select import Select from ak_sap.utils.logger import log class Sap2000Wrapper: @@ -27,6 +28,7 @@ def __init__(self, attach_to_exist: bool = True, program_path: str|Path|None = N self.Load = Load(mySapObject=self.mySapObject) self.Results = Results(mySapObject=self.mySapObject) self.Material = Material(mySapObject=self.mySapObject) + self.Select = Select(mySapObject=self.mySapObject) log.info('Sap2000Wrapper Initialized') diff --git a/src/tests/Select/test_Select_main.py b/src/tests/Select/test_Select_main.py new file mode 100644 index 0000000..4078931 --- /dev/null +++ b/src/tests/Select/test_Select_main.py @@ -0,0 +1,18 @@ +from ak_sap.Select.main import selected_parse + +def test_selected_parse(): + ret = [0, (), (), 0] + assert selected_parse(ret) == [] + + ret = [3, (1, 1, 2), ('3', '5', '4'), 0] + assert selected_parse(ret) == [ + { + 'ObjectType': 'Point', 'ObjectName': '3'}, + { + 'ObjectType': 'Point', 'ObjectName': '5'}, + { + 'ObjectType': 'Frame', 'ObjectName': '4'}] + + ret = [1, (1,), ('3',), 0] + assert selected_parse(ret) == [{'ObjectType': 'Point', 'ObjectName': '3'}] + \ No newline at end of file From 49bc6faebbc8e9d6a6fa91d4c1f808218d893786 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:57:53 -0800 Subject: [PATCH 15/16] Update Layout.md --- documentation/Layout.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/documentation/Layout.md b/documentation/Layout.md index 53df78c..07f1d27 100644 --- a/documentation/Layout.md +++ b/documentation/Layout.md @@ -177,6 +177,31 @@ df.iloc[0,0] = 'New Value' tables.update(TableKey='Material Properties 01 - General', data=df, apply=True) ``` +## Select +elect = sap.Select + +select.all() #Select all objects +select.clear() #Deselect all objects + +select.constraint(name='Diaph1')#Select points in constraint +select.constraint(name='Diaph1', reverse=True) #Deselect points in constraint + +select.invert() #Invert selections +select.selected #Returns list of selected objects +select.previous() #restores the previous selection + +#Selection based on plane +select.in_plane(pointname='1', plane='XY') #Select in XY plane +select.in_plane(pointname='2', plane='YZ', reverse=False) #Deselect + +#Select by property +select.property(type='Area', name='ASEC1') +select.property(type='Cable', name='CAB1', reverse=True) +select.property(type='Frame', name='FSEC1') +select.property(type='Link', name='GAP1', reverse=True) +select.property(type='Material', name='A992Fy50') +select.property(type='Solid', name='SOLID1', reverse=True) +select.property(type='Tendon', name='TEN1') ## Loads Control the definition and assignments of loads. From 5da4513d67ef25e4c6acc4cfa7d46f2f47a05374 Mon Sep 17 00:00:00 2001 From: Arun Kishore <50844460+rpakishore@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:13:46 -0800 Subject: [PATCH 16/16] Update __init__.py --- src/ak_sap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ak_sap/__init__.py b/src/ak_sap/__init__.py index 83f19bd..a68bdda 100644 --- a/src/ak_sap/__init__.py +++ b/src/ak_sap/__init__.py @@ -1,5 +1,5 @@ "Python wrapper for SAP2000 API" -__version__ = "0.0.1" +__version__ = "0.0.2" from ak_sap.utils.logger import log from ak_sap.wrapper import Sap2000Wrapper