diff --git a/guis/main.ui b/guis/main.ui index 5087654..0517d28 100644 --- a/guis/main.ui +++ b/guis/main.ui @@ -53,7 +53,7 @@ - 0 + 1 @@ -299,23 +299,6 @@ B for box Capytaine - - - - my_model - - - - - - - Go! - - - - - - @@ -326,20 +309,16 @@ B for box - - - - Make DAVE model + + + + 0.010000000000000 - - - - - - TextLabel + + 9999999.000000000000000 - - true + + 100.000000000000000 @@ -350,72 +329,62 @@ B for box - - + + - Periods [s] + Headings [degrees] - - + + - hyd origin + Output file names - - + + - Symmetry in XZ + Add lid at z=-0.01m (can not be used together with symmetry) - - + + - shape file for buoyancy + Use symmetry in XY for output (only headings 0...180 defined) - - + + - Body name [str] + TextLabel - - - - - - - - - np.linspace(0, 180, 9) + + true - - + + - Output file names + [*np.linspace(0.5,10,num=20), 11,12,14,16,20] - - - - + Show mesh - - + + - Headings [degrees] + Periods [s] @@ -426,53 +395,41 @@ B for box - - - - - - - - + + - cog z + Waterdepth [m] - - + + - Radii of gyration + Use symmetry in XZ for mesh (only half of mesh defined) - - + + - Waterdepth [m] + Go! - - + + + + + - [*np.linspace(0.5,10,num=20), 11,12,14,16,20] + np.linspace(0, 180, 9) - - - - - - - 0.010000000000000 - - - 9999999.000000000000000 - - - 100.000000000000000 + + + + my_model @@ -557,7 +514,7 @@ B for box 0 0 - 232 + 250 932 diff --git a/src/pymeshup/gui/capytaine_runner.py b/src/pymeshup/gui/capytaine_runner.py index 01bb5e7..52555a3 100644 --- a/src/pymeshup/gui/capytaine_runner.py +++ b/src/pymeshup/gui/capytaine_runner.py @@ -1,4 +1,5 @@ import logging +import warnings import netCDF4 # required for saving - fallback to scipy.io.netcdf causes issues @@ -33,29 +34,48 @@ def make_database(body, omegas, wave_directions, waterdepth=0): return dataset -def run_capytaine(name: str, # name of the body eg "boat" - file_grid : str, # input file, e.g. grid.stl - periods : np.array, # periods in seconds +def run_capytaine(file_grid : str, # input file, e.g. grid.stl + periods : np.array, # periods in seconds directions_deg : np.array, - waterdepth : float = None, # waterdepth - symmetry : bool = False, # symmetry in XZ plane - show_only : bool = False # show only the mesh + waterdepth : float = None, # waterdepth + grid_symmetry : bool = False, # symmetry in XZ plane + heading_symmetry : bool = False, # symmetry in YZ plane + show_only : bool = False, # show only the mesh + lid : bool = True, # add a lid + outfile : str = None # output file ): + name = file_grid[:-4] + + if grid_symmetry and not heading_symmetry: + warnings.warn('Grid symmetry requires heading symmetry') + periods = np.array(periods) directions = np.array(directions_deg) * pi / 180 - file_out_nc = file_grid[:-4] + '.nc' - outfile = "{}.dhyd".format(file_grid[:-4]) + file_out_nc = outfile + '.nc' + outfile_dhyd = outfile + '.dhyd' omega = 2 * np.pi / periods - boat = cpt.FloatingBody.from_file(file_grid, file_format="stl", name=name) + # load and create mesh + hull_mesh = cpt.load_mesh(file_grid, file_format="stl") + + # generate lid + if lid: + lid_mesh = hull_mesh.generate_lid(z=-0.01) + else: + lid_mesh = None + + + if grid_symmetry: + if lid: + raise ValueError('Symmetry and lid are not compatible') + + hull_mesh = ReflectionSymmetricMesh(hull_mesh, plane=xOz_Plane, name=f"{name}_mesh") - if symmetry: - mesh = ReflectionSymmetricMesh(boat.mesh, plane=xOz_Plane, name=f"{name}_mesh") - boat = cpt.FloatingBody(mesh=mesh) + boat = cpt.FloatingBody(mesh=hull_mesh, lid_mesh=lid_mesh) boat.add_all_rigid_body_dofs() boat.keep_immersed_part() @@ -79,12 +99,12 @@ def run_capytaine(name: str, # name of the body eg "boat" print(f'saved NC results as {file_out_nc}') hyd = Hyddb1.create_from_capytaine(filename=file_out_nc) - if symmetry: + if heading_symmetry: hyd.symmetry = mafredo.Symmetry.XZ else: hyd.symmetry = mafredo.Symmetry.No - hyd.save_as(outfile) - print(f'Saved as: {outfile}') + hyd.save_as(outfile_dhyd) + print(f'Saved as: {outfile_dhyd}') hyd.plot(do_show=True) diff --git a/src/pymeshup/gui/examples/sailboat.pym b/src/pymeshup/gui/examples/sailboat.pym new file mode 100644 index 0000000..40c2694 --- /dev/null +++ b/src/pymeshup/gui/examples/sailboat.pym @@ -0,0 +1,47 @@ +# Example of a pontoon shaped barge +# created from Frames + +width = 30.48 +height = 6.114 +fr = 100.6 / 55 # frame + +stern = Frame(0,4.284, + width/2, 4.284, + width/2, height).autocomplete() + +end_of_skeg = Frame(0,0, + width/2, 0, + width/2, height).autocomplete() + +main = Frame(0,0, + width/2, 0, + width/2, height).autocomplete() + +half_bow = Frame(0,1.5, + width/2, 1.5, + width/2, height).autocomplete() + + +bow = stern.copy() + + +Barge = Hull( + 0, bow, + 6*fr, end_of_skeg, + 47*fr, main, + 53*fr, half_bow, + 55*fr, bow + ) + + +# Add skegs + +skeg = Box(0, 8.9, + -0.2, 0.2, + 0, 6) +skegPS = skeg.move(y=8) +skegSB = skeg.move(y=-8) + +Hull_K10031 = Barge.add(skegPS).add(skegSB) + +del skegPS, skegSB, skeg, Barge \ No newline at end of file diff --git a/src/pymeshup/gui/forms/main_form.py b/src/pymeshup/gui/forms/main_form.py index 23c3f5e..1b598b5 100644 --- a/src/pymeshup/gui/forms/main_form.py +++ b/src/pymeshup/gui/forms/main_form.py @@ -221,145 +221,100 @@ def setupUi(self, MainWindow): self.Capytaine.setObjectName(u"Capytaine") self.gridLayout_4 = QGridLayout(self.Capytaine) self.gridLayout_4.setObjectName(u"gridLayout_4") - self.teOutputFile = QLineEdit(self.Capytaine) - self.teOutputFile.setObjectName(u"teOutputFile") - - self.gridLayout_4.addWidget(self.teOutputFile, 15, 1, 1, 2) - - self.pbRunCapytaine = QPushButton(self.Capytaine) - self.pbRunCapytaine.setObjectName(u"pbRunCapytaine") - - self.gridLayout_4.addWidget(self.pbRunCapytaine, 26, 0, 1, 1) - - self.teHydOrigin = QLineEdit(self.Capytaine) - self.teHydOrigin.setObjectName(u"teHydOrigin") - - self.gridLayout_4.addWidget(self.teHydOrigin, 19, 2, 1, 1) - self.lblHeading = QLabel(self.Capytaine) self.lblHeading.setObjectName(u"lblHeading") self.lblHeading.setWordWrap(True) self.gridLayout_4.addWidget(self.lblHeading, 7, 2, 1, 1) - self.cbMakeDaveModel = QCheckBox(self.Capytaine) - self.cbMakeDaveModel.setObjectName(u"cbMakeDaveModel") - - self.gridLayout_4.addWidget(self.cbMakeDaveModel, 18, 1, 1, 1) - - self.lblPeriods = QLabel(self.Capytaine) - self.lblPeriods.setObjectName(u"lblPeriods") - self.lblPeriods.setWordWrap(True) + self.teWaterdepth = QDoubleSpinBox(self.Capytaine) + self.teWaterdepth.setObjectName(u"teWaterdepth") + self.teWaterdepth.setMinimum(0.010000000000000) + self.teWaterdepth.setMaximum(9999999.000000000000000) + self.teWaterdepth.setValue(100.000000000000000) - self.gridLayout_4.addWidget(self.lblPeriods, 4, 2, 1, 1) + self.gridLayout_4.addWidget(self.teWaterdepth, 8, 2, 1, 1) self.cbInf = QCheckBox(self.Capytaine) self.cbInf.setObjectName(u"cbInf") self.gridLayout_4.addWidget(self.cbInf, 8, 1, 1, 1) - self.label_11 = QLabel(self.Capytaine) - self.label_11.setObjectName(u"label_11") - - self.gridLayout_4.addWidget(self.label_11, 2, 0, 1, 1) - - self.label_16 = QLabel(self.Capytaine) - self.label_16.setObjectName(u"label_16") - - self.gridLayout_4.addWidget(self.label_16, 19, 1, 1, 1) - - self.cbSymmetry = QCheckBox(self.Capytaine) - self.cbSymmetry.setObjectName(u"cbSymmetry") - - self.gridLayout_4.addWidget(self.cbSymmetry, 13, 0, 1, 3) + self.label_13 = QLabel(self.Capytaine) + self.label_13.setObjectName(u"label_13") - self.label_17 = QLabel(self.Capytaine) - self.label_17.setObjectName(u"label_17") + self.gridLayout_4.addWidget(self.label_13, 5, 0, 1, 1) - self.gridLayout_4.addWidget(self.label_17, 21, 1, 1, 1) + self.label_20 = QLabel(self.Capytaine) + self.label_20.setObjectName(u"label_20") - self.label_12 = QLabel(self.Capytaine) - self.label_12.setObjectName(u"label_12") + self.gridLayout_4.addWidget(self.label_20, 1, 0, 1, 1) - self.gridLayout_4.addWidget(self.label_12, 1, 0, 1, 1) + self.cbLid = QCheckBox(self.Capytaine) + self.cbLid.setObjectName(u"cbLid") - self.teShapeFile = QLineEdit(self.Capytaine) - self.teShapeFile.setObjectName(u"teShapeFile") + self.gridLayout_4.addWidget(self.cbLid, 9, 0, 1, 3) - self.gridLayout_4.addWidget(self.teShapeFile, 21, 2, 1, 1) + self.cbSymmetryHeadings = QCheckBox(self.Capytaine) + self.cbSymmetryHeadings.setObjectName(u"cbSymmetryHeadings") - self.teHeading = QLineEdit(self.Capytaine) - self.teHeading.setObjectName(u"teHeading") + self.gridLayout_4.addWidget(self.cbSymmetryHeadings, 15, 0, 1, 3) - self.gridLayout_4.addWidget(self.teHeading, 5, 2, 1, 1) - - self.label_20 = QLabel(self.Capytaine) - self.label_20.setObjectName(u"label_20") + self.lblPeriods = QLabel(self.Capytaine) + self.lblPeriods.setObjectName(u"lblPeriods") + self.lblPeriods.setWordWrap(True) - self.gridLayout_4.addWidget(self.label_20, 15, 0, 1, 1) + self.gridLayout_4.addWidget(self.lblPeriods, 4, 2, 1, 1) - self.teMeshFile = QLineEdit(self.Capytaine) - self.teMeshFile.setObjectName(u"teMeshFile") + self.tePeriods = QLineEdit(self.Capytaine) + self.tePeriods.setObjectName(u"tePeriods") - self.gridLayout_4.addWidget(self.teMeshFile, 0, 2, 1, 1) + self.gridLayout_4.addWidget(self.tePeriods, 2, 2, 1, 1) self.pbShowMesh = QPushButton(self.Capytaine) self.pbShowMesh.setObjectName(u"pbShowMesh") - self.gridLayout_4.addWidget(self.pbShowMesh, 14, 0, 1, 3) + self.gridLayout_4.addWidget(self.pbShowMesh, 16, 0, 1, 1) - self.label_13 = QLabel(self.Capytaine) - self.label_13.setObjectName(u"label_13") + self.label_11 = QLabel(self.Capytaine) + self.label_11.setObjectName(u"label_11") - self.gridLayout_4.addWidget(self.label_13, 5, 0, 1, 1) + self.gridLayout_4.addWidget(self.label_11, 2, 0, 1, 1) self.label_9 = QLabel(self.Capytaine) self.label_9.setObjectName(u"label_9") self.gridLayout_4.addWidget(self.label_9, 0, 0, 1, 1) - self.teCogZ = QLineEdit(self.Capytaine) - self.teCogZ.setObjectName(u"teCogZ") - - self.gridLayout_4.addWidget(self.teCogZ, 25, 2, 1, 1) - - self.teName = QLineEdit(self.Capytaine) - self.teName.setObjectName(u"teName") - - self.gridLayout_4.addWidget(self.teName, 1, 2, 1, 1) - - self.label_19 = QLabel(self.Capytaine) - self.label_19.setObjectName(u"label_19") + self.label_15 = QLabel(self.Capytaine) + self.label_15.setObjectName(u"label_15") - self.gridLayout_4.addWidget(self.label_19, 25, 1, 1, 1) + self.gridLayout_4.addWidget(self.label_15, 8, 0, 1, 1) - self.label_18 = QLabel(self.Capytaine) - self.label_18.setObjectName(u"label_18") + self.cbSymmetryMesh = QCheckBox(self.Capytaine) + self.cbSymmetryMesh.setObjectName(u"cbSymmetryMesh") - self.gridLayout_4.addWidget(self.label_18, 23, 1, 1, 1) + self.gridLayout_4.addWidget(self.cbSymmetryMesh, 13, 0, 1, 3) - self.label_15 = QLabel(self.Capytaine) - self.label_15.setObjectName(u"label_15") + self.pbRunCapytaine = QPushButton(self.Capytaine) + self.pbRunCapytaine.setObjectName(u"pbRunCapytaine") - self.gridLayout_4.addWidget(self.label_15, 8, 0, 1, 1) + self.gridLayout_4.addWidget(self.pbRunCapytaine, 21, 0, 1, 1) - self.tePeriods = QLineEdit(self.Capytaine) - self.tePeriods.setObjectName(u"tePeriods") + self.teMeshFile = QLineEdit(self.Capytaine) + self.teMeshFile.setObjectName(u"teMeshFile") - self.gridLayout_4.addWidget(self.tePeriods, 2, 2, 1, 1) + self.gridLayout_4.addWidget(self.teMeshFile, 0, 2, 1, 1) - self.teRadii = QLineEdit(self.Capytaine) - self.teRadii.setObjectName(u"teRadii") + self.teHeading = QLineEdit(self.Capytaine) + self.teHeading.setObjectName(u"teHeading") - self.gridLayout_4.addWidget(self.teRadii, 23, 2, 1, 1) + self.gridLayout_4.addWidget(self.teHeading, 5, 2, 1, 1) - self.teWaterdepth = QDoubleSpinBox(self.Capytaine) - self.teWaterdepth.setObjectName(u"teWaterdepth") - self.teWaterdepth.setMinimum(0.010000000000000) - self.teWaterdepth.setMaximum(9999999.000000000000000) - self.teWaterdepth.setValue(100.000000000000000) + self.teOutputFile = QLineEdit(self.Capytaine) + self.teOutputFile.setObjectName(u"teOutputFile") - self.gridLayout_4.addWidget(self.teWaterdepth, 8, 2, 1, 1) + self.gridLayout_4.addWidget(self.teOutputFile, 1, 2, 1, 1) self.tabWidget.addTab(self.Capytaine, "") self.splitter_4.addWidget(self.tabWidget) @@ -405,7 +360,7 @@ def setupUi(self, MainWindow): self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") - self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 232, 932)) + self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 250, 932)) self.horizontalLayout_6 = QHBoxLayout(self.scrollAreaWidgetContents) self.horizontalLayout_6.setObjectName(u"horizontalLayout_6") self.label = QLabel(self.scrollAreaWidgetContents) @@ -436,7 +391,7 @@ def setupUi(self, MainWindow): self.retranslateUi(MainWindow) - self.tabWidget.setCurrentIndex(0) + self.tabWidget.setCurrentIndex(1) QMetaObject.connectSlotsByName(MainWindow) @@ -475,26 +430,22 @@ def retranslateUi(self, MainWindow): "2/3 for 2d/3d", None)) #endif // QT_CONFIG(tooltip) self.tabWidget.setTabText(self.tabWidget.indexOf(self.Mesh), QCoreApplication.translate("MainWindow", u"Mesh", None)) - self.teOutputFile.setText(QCoreApplication.translate("MainWindow", u"my_model", None)) - self.pbRunCapytaine.setText(QCoreApplication.translate("MainWindow", u"Go!", None)) self.lblHeading.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) - self.cbMakeDaveModel.setText(QCoreApplication.translate("MainWindow", u"Make DAVE model", None)) - self.lblPeriods.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) self.cbInf.setText(QCoreApplication.translate("MainWindow", u"Infinite", None)) - self.label_11.setText(QCoreApplication.translate("MainWindow", u"Periods [s]", None)) - self.label_16.setText(QCoreApplication.translate("MainWindow", u"hyd origin", None)) - self.cbSymmetry.setText(QCoreApplication.translate("MainWindow", u"Symmetry in XZ", None)) - self.label_17.setText(QCoreApplication.translate("MainWindow", u"shape file for buoyancy", None)) - self.label_12.setText(QCoreApplication.translate("MainWindow", u"Body name [str]", None)) - self.teHeading.setText(QCoreApplication.translate("MainWindow", u"np.linspace(0, 180, 9)", None)) + self.label_13.setText(QCoreApplication.translate("MainWindow", u"Headings [degrees]", None)) self.label_20.setText(QCoreApplication.translate("MainWindow", u"Output file names", None)) + self.cbLid.setText(QCoreApplication.translate("MainWindow", u"Add lid at z=-0.01m (can not be used together with symmetry)", None)) + self.cbSymmetryHeadings.setText(QCoreApplication.translate("MainWindow", u"Use symmetry in XY for output (only headings 0...180 defined)", None)) + self.lblPeriods.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) + self.tePeriods.setText(QCoreApplication.translate("MainWindow", u"[*np.linspace(0.5,10,num=20), 11,12,14,16,20]", None)) self.pbShowMesh.setText(QCoreApplication.translate("MainWindow", u"Show mesh", None)) - self.label_13.setText(QCoreApplication.translate("MainWindow", u"Headings [degrees]", None)) + self.label_11.setText(QCoreApplication.translate("MainWindow", u"Periods [s]", None)) self.label_9.setText(QCoreApplication.translate("MainWindow", u"Mesh [file, in workfolder]", None)) - self.label_19.setText(QCoreApplication.translate("MainWindow", u"cog z", None)) - self.label_18.setText(QCoreApplication.translate("MainWindow", u"Radii of gyration", None)) self.label_15.setText(QCoreApplication.translate("MainWindow", u"Waterdepth [m]", None)) - self.tePeriods.setText(QCoreApplication.translate("MainWindow", u"[*np.linspace(0.5,10,num=20), 11,12,14,16,20]", None)) + self.cbSymmetryMesh.setText(QCoreApplication.translate("MainWindow", u"Use symmetry in XZ for mesh (only half of mesh defined)", None)) + self.pbRunCapytaine.setText(QCoreApplication.translate("MainWindow", u"Go!", None)) + self.teHeading.setText(QCoreApplication.translate("MainWindow", u"np.linspace(0, 180, 9)", None)) + self.teOutputFile.setText(QCoreApplication.translate("MainWindow", u"my_model", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.Capytaine), QCoreApplication.translate("MainWindow", u"Capytaine", None)) self.pbWorkFolder.setText(QCoreApplication.translate("MainWindow", u"Workfolder:", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) diff --git a/src/pymeshup/gui/main.py b/src/pymeshup/gui/main.py index c48e4ce..9f4f700 100644 --- a/src/pymeshup/gui/main.py +++ b/src/pymeshup/gui/main.py @@ -357,23 +357,35 @@ def run_captyaine(self, dryrun=False): periods = self.update_period() heading = self.update_heading() - name = self.ui.teName.text() file_grid = str(pathlib.Path(self.curdir) / self.ui.teMeshFile.text()) - symmetry = self.ui.cbSymmetry.isChecked() + symmetry_m = self.ui.cbSymmetryMesh.isChecked() + symmetry_h = self.ui.cbSymmetryHeadings.isChecked() + + if symmetry_m and not symmetry_h: + msg_box = QMessageBox() + msg_box.setText("Symmetry in grid but not in headings, are you sure? Continue?") + msg_box.setWindowTitle("Confirmation") + msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + if msg_box.exec() == QMessageBox.No: + return if self.ui.cbInf.isChecked(): waterdepth = float("inf") else: waterdepth = self.ui.teWaterdepth.value() + do_lid = self.ui.cbLid.isChecked() + run_capytaine( - name=name, file_grid=file_grid, periods=periods, directions_deg=heading, waterdepth=waterdepth, - symmetry=symmetry, + grid_symmetry=symmetry_m, + heading_symmetry=symmetry_h, show_only=dryrun, + lid=do_lid, + outfile=self.ui.teOutputFile.text() ) def run(self): diff --git a/src/pymeshup/helpers/triangulate_non_convex.py b/src/pymeshup/helpers/triangulate_non_convex.py index fb28cf5..bcb25df 100644 --- a/src/pymeshup/helpers/triangulate_non_convex.py +++ b/src/pymeshup/helpers/triangulate_non_convex.py @@ -1,3 +1,5 @@ +import warnings + from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray from vtkmodules.vtkFiltersGeneral import vtkContourTriangulator @@ -29,7 +31,52 @@ def triangulate_poly(vertices): result = vtkContourTriangulator.TriangulatePolygon(idList, vtkPts,cellArray) if result != 1: - raise ValueError("Triangulation failed") + + + # import matplotlib.pyplot as plt + # + # # plot in 3d + # fig = plt.figure() + # ax = fig.add_subplot(111, projection='3d') + # + # for i,p in enumerate(out_points): + # ax.scatter(*p) + # + # # add label with number + # ax.text(*p, s=str(i)) + # + # plt.title("Triangulation of the following points failed") + # plt.show() + + + warnings.warn("Triangulation failed, trying to re-order the points - SOME GEOMETRY MAY BE MISSING") + + # re-order the points to make the triangulation work and try again + # do this by making a convex hull of the points + + from scipy.spatial import ConvexHull + import numpy as np + + # check all points have the same x-coordinate + unique_x = set([p[0] for p in out_points]) + assert len(unique_x) == 1 + + unique_x = unique_x.pop() + + # points 2d for convex hull + points_2d = [(p[1], p[2]) for p in out_points] + + hull = ConvexHull(np.array(points_2d)) + + # get the vertices of the convex hull + hull_points = [out_points[i] for i in hull.vertices] + + # add the first point to the end to close the hull + hull_points.append(hull_points[0]) + + # try again + return triangulate_poly(hull_points) + # extract the vertices from cellArray diff --git a/src/run.py b/src/run.py new file mode 100644 index 0000000..dacc528 --- /dev/null +++ b/src/run.py @@ -0,0 +1,9 @@ +import sys +sys.path.append(r"C:\data\DAVE\public\pymeshup\src") +sys.path.append(r"C:\data\DAVE\public\mafredo\src") + +from pymeshup.gui.main import * + +app = QApplication() +gui = Gui() +app.exec() diff --git a/tests/test_read_ghs_P41.py b/tests/test_read_ghs_P41.py new file mode 100644 index 0000000..e69de29