Macro Half-Hull Model: Difference between revisions

From FreeCAD Documentation
(Marked this version for translation)
 
(45 intermediate revisions by 6 users not shown)
Line 1: Line 1:
<languages/>
{{Macro|Icon=Macro_Half_Hull_Model|Name=Macro Half-Hull Model|Description=This macro generates both three dimensional half-hull and full-hull models from a series of 2D line drawings.|Author=Piffpoof}}
<translate>
<!--T:1-->
{{Macro
|Name=Macro Half-Hull Model
|Icon=Macro_Half_Hull_Model.png
|Description=This macro generates both three dimensional half-hull and full-hull models from a series of 2D line drawings.<br/>This macro makes simple models of the hulls of boats and ships. It is intended to aid people modeling or designing hulls by providing the hull surface in a straight forward and timely fashion so they can avoid the time consuming full process.
|Author=Piffpoof
|Version=1.0
|Date=2016-01-25
|FCVersion=<= 0.16
|Download=[https://www.freecadweb.org/wiki/images/4/44/Macro_Half_Hull_Model.png ToolBar Icon]
}}

==Description== <!--T:95-->
This macro makes simple models of the hulls of boats and ships. It is intended to aid people modeling or designing hulls by providing the hull surface in a straight forward and timely fashion so they can avoid the time consuming full process.
This macro makes simple models of the hulls of boats and ships. It is intended to aid people modeling or designing hulls by providing the hull surface in a straight forward and timely fashion so they can avoid the time consuming full process.


</translate>
{{Codeextralink|http://pastebin.com/raw/tZMpUi6F}}
<translate>

<!--T:97-->
[[File:Macro_Half-Hull_ModelScreenBoatInABottle.jpg]]
[[File:Macro_Half-Hull_ModelScreenBoatInABottle.jpg]]


==Background==
==Background== <!--T:3-->


<!--T:4-->
Since antiquity boat builders have had to design boats and then realise them in a three dimensional environment where very few lines are straight, very few surfaces flat and no angles constant over a distance. Hull models were used to make to-scale models which could then be used to review the design or communicate concepts to other persons. The laws of hydrodynamics demand that any vessel passing through a liquid be symmetrical in the cross-section if it is to track (move in a straight line) accurately. Consequently building one side of the hull in the model was all that was necessary - if the half-hull was perfect then its mirror image would also be perfect. In latter years half-hulls became used for symbolic purposes such as plaques, and nautical art.
Since antiquity boat builders have had to design boats and then realise them in a three dimensional environment where very few lines are straight, very few surfaces flat and no angles constant over a distance. Hull models were used to make to-scale models which could then be used to review the design or communicate concepts to other persons. The laws of hydrodynamics demand that any vessel passing through a liquid be symmetrical in the cross-section if it is to track (move in a straight line) accurately. Consequently building one side of the hull in the model was all that was necessary - if the half-hull was perfect then its mirror image would also be perfect. In latter years half-hulls became used for symbolic purposes such as plaques, and nautical art.


<!--T:5-->
This code came out of a project to build a model in FreeCAD of a 12.5 metre sailing boat. The builders no longer had drawings and the son of the original owner doubted there ever were full drawings. Consequently the hull dimensions had to be measured and then a model constructed within the software, Modern software packages like FreeCAD offer many beneficial features like symmetry constraints but when the final measurement is a model that is "easy on the eye", much "massaging" of models is required. This code was written to help automate that process.
This code came out of a project to build a model in FreeCAD of a 12.5 metre sailing boat. The builders no longer had drawings and the son of the original owner doubted there ever were full drawings. Consequently the hull dimensions had to be measured and then a model constructed within the software, Modern software packages like FreeCAD offer many beneficial features like symmetry constraints but when the final measurement is a model that is "easy on the eye", much "massaging" of models is required. This code was written to help automate that process.


<!--T:6-->
The initial purpose of this code was to provide a symmetrical hull which was malleable so it could be fitted to the 3D model being constructed. As the software progressed it was generalised so hopefully it would be of some use to persons:
The initial purpose of this code was to provide a symmetrical hull which was malleable so it could be fitted to the 3D model being constructed. As the software progressed it was generalised so hopefully it would be of some use to persons:
* boat designers producing boat diagrams which can be built from
* boat designers producing boat diagrams which can be built from
Line 15: Line 37:
* model hull builders.
* model hull builders.


<!--T:7-->
Certainly commercial boat designers have all sorts of high end software to assist their work, this isn't supposed to replace that. Rather this is for hte hobbyist or home builder to tinker around with.
Certainly commercial boat designers have all sorts of high end software to assist their work, this isn't supposed to replace that. Rather this is for the hobbyist or home builder to tinker around with.


<!--T:8-->
Nautical blueprints have a distinct history with respect to buildings and so are somewhat different in presentation. This is an example of a sailing boat which is more than a century old:
Nautical blueprints have a distinct history with respect to buildings and so are somewhat different in presentation. This is an example of a sailing boat which is more than a century old:


<!--T:9-->
[[File:Macro_Half-Hull_ModelImage0.jpg]]
[[File:Macro_Half-Hull_ModelImage0.jpg]]


<!--T:10-->
One of the end goals of this software is to use the Draft workbench to generate some of these diagrams using the model to generate the lines.
One of the end goals of this software is to use the Draft workbench to generate some of these diagrams using the model to generate the lines.


<!--T:11-->
'''Note on Units in FreeCAD:'''
'''Note on Units in FreeCAD:'''


<!--T:12-->
At present there isn't really any units management system within FreeCAD, but obviously a boat builder or modeller will need an accurate dimensioning system. To use this macro, decide to set the FreeCAD grid size to what ever is appropriate for your work (e.g. mm, cm, inch, foot). FreeCAD is consistent, one FreeCAD unit will constantly equal to one FreeCAD unit. And if you have decided to have a FreeCAD unit equal a specific physical length, then your drawings will remain consistently dimensioned. At present there is work on a units system for FreeCAD so this situation may change soon.
At present there isn't really any units management system within FreeCAD, but obviously a boat builder or modeller will need an accurate dimensioning system. To use this macro, decide to set the FreeCAD grid size to what ever is appropriate for your work (e.g. mm, cm, inch, foot). FreeCAD is consistent, one FreeCAD unit will constantly equal to one FreeCAD unit. And if you have decided to have a FreeCAD unit equal a specific physical length, then your drawings will remain consistently dimensioned. At present there is work on a units system for FreeCAD so this situation may change soon.


==Description==
==Description== <!--T:13-->
For this macro the hull shapes are defined by a minimum of 3 FreeCAD Sketches: one in the YZ plane, one or more in the XZ plane, one in the XY plane. Here is the minimum hull supported by this macro, it has only 3 Sketches:
For this macro the hull shapes are defined by a minimum of 3 FreeCAD Sketches: one in the YZ plane, one or more in the XZ plane, one in the XY plane. Here is the minimum hull supported by this macro, it has only 3 Sketches:


<!--T:14-->
[[File:Macro_Half-Hull_ModelImage1.jpg]]
[[File:Macro_Half-Hull_ModelImage1.jpg]]


<!--T:15-->
<b>Note</b>: in the above digram we are looking directly at the stern, the bow is pointing away from the viewpoint.
<b>Note</b>: in the above diagram we are looking directly at the stern, the bow is pointing away from the viewpoint.


<!--T:16-->
From front to back (bow to stern) the 3 Sketches are:
From front to back (bow to stern) the 3 Sketches are:
<table style="width: 500px" border="1">
<table style="width: 500px" border="1">
<tr>
<tr>
<td>stemline<br>
<td>stemline<br />
</td>
</td>
<td>YZ plane<br>
<td>YZ plane<br />
</td>
</td>
<td>red line in sketch<br>
<td>red line in sketch<br />
</td>
</td>
</tr>
</tr>
<tr>
<tr>
<td>cross-section<br>
<td>cross-section<br />
</td>
</td>
<td>XZ plane<br>
<td>XZ plane<br />
</td>
</td>
<td>green line in Sketch<br>
<td>green line in Sketch<br />
</td>
</td>
</tr>
</tr>
<tr>
<tr>
<td>transom<br>
<td>transom<br />
</td>
</td>
<td>XY plane<br>
<td>XY plane<br />
</td>
</td>
<td>blue line in Sketch<br>
<td>blue line in Sketch<br />
</td>
</td>
</tr>
</tr>
Line 63: Line 94:




<!--T:17-->
Perhaps it is easier to see with 7 Sketches (one in YZ plane, one in XY plane and 5 in the XZ plane):
Perhaps it is easier to see with 7 Sketches (one in YZ plane, one in XY plane and 5 in the XZ plane):


<!--T:18-->
[[File:Macro_Half-Hull_ModelImage2.jpg]]
[[File:Macro_Half-Hull_ModelImage2.jpg]]


<!--T:19-->
With 5 Sketches in the XZ plane it is becoming easier to perceive the shape of the hull. The next 2 pictures show the Sketch lines superimposed on the model FreeCAD constructs,
With 5 Sketches in the XZ plane it is becoming easier to perceive the shape of the hull. The next 2 pictures show the Sketch lines superimposed on the model FreeCAD constructs,


<!--T:20-->
[[File:Macro_Half-Hull_ModelImage3.jpg]]
[[File:Macro_Half-Hull_ModelImage3.jpg]]


<!--T:21-->
the second is the same model rotated 90 degrees so the bow is in the foreground:
the second is the same model rotated 90 degrees so the bow is in the foreground:


<!--T:22-->
[[File:Macro_Half-Hull_ModelImage3a.jpg]]
[[File:Macro_Half-Hull_ModelImage3a.jpg]]


<!--T:23-->
Some points to consider:
Some points to consider:


<!--T:24-->
* the Sketches are only for:
* the Sketches are only for:
** the stemline (or bow line) in the YZ plane (red in the above diagram);
** the stemline (or bow line) in the YZ plane (red in the above diagram);
Line 88: Line 127:
* there is no limit to the number of Sketches in the XZ plane (i.e. cross-sections), any number from one up
* there is no limit to the number of Sketches in the XZ plane (i.e. cross-sections), any number from one up


<!--T:25-->
With enough Sketches the model generated may even approach:
With enough Sketches the model generated may even approach:


<!--T:26-->
[[File:Macro_Half-Hull_ModelImage4.jpg]]
[[File:Macro_Half-Hull_ModelImage4.jpg]]


<!--T:27-->
The HalfHull macro generates 4 models:
The HalfHull macro generates 4 models:


<!--T:28-->
* starboard half-hull
* starboard half-hull
* port half-hull
* port half-hull
Line 99: Line 142:
* bulkheads for the complete hull, either with a flush deck or with a coachhouse
* bulkheads for the complete hull, either with a flush deck or with a coachhouse


<!--T:29-->
These models are all output in the unified location space of FreeCAD so they can be fitted together - for example the bulkheads can be inserted into the complete hull seamlessly. This is a picture of bulkheads in a boat model during construction:
These models are all output in the unified location space of FreeCAD so they can be fitted together - for example the bulkheads can be inserted into the complete hull seamlessly. This is a picture of bulkheads in a boat model during construction:


<!--T:30-->
[[File:Macro_Half-Hull_ModelImage5.jpg]]
[[File:Macro_Half-Hull_ModelImage5.jpg]]


<!--T:31-->
As well as the bulkheads generated by the macro (note that these bulkheads are for a coachhouse rather than a flush deck):
As well as the bulkheads generated by the macro (note that these bulkheads are for a coachhouse rather than a flush deck):


<!--T:32-->
[[File:Macro_Half-Hull_ModelImage6.jpg]]
[[File:Macro_Half-Hull_ModelImage6.jpg]]


<!--T:33-->
The composite image below shows the main outputs from this software (the port side half-hull is actually not shown but it is the mirror of the starboard half-hull which is shown). The outputs are in clockwise order from the upper left corner:
The composite image below shows the main outputs from this software (the port side half-hull is actually not shown but it is the mirror of the starboard half-hull which is shown). The outputs are in clockwise order from the upper left corner:
* starboard half-hull
* starboard half-hull
Line 113: Line 161:
* the complete hull with the bulkheads inserted
* the complete hull with the bulkheads inserted


<!--T:34-->
[[File:Macro_Half-Hull_ModelImage7.jpg]]
[[File:Macro_Half-Hull_ModelImage7.jpg]]


<!--T:35-->
As novelty features, the macro will also optionally produce plaques for the half-hulls, and even a bottle for the complete hull:
As novelty features, the macro will also optionally produce plaques for the half-hulls, and even a bottle for the complete hull:


<!--T:36-->
[[File:Macro_Half-Hull_ModelImage8.jpg]]
[[File:Macro_Half-Hull_ModelImage8.jpg]]


<!--T:37-->
[[File:Macro_Half-Hull_ModelImage9.jpg]]
[[File:Macro_Half-Hull_ModelImage9.jpg]]


==Installation==
==Installation== <!--T:38-->
All the code for halfHullModel.FCMacro is in one macro. So installation is comprised of copying the code to the appropriate Macro directory and invoking the Build Utility from the Macro menu, the Python console or a toolbar button (the preferred method).
All the code for halfHullModel.FCMacro is in one macro. So installation is comprised of copying the code to the appropriate Macro directory and invoking the Build Utility from the Macro menu, the Python console or a toolbar button (the preferred method).
* see [[Macro_Install_HowTo]] for information on how to install this macro code
* see [[How to install macros]] for information on how to install this macro code
* see [[Customize_ToolsBar]] for information how to install as a button on a toolbar
* see [[Customize Toolbars]] for information how to install as a button on a toolbar


==Usage==
==Usage== <!--T:39-->


<!--T:40-->
The FreeCAD operations involved in generating the hull model are rather complex and numerous. Things like the direction a line is drawn can cause the FreeCAD construction of the hull to either abort or turn out like:
The FreeCAD operations involved in generating the hull model are rather complex and numerous. Things like the direction a line is drawn can cause the FreeCAD construction of the hull to either abort or turn out like:


<!--T:41-->
[[File:Macro_Half-Hull_ModelUsage1.jpg]]
[[File:Macro_Half-Hull_ModelUsage1.jpg]]


<!--T:42-->
Consequently the steps below need to be followed closely. The macro does allow for certain data inconsistencies but generally if the data (i.e. the Sketches) are incorrect then the output will look like a cheese grater or the macro fill fail with an error.
Consequently the steps below need to be followed closely. The macro does allow for certain data inconsistencies but generally if the data (i.e. the Sketches) are incorrect then the output will look like a cheese grater or the macro fill fail with an error.


<!--T:43-->
The following instructions refer to the quadrants of the XY graph, this refers to the 4 quarters of the XY graph and they are labelled as follows:
The following instructions refer to the quadrants of the XY graph, this refers to the 4 quarters of the XY graph and they are labelled as follows:


<!--T:44-->
[[File:Macro_Half-Hull_ModelUsage2.jpg]]
[[File:Macro_Half-Hull_ModelUsage2.jpg]]


<!--T:45-->
'''Create a New Document'''
'''Create a New Document'''


<!--T:46-->
The very first thing is to create a new document in FreeCAD [[Image:Document-new.svg|24px]]. This document will hold all the Sketches that make up your hull definition.
The very first thing is to create a new document in FreeCAD [[Image:Document-new.svg|24px]]. This document will hold all the Sketches that make up your hull definition.


===Create the Stemline===
===Create the Stemline=== <!--T:47-->


<!--T:48-->
The first step is one of creating data for the hull model to be made from. The data is supplied in the form of Sketches within FreeCAD. After the hull model is generated, if changes are to be made then the Sketches are simply edited, and the second step of running the macro repeated.
The first step is one of creating data for the hull model to be made from. The data is supplied in the form of Sketches within FreeCAD. After the hull model is generated, if changes are to be made then the Sketches are simply edited, and the second step of running the macro repeatedly.


<!--T:49-->
# create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the YZ-Plane
# create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the YZ-Plane
# start at origin (0,0) and draw upwards into Quadrant I<br>[[File:Macro_Half-Hull_ModelUsage3.jpg]]
# start at origin (0,0) and draw upwards into Quadrant I<br />[[File:Macro_Half-Hull_ModelUsage3.jpg]]
# the bottom end of the stemline is at the origin (0,0) - this will be the point from where the placement of all the cross-sections and transom will be made
# the bottom end of the stemline is at the origin (0,0) - this will be the point from where the placement of all the cross-sections and transom will be made
# the number of line segments in this Sketch determines the number which will be required in each other Sketch
# the number of line segments in this Sketch determines the number which will be required in each other Sketch
Line 153: Line 214:
# for ease of identification it is probably worth naming the Sketch something like "stemline sketch"
# for ease of identification it is probably worth naming the Sketch something like "stemline sketch"


===Create the Cross-section Sketch(es)===
===Create the Cross-section Sketch(es)=== <!--T:50-->
# create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the XZ-Plane
# create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the XZ-Plane
# the following dialog will appear:<br>[[File:Macro_Half-Hull_ModelScreenSnapshot1.jpg]]<br>The dialog is asking how far from the origin the Sketch should be placed. This will refer to how far the cross-section is from the bottom of the stemline (which was placed at (0,0)). The cross-sections can be equally spaced but need not be. The forward-most cross-section will be at Y=0 (i.e. the origin where the stemline bottom ends) or at Y<=0. The cross-sections will be at increasingly negative Y values until the transom is at the most negative Y value. In the example above, the cross-section Sketch will be placed 50 FreeCAD units from the origin on the negative Y axis.
# the following dialog will appear:<br />[[File:Macro_Half-Hull_ModelScreenSnapshot1.jpg]]<br />The dialog is asking how far from the origin the Sketch should be placed. This will refer to how far the cross-section is from the bottom of the stemline (which was placed at (0,0)). The cross-sections can be equally spaced but need not be. The forward-most cross-section will be at Y=0 (i.e. the origin where the stemline bottom ends) or at Y<=0. The cross-sections will be at increasingly negative Y values until the transom is at the most negative Y value. In the example above, the cross-section Sketch will be placed 50 FreeCAD units from the origin on the negative Y axis.
#start on the Y axis and draw upwards into Quadrant I<br>- the first (i.e. foremost) cross-section should start at origin (0,0) (or it will look odd as the stemline ends at 0,0) but other cross-sections need only start on the Y axis.<br>[[File:Macro_Half-Hull_ModelUsage3.jpg]]
#start on the Y axis and draw upwards into Quadrant I<br />- the first (i.e. foremost) cross-section should start at origin (0,0) (or it will look odd as the stemline ends at 0,0) but other cross-sections need only start on the Y axis.<br />[[File:Macro_Half-Hull_ModelUsage3.jpg]]
#use the same number of line segments as in the Stemline Sketch
#use the same number of line segments as in the Stemline Sketch
# save sketch [[Image:Sketcher_LeaveSketch.svg|24px]]
# save sketch [[Image:Sketcher_LeaveSketch.svg|24px]]
Line 162: Line 223:
#for naming, it will make things easier to give some sort of sequence to the cross-sections, starting at the bow (i.e. the stemline) and increasing towards the stern (i.e. the transom)
#for naming, it will make things easier to give some sort of sequence to the cross-sections, starting at the bow (i.e. the stemline) and increasing towards the stern (i.e. the transom)


===Create the Transom Sketch in the XY Plane===
===Create the Transom Sketch in the XY Plane=== <!--T:51-->


<!--T:52-->
#create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the XY-Plane
#create a new sketch [[Image:Sketcher_NewSketch.svg|24px]], in the XY-Plane
#start on the Y axis between quadrant III and IV, and draw upwards into Quadrant IV so that the end point is coincident with the rightmost point of the lowest cross-section Sketch in the YZ-plane<br>[[File:Macro_Half-Hull_ModelUsage4.jpg]]
#start on the Y axis between quadrant III and IV, and draw upwards into Quadrant IV so that the end point is coincident with the rightmost point of the lowest cross-section Sketch in the YZ-plane<br />[[File:Macro_Half-Hull_ModelUsage4.jpg]]
#use the same number of line segments as in the stemline Sketch
#use the same number of line segments as in the stemline Sketch
# save sketch [[Image:Sketcher_LeaveSketch.svg|24px]]
# save sketch [[Image:Sketcher_LeaveSketch.svg|24px]]
#for ease of identification it is probably worth naming the Sketch something like "transom sketch"
#for ease of identification it is probably worth naming the Sketch something like "transom sketch"


<!--T:53-->
'''Save the New Document'''
'''Save the New Document'''


<!--T:54-->
Now save the document [[Image:Document-save.svg|24px]] which contains the Sketches that will define the hull shape, giving it some name that is descriptive
Now save the document [[Image:Document-save.svg|24px]] which contains the Sketches that will define the hull shape, giving it some name that is descriptive


<!--T:55-->
Once the Sketches have all been created and positioned, the document should look like this from the top view [[Image:View-top.svg|24px]]:
Once the Sketches have all been created and positioned, the document should look like this from the top view [[Image:View-top.svg|24px]]:


<!--T:56-->
[[File:Macro_Half-Hull_ModelUsage5.jpg]]
[[File:Macro_Half-Hull_ModelUsage5.jpg]]


<!--T:57-->
The principal limitations for constructing the model are:
The principal limitations for constructing the model are:
* the stemline bottom should end at (0,0)
* the stemline bottom should end at (0,0)
* the bottom centremost end of each cross-section should end at the Y axis - note that it can have any Z value
* the bottom centremost end of each cross-section should end at the Y axis - note that it can have any Z value


<!--T:58-->
This concludes the first step which is one of creating the data which the macro will use to create both half-hulls and complete-hulls. The second step is described in the following section which is titled User Interface
This concludes the first step which is one of creating the data which the macro will use to create both half-hulls and complete-hulls. The second step is described in the following section which is titled User Interface


==User Interface==
==User Interface== <!--T:59-->


<!--T:60-->
In this step the macro will gather some information from the user and then process the input Sketches to generate the desired hull models. This is the only GUI image for the macro and is primarily configuration details for the production of the hull models from the Sketches:
In this step the macro will gather some information from the user and then process the input Sketches to generate the desired hull models. This is the only GUI image for the macro and is primarily configuration details for the production of the hull models from the Sketches:


<!--T:61-->
[[File:Macro Half-Hull ModelScreenSnapshot2.jpg]]
[[File:Macro Half-Hull ModelScreenSnapshot2.jpg]]


<!--T:62-->
The choices on the GUI window are:
The choices on the GUI window are:


<!--T:63-->
* Starboard half-hull<br>- checking this will cause the macro to produce a starboard half-hull model
**Mounting plaque<br>- if checked the macro will mount the half-hull on a plaque
* Starboard half-hull<br />- checking this will cause the macro to produce a starboard half-hull model
***Allow space for keel<br>- if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
**Mounting plaque<br />- if checked the macro will mount the half-hull on a plaque
***Allow space for keel<br />- if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
* Port half-hull<br>- checking this will cause the macro to produce a port half-hull model
** Mounting plaque<br>- if checked the macro will mount the half-hull on a plaque
* Port half-hull<br />- checking this will cause the macro to produce a port half-hull model
***Allow space for keel<br>- if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
** Mounting plaque<br />- if checked the macro will mount the half-hull on a plaque
***Allow space for keel<br />- if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
* Complete hull<br>- checking this will cause the macro to produce a complete model
** Bottle for complete hull<br>- if checked the macro will place the complete hull inside a transparent bottle (complete with cork)
* Complete hull<br />- checking this will cause the macro to produce a complete model
***Allow space for keel<br>- if checked will cause the half hull to be positioned higher in the bottle than the centre position, this is so a separately generated keel could be placed below the hull
** Bottle for complete hull<br />- if checked the macro will place the complete hull inside a transparent bottle (complete with cork)
* Bulkheads for flush deck<br>- checking this will cause the macro to produce bulkheads whose tops are level with the top of the hull, bulkheads will not be generated for the most forward 2 cross-sections or the aft-most 2 cross-sections
***Allow space for keel<br />- if checked will cause the half hull to be positioned higher in the bottle than the centre position, this is so a separately generated keel could be placed below the hull
* Bulkheads for coachhouse<br>- checking this will cause the macro to produce bulkheads whose tops are possibly above the top of the hull.
* Bulkheads for flush deck<br />- checking this will cause the macro to produce bulkheads whose tops are level with the top of the hull, bulkheads will not be generated for the most forward 2 cross-sections or the aft-most 2 cross-sections
* Bulkheads for coachhouse<br />- checking this will cause the macro to produce bulkheads whose tops are possibly above the top of the hull.
** Bulkheads to skip at bow determines how many cross-sections will be left without a bulkhead at the bow
** Bulkheads to skip at bow determines how many cross-sections will be left without a bulkhead at the bow
** Bulkheads to skip at stern determines how many cross-sections will be left without a bulkhead at the stern
** Bulkheads to skip at stern determines how many cross-sections will be left without a bulkhead at the stern
* The dimensions of the top of the bulkheads will be configured as per the following diagram:<br>[[File:Macro_Half-Hull_ModelUi1.jpg]]
* The dimensions of the top of the bulkheads will be configured as per the following diagram:<br />[[File:Macro_Half-Hull_ModelUi1.jpg]]
* Cancel button<br>- the execution is halted and the window closes
* Cancel button<br />- the execution is halted and the window closes
* Re-Use Last File button<br>- the execution uses the data file AND SETTINGS from the the last run, any changes to settings are ignored
* Re-Use Last File button<br />- the execution uses the data file AND SETTINGS from the the last run, any changes to settings are ignored
* Select File button<br>- the standard Open File window is opened where the user can either select a file or Cancel and exit
* Select File button<br />- the standard Open File window is opened where the user can either select a file or Cancel and exit


<!--T:64-->
When the macro runs it takes configuring data from the user and then reads Sketcher sketches in the selected input file.
When the macro runs it takes configuring data from the user and then reads Sketcher sketches in the selected input file.


<!--T:65-->
<b>Note</b>: As the macro works through the Sketches it prints out any exceptions as well as some milestones on the Report View. If you get unexpected results or some parts are missing, that is probably the first place to check.
<b>Note</b>: As the macro works through the Sketches it prints out any exceptions as well as some milestones on the Report View. If you get unexpected results or some parts are missing, that is probably the first place to check.


==Options==
==Options== <!--T:66-->
There are various types of bows and sterns for boats, with sterns having much more variety than bows. Here are examples of transoms and bows from the real world alongside the similar macro output:
There are various types of bows and sterns for boats, with sterns having much more variety than bows. Here are examples of transoms and bows from the real world alongside the similar macro output:


<!--T:67-->
'''Sheer Stern'''
'''Sheer Stern'''


<!--T:68-->
Probably the most common stern, typical of all sizes of vessels from commercial ships through to rowing boats.
Probably the most common stern, typical of all sizes of vessels from commercial ships through to rowing boats.


<!--T:69-->
<table style="width: 500pix" border="0">
<table style="width: 500pix" border="0">
<tr>
<tr>
Line 229: Line 306:
* the XY transom should be as close to the aftmost cross-section as is possible.
* the XY transom should be as close to the aftmost cross-section as is possible.


<!--T:70-->
'''Sugar-Scoop Stern'''
'''Sugar-Scoop Stern'''


<!--T:71-->
Most likely to be found on a sailing yacht, it is a product of designing to maximise the length of the waterline to benefit from class rules for racing under sail.
Most likely to be found on a sailing yacht, it is a product of designing to maximise the length of the waterline to benefit from class rules for racing under sail.


<!--T:72-->
<table style="width: 500pix" border="0">
<table style="width: 500pix" border="0">
<tr>
<tr>
Line 241: Line 321:
* place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees (or whatever is called for) around the X-axis
* place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees (or whatever is called for) around the X-axis


<!--T:73-->
'''Canoe Stern'''
'''Canoe Stern'''


<!--T:74-->
Found on all sizes of water craft, power and sail, pleasure and commercial.
Found on all sizes of water craft, power and sail, pleasure and commercial.


<!--T:75-->
<table style="width: 500pix" border="0">
<table style="width: 500pix" border="0">
<tr>
<tr>
Line 253: Line 336:
* place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees around the X-axis
* place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees around the X-axis


<!--T:76-->
'''Normal Bow'''
'''Normal Bow'''


<!--T:77-->
There is a lot less variety in bow shapes than with transoms:
There is a lot less variety in bow shapes than with transoms:


<!--T:78-->
<table style="width: 500pix" border="0">
<table style="width: 500pix" border="0">
<tr>
<tr>
Line 264: Line 350:
</table>
</table>


<!--T:79-->
'''Trireme Bow'''
'''Trireme Bow'''


<!--T:80-->
Although not seen very frequently in the last 2 millenia, this was once the definitive bow profile for war-making vessels:
Although not seen very frequently in the last 2 millenia, this was once the definitive bow profile for war-making vessels:


<!--T:81-->
<table style="width: 500pix" border="0">
<table style="width: 500pix" border="0">
<tr>
<tr>
Line 276: Line 365:
* in order for the bow to be correct the poly-line for the stemline needs to be drawn from the bottom to the top which will mean right to left in the Sketcher
* in order for the bow to be correct the poly-line for the stemline needs to be drawn from the bottom to the top which will mean right to left in the Sketcher


==Sample Files==
==Sample Files== <!--T:82-->
These files are samples of Sketch data to use with the macro, mainly they are the models for the screen snapshots in the Options section above. The files work with the Macro and so can be downloaded and played with to adapt to your specific requirements. The prefix of 5x3 (for example) means the model has 5 cross-sections and 3 line segments per sketch
These files are samples of Sketch data to use with the macro, mainly they are the models for the screen snapshots in the Options section above. The files work with the Macro and so can be downloaded and played with to adapt to your specific requirements. The prefix of 5x3 (for example) means the model has 5 cross-sections and 3 line segments per cross-section (i.e. sketch)


<!--T:84-->
* 3x3 hull with the minimum number of Sketches (stemline, one cross-section, transom) and 3 line segments per Sketch
To use one of the example files, right-click on the file link and select Save File As... from the menu. The filename will be specified, choose the desired folder/directory to hold the example file.
* 5x3 with sheer transom
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/3x1.FCStd?raw=true| 3x1 hull] with the minimum number of Sketches (stemline, one cross-section, transom) with 1 line segment per Sketch <<<<< NOT CURRENTLY WORKING
* 5x3 with sugar scoop transom
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x3%20bowNormal.FCStd?raw=true| 5x3 with normal bow]
* 5x3 with canoe transom
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x3%20bowTrireme.FCStd?raw=true| 5x3 with trireme bow]
* 5x3 with trireme bow
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x3%20sternCanoeStern.FCStd?raw=true| 5x3 with canoe stern]
* 12x3 sailing yacht
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x3%20sternSheerTransom.FCStd?raw=true| 5x3 with sheer transom stern]
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x3%20sternSugarScoop.FCStd?raw=true| 5x3 with sugar scoop stern]
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/5x5%20workboat.FCStd?raw=true| 5x5 workboat]
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/7x5%20pirate.FCStd?raw=true| 7x5 pirate boat]
*[https://github.com/FreeCAD/Examples/raw/master/Half_Hull_Macro_ExampleFiles/12x3%20halfHullS.FCStd?raw=true| 12x3 sailing yacht]


==Remarks== <!--T:85-->

* almost all the examples on this page are generated with only 3 line segments defining the side of the hull which gives a very faceted appearance, increasing the number of segments in each Sketch would generate a much smoother surface which would increase the realism
* [https://github.com/piffpoof/trees/blob/master/3x1.FCStd sample file 3x1] GitHub multiple file repository
* [https://github.com/piffpoof/singleFile/blob/master/3x1.FCStd sample file 3x1] GitHub single file repository
* [https://www.dropbox.com/s/4mtd21g6x0tqgjf/3x1.FCStd?dl=0 sample file 3x1] DropBox repository
* files attached to forum posting (a la Mario)
* [[File:Macro_Half-Hull_ModelExample6.FCStd]] - doesn't work

* Gitorious
* GitLab
* BitBucket
* SourceForge

==Remarks==
* almost all the examples on this page are generated with only 3 line segments defining the side of the hull which gives a very faceted appearance, increasing the number of segments in each Sketch would generate a much smoother surface which would increase the realsism
* doesn't do keels, skegs or rudders, in other words, it doesn't do any of the wet area
* doesn't do keels, skegs or rudders, in other words, it doesn't do any of the wet area
* doesn't do square bows like push-boats or towed barges
* doesn't do square bows like push-boats or towed barges
* doesn't do submarines (although it will do the lower half of a submarine)
* doesn't do submarines (although it will do the lower half of a submarine)


==Known Problems==
==Known Problems== <!--T:86-->
The 'Ruled Surface' feature of FreeCAD is used to generate the hull sections from the Sketches. It can sometimes generate the wrong result and display a grater like surface instead of a smooth planar one. This will typically occur when the Sketches are rotated such as when a Sugar Scoop stern is modeled. Also angling a Sheer Transom stern can cause this. If it occurs then typically it will do so in either the half-hull models or the complete hull model - it never seems to occur in all three models for the same hull. Also it usually only happens at the extreme bow or stern. If it happens to section in the middle of the boat then most likely one of the Sketches was drawn in the wrong direction (i.e. either random sequence or top-down where as all lines should be drawn bottom-up)
The 'Ruled Surface' feature of FreeCAD is used to generate the hull sections from the Sketches. It can sometimes generate the wrong result and display a grater like surface instead of a smooth planar one. This will typically occur when the Sketches are rotated such as when a Sugar Scoop stern is modeled. Also angling a Sheer Transom stern can cause this. If it occurs then typically it will do so in either the half-hull models or the complete hull model - it never seems to occur in all three models for the same hull. Also it usually only happens at the extreme bow or stern. If it happens to section in the middle of the boat then most likely one of the Sketches was drawn in the wrong direction (i.e. either random sequence or top-down where as all lines should be drawn bottom-up)


<!--T:87-->
It can usually be removed by using the following steps:
It can usually be removed by using the following steps:
* in the Model tab of the Combo View, click on the faulty segment to select it, the faulty segment will show as highlighted on the display
* in the Model tab of the Combo View, click on the faulty segment to select it, the faulty segment will show as highlighted on the display
Line 313: Line 396:
* try one of the other settings (remembering the faulty segment must still be selected in the upper part of the Combo View) which will usually correct the problem
* try one of the other settings (remembering the faulty segment must still be selected in the upper part of the Combo View) which will usually correct the problem


<!--T:88-->
The following screen snapshot shows the relevant portion of the screen:
The following screen snapshot shows the relevant portion of the screen:


<!--T:89-->
[[File:Macro_Half-Hull_ModelKnownProblems1.jpg]]
[[File:Macro_Half-Hull_ModelKnownProblems1.jpg]]


==Future Possibilities==
==Future Possibilities== <!--T:90-->


<!--T:91-->
* replace line segments of cross-sections with curved lines<br>- this is just at the idea stage but would give a much smoother surface in the vertical dimension, however the horizontal surface would still be faceted as it is now)
* replace line segments of cross-sections with curved lines<br />- this is just at the idea stage but would give a much smoother surface in the vertical dimension, however the horizontal surface would still be faceted as it is now
* integrate with Draft workbench to produce drawings from models<br>- an initial goal, but the feasibility has not been investigated
* integrate with Draft workbench to produce drawings from models<br />- an initial goal, but the feasibility has not been investigated
* handle keels, skegs and rudders<br>- one work around for keels with the present system is to model the keel as a half-hull on it's own and then assemble it onto the bottom of the main hull; this would still do nothing for rudders and skegs though
* handle keels, skegs and rudders<br />- one work around for keels with the present system is to model the keel as a half-hull on it's own and then assemble it onto the bottom of the main hull; this would still do nothing for rudders and skegs though


==Glossary==
==Glossary== <!--T:92-->
As with any ancient and practiced trade, a rich and sometimes confusing vocabulary has developed around ships, boats and nautical practices. In describing this macro it is both awkward and inefficient to describe the process without using the correct and accurate terms. The obvious problem is that the average lay person will be unfamiliar with such terminology, hence this vocabulary:
As with any ancient and practiced trade, a rich and sometimes confusing vocabulary has developed around ships, boats and nautical practices. In describing this macro it is both awkward and inefficient to describe the process without using the correct and accurate terms. The obvious problem is that the average lay person will be unfamiliar with such terminology, hence this vocabulary:
<table style="width: 100%;" border="0">
<table style="width: 100%;" border="0">
Line 333: Line 419:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 344: Line 430:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 356: Line 442:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 368: Line 454:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 380: Line 466:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 392: Line 478:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 404: Line 490:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 416: Line 502:
</tr>
</tr>
<tr>
<tr>
<td><br>
<td><br />
</td>
</td>
<td><br>
<td><br />
</td>
</td>
</tr>
</tr>
Line 429: Line 515:
</table>
</table>


==Links==
==Links== <!--T:93-->
* [http://en.wikipedia.org/wiki/Half_hull_model_ship Half Hull Model Ship] (Wikipedia)
* [http://en.wikipedia.org/wiki/Half_hull_model_ship Half Hull Model Ship] (Wikipedia)
* [http://www.halfhullshipmodels.com/smf/discussions-of-half-hulls-and-ship-models/understanding-and-practising-half-hull-boat-modelling/?PHPSESSID=56fedd9f6fa2db453a77347f10d57b7f Why Half Hulls] (Maritime Half Hull Ship Models and Nautical Art website)
* [http://www.halfhullshipmodels.com/smf/discussions-of-half-hulls-and-ship-models/understanding-and-practising-half-hull-boat-modelling/?PHPSESSID=56fedd9f6fa2db453a77347f10d57b7f Why Half Hulls] (Maritime Half Hull Ship Models and Nautical Art website)
* [http://pages.swcp.com/usvmyg/design/design.htm Traditional Model Yacht Design] (US Vintage Model Yacht Group)
* [http://pages.swcp.com/usvmyg/design/design.htm Traditional Model Yacht Design] (US Vintage Model Yacht Group)


==Script==
==Script== <!--T:94-->
</translate>
This code is running bug free. But due to the large range of possible inputs it may fail for some inputs. If so please report it.
ToolBar Icon [[Image:Macro_Half_Hull_Model.png]]
{{Code|code=
#
#
# Half Hull
#
#
################################


'''Macro_Half_Hull_Model.FCMacro'''
import FreeCAD
<translate>
from FreeCAD import Base, Draft
import Part, PartGui, sys, math, collections
from collections import OrderedDict
from os.path import expanduser # default input directory
from PySide import QtGui, QtCore


<!--T:98-->
# UI Class definitions
This script is running bug free. But due to the large range of possible inputs it may fail for some inputs. If so please report it.


<!--T:96-->
class ConfigParams:
The script is too long for the Wiki to display so it must be copied or downloaded from [http://pastebin.com/raw.php?i=tZMpUi6F unabbreviated script on pastebin.com]
"""carrier for the user selection parameters"""
</translate>
def __init__(self):
self.result = None
self.cb1a = None
self.cb1b = None
self.cb1c = None
self.cb2a = None
self.cb2b = None
self.cb2c = None
self.cb3a = None
self.cb3b = None
self.cb3c = None
self.cb4a = None
self.rb4b = None
self.rb5b = None
self.skipAtBow = None
self.skipAtStern = None
self.deckWidth = None
self.deckThrow = None
self.coachhouseRise = None
self.coachhouseIncline = None
self.documentFileName = None


{{clear}}
class GetConfigParams(QtGui.QDialog):
""""""
def __init__(self):
super(GetConfigParams, self).__init__()
self.initUI()
def initUI(self):
self.configParams = ConfigParams()
# set default return value
self.configParams.result = userCancelled
# set default values
skipAtBowDefault = str(2)
skipAtSternDefault = str(2)
deckWidthDefault = str(50)
deckThrowDefault = str(2)
coachhouseRiseDefault = str(50)
coachhouseInclineDefault = str(8)
# field descriptors
self.promptLbl = QtGui.QLabel("Please Choose Options:", self)
self.promptLbl.move(20, 20)
# checkboxes - define first so signals can be set up
self.cb1a = QtGui.QCheckBox("Starboard half-hull", self)
self.cb1b = QtGui.QCheckBox("Mounting plaque", self)
self.cb1c = QtGui.QCheckBox("Allow space for keel", self)
self.cb2a = QtGui.QCheckBox("Port half-hull", self)
self.cb2b = QtGui.QCheckBox("Mounting plaque", self)
self.cb2c = QtGui.QCheckBox("Allow space for keel", self)
self.cb3a = QtGui.QCheckBox("Complete hull", self)
self.cb3b = QtGui.QCheckBox("Bottle for complete hull", self)
self.cb3c = QtGui.QCheckBox("Allow space for keel", self)
self.rb4b = QtGui.QRadioButton("Bulkheads for flush deck",self)
self.rb5b = QtGui.QRadioButton("Bulkheads for coachhouse",self)
#
self.cb1a.clicked.connect(self.onCb1a)
self.cb1a.toggle() # set default value
self.cb1c.setEnabled(False)
#self.cb1a.stateChanged.connect(self.onCb1a)
self.cb1a.move(20,50)
#
self.cb1b.clicked.connect(self.onCb1b)
self.cb1b.move(250,50)
#
self.cb1c.clicked.connect(self.onCb1c)
self.cb1c.move(450,50)
#
self.cb2a.clicked.connect(self.onCb2a)
self.cb2b.setEnabled(False)
self.cb2c.setEnabled(False)
self.cb2a.move(20,80)
#
self.cb2b.clicked.connect(self.onCb2b)
self.cb2b.move(250,80)
#
self.cb2c.clicked.connect(self.onCb2c)
self.cb2c.move(450,80)
#
self.cb3a.clicked.connect(self.onCb3a)
self.cb3b.setEnabled(False)
self.cb3c.setEnabled(False)
self.cb3a.move(20,110)
#
self.cb3b.clicked.connect(self.onCb3b)
self.cb3b.move(250,110)
#
self.cb3c.clicked.connect(self.onCb3c)
self.cb3c.move(450,110)
#
self.cb4a = QtGui.QCheckBox("Bulkheads for complete hull", self)
self.cb4a.clicked.connect(self.onCb4a)
self.rb4b.setEnabled(False)
self.rb5b.setEnabled(False)
#self.hideCoachhouseFields(True) # grey out coachhouse fields
self.cb4a.move(20,140)
# radio buttons
self.rb4b.move(250,140)
self.rb4b.clicked.connect(self.onRb4b)
#
self.rb5b.move(250,170)
self.rb5b.clicked.connect(self.onRb5b)
#
self.skipAtBowLabel = QtGui.QLabel("Cross-sections to skip at bow:", self)
self.skipAtBowLabel.move(270, 200)
self.skipAtBow = 0
#
self.skipAtSternLabel = QtGui.QLabel("Cross-sections to skip at stern:", self)
self.skipAtSternLabel.move(270, 230)
self.skipAtStern = 0
#
self.deckWidthLabel = QtGui.QLabel("Deck Width:", self)
self.deckWidthLabel.move(270, 260)
self.deckWidth = QtGui.QLineEdit(self)
self.deckWidth.setInputMask("999")
self.deckWidth.setText(deckWidthDefault)
self.deckWidth.setFixedWidth(35)
self.deckWidth.move(493, 260)
#
self.deckThrowLabel = QtGui.QLabel("Deck throw:", self)
self.deckThrowLabel.move(270, 290)
self.deckThrow = QtGui.QLineEdit(self)
self.deckThrow.setInputMask("999")
self.deckThrow.setText(deckThrowDefault)
self.deckThrow.setFixedWidth(35)
self.deckThrow.move(493, 290)
#
self.coachhouseRiseLabel = QtGui.QLabel("Coachhouse Rise:", self)
self.coachhouseRiseLabel.move(270, 320)
self.coachhouseRise = QtGui.QLineEdit(self)
self.coachhouseRise.setInputMask("999")
self.coachhouseRise.setText(coachhouseRiseDefault)
self.coachhouseRise.setFixedWidth(35)
self.coachhouseRise.move(493, 320)
#
self.coachhouseInclineLabel = QtGui.QLabel("Coachhouse Incline:", self)
self.coachhouseInclineLabel.move(270, 350)
self.coachhouseIncline = QtGui.QLineEdit(self)
self.coachhouseIncline.setInputMask("999")
self.coachhouseIncline.setText(coachhouseInclineDefault)
self.coachhouseIncline.setFixedWidth(35)
self.coachhouseIncline.move(493, 350)
# set up lists for pop-ups
self.popupItemsB = OrderedDict([("2",""),("3",""),("4",""),("5",""),("6",""),("7",""),("8",""),("9",""),("10","")])
self.popupItemsS = OrderedDict([("1",""),("2",""),("3",""),("4",""),("5",""),("6",""),("7",""),("8",""),("9",""),("10","")])
# set up pop-up menu of bulkheads to skip bulkheads at bow
self.skipAtBowPop = QtGui.QComboBox(self)
self.skipAtBowPop.addItems(self.popupItemsB.keys())
self.skipAtBowPop.setCurrentIndex(self.popupItemsB.keys().index(skipAtBowDefault))
self.skipAtBow = skipAtBowDefault
self.skipAtBowPop.move(490, 200)
self.skipAtBowPop.activated[str].connect(self.onSkipBowActivated)
# set up pop-up menu of bulkheads to skip bulkheads at stern
self.skipAtSternPop = QtGui.QComboBox(self)
self.skipAtSternPop.addItems(self.popupItemsS.keys())
self.skipAtSternPop.setCurrentIndex(self.popupItemsS.keys().index(skipAtSternDefault))
self.skipAtStern = skipAtSternDefault
self.skipAtSternPop.move(490, 230)
self.skipAtSternPop.activated[str].connect(self.onSkipSternActivated)
# cancel button
cancelButton = QtGui.QPushButton('Cancel', self)
cancelButton.clicked.connect(self.onCancel)
cancelButton.move(260, 390)
# last used button
lastFileButton = QtGui.QPushButton('Re-use last file', self)
lastFileButton.clicked.connect(self.onLastFile)
lastFileButton.move(345, 390)
# OK button
sfButton = QtGui.QPushButton('Select File', self)
sfButton.clicked.connect(self.onSf)
sfButton.move(480, 390)
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 630, 445)
self.setWindowTitle("Macro Configuration")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.disableCoachhouseFields(True) # grey out coachhouse fields
self.show()
#
def onCb1a(self):
if self.cb1a.isChecked():
self.cb1b.setEnabled(True)
else:
self.cb1b.setEnabled(False)
self.cb1b.setChecked(False)
self.cb1c.setEnabled(False)
self.cb1c.setChecked(False)
def onCb1b(self):
if self.cb1b.isChecked():
self.cb1c.setEnabled(True)
else:
self.cb1c.setEnabled(False)
self.cb1c.setChecked(False)
def onCb1c(self):
pass
def onCb2a(self):
if self.cb2a.isChecked():
self.cb2b.setEnabled(True)
else:
self.cb2b.setEnabled(False)
self.cb2b.setChecked(False)
self.cb2c.setEnabled(False)
self.cb2c.setChecked(False)
def onCb2b(self):
if self.cb2b.isChecked():
self.cb2c.setEnabled(True)
else:
self.cb2c.setEnabled(False)
self.cb2c.setChecked(False)
def onCb2c(self):
pass
def onCb3a(self):
if self.cb3a.isChecked():
self.cb3b.setEnabled(True)
else:
self.cb3b.setEnabled(False)
self.cb3b.setChecked(False)
self.cb3c.setEnabled(False)
self.cb3c.setChecked(False)
def onCb3b(self):
if self.cb3b.isChecked():
self.cb3c.setEnabled(True)
else:
self.cb3c.setEnabled(False)
self.cb3c.setChecked(False)
def onCb3c(self):
pass
def onCb4a(self):
if self.cb4a.isChecked():
self.rb4b.setEnabled(True)
self.rb4b.setChecked(True)
self.rb5b.setEnabled(True)
self.rb5b.setChecked(False)
else:
self.rb4b.setChecked(False)
self.rb4b.setEnabled(False)
self.rb5b.setChecked(False)
self.rb5b.setEnabled(False)
self.disableCoachhouseFields(True)
def onRb4b(self):
if self.rb4b.isChecked():
self.disableCoachhouseFields(True)
else:
self.disableCoachhouseFields(False)
def onRb5b(self):
if self.rb5b.isChecked():
self.disableCoachhouseFields(False)
else:
self.disableCoachhouseFields(True)
def onSkipBowActivated(self, text):
self.skipAtBow = text
def onSkipSternActivated(self, text):
self.skipAtStern = text
def disableCoachhouseFields(self, aFlag):
# enable or disable coachhouse parameter fields
if aFlag:
self.skipAtBowLabel.setEnabled(False)
self.skipAtBowPop.setEnabled(False)
self.skipAtSternLabel.setEnabled(False)
self.skipAtSternPop.setEnabled(False)
self.deckWidthLabel.setEnabled(False)
self.deckWidth.setEnabled(False)
self.deckThrowLabel.setEnabled(False)
self.deckThrow.setEnabled(False)
self.coachhouseRiseLabel.setEnabled(False)
self.coachhouseRise.setEnabled(False)
self.coachhouseInclineLabel.setEnabled(False)
self.coachhouseIncline.setEnabled(False)
else:
self.skipAtBowLabel.setEnabled(True)
self.skipAtBowPop.setEnabled(True)
self.skipAtSternLabel.setEnabled(True)
self.skipAtSternPop.setEnabled(True)
self.deckWidthLabel.setEnabled(True)
self.deckWidth.setEnabled(True)
self.deckThrowLabel.setEnabled(True)
self.deckThrow.setEnabled(True)
self.coachhouseRiseLabel.setEnabled(True)
self.coachhouseRise.setEnabled(True)
self.coachhouseInclineLabel.setEnabled(True)
self.coachhouseIncline.setEnabled(True)
def onCancel(self):
self.configParams.result = userCancelled
self.close()
def transferConfigParams(self):
self.configParams.cb1a = self.cb1a.isChecked()
self.configParams.cb1b = self.cb1b.isChecked()
self.configParams.cb1c = self.cb1c.isChecked()
self.configParams.cb2a = self.cb2a.isChecked()
self.configParams.cb2b = self.cb2b.isChecked()
self.configParams.cb2c = self.cb2c.isChecked()
self.configParams.cb3a = self.cb3a.isChecked()
self.configParams.cb3b = self.cb3b.isChecked()
self.configParams.cb3c = self.cb3c.isChecked()
self.configParams.cb4a = self.cb4a.isChecked()
self.configParams.rb5b = self.rb5b.isChecked()
self.configParams.skipAtBow = self.skipAtBow
self.configParams.skipAtStern = self.skipAtStern
self.configParams.deckWidth = self.deckWidth
self.configParams.deckThrow = self.deckThrow
self.configParams.coachhouseRise = self.coachhouseRise
self.configParams.coachhouseIncline = self.coachhouseIncline
def onLastFile(self):
self.configParams.result = userLastFile
self.transferConfigParams()
self.close()
def onSf(self):
self.configParams.result = userOK
self.transferConfigParams()
self.close()

# Class definitions

class HullCrossSection:
"Holder of information pertaining to a cross section profile"
#persistentInstance = ""
#import copy
def __init__(self,aSketch):
self.sketch = aSketch
self.geometryCount = aSketch.GeometryCount
self.geometryS = None # geometry for starboard side
self.geometryP = None # geometry for port side
self.geometryC = None # geometry for complete hull (i.e. both halves as one)
self.label = aSketch.Label
# next 2 lines due to mysterious label morphing routine of FreeCAD
self.altLabel = self.label.replace(" ", "_")
self.altLabel = self.altLabel.replace("-", "_")
#
self.xPos = 0.0 # normalise sketch to axis
self.yPos = aSketch.Placement.Base.y
self.zPos = aSketch.Placement.Base.z
self.xMin = infinity # will hold min X value in Sketch
self.yMin = infinity # will hold min Y value in Sketch
self.xMax = infinityNegative # will hold max X value in Sketch
self.yMax = infinityNegative # will hold max Y value in Sketch
self.endPoint = None # will be the 'top' point on the polyline
self.key = int(self.yPos)
self.rotation = aSketch.Placement.Rotation
self.rotAngle = aSketch.Placement.Rotation.Angle
self.rotAxis = aSketch.Placement.Rotation.Axis
self.rotQ = aSketch.Placement.Rotation.Q
# following 4 statements seem necessary to pass the Rotation quad-value
self.rotQ1 = aSketch.Placement.Rotation.Q[0]
self.rotQ2 = aSketch.Placement.Rotation.Q[1]
self.rotQ3 = aSketch.Placement.Rotation.Q[2]
self.rotQ4 = aSketch.Placement.Rotation.Q[3]
# set flags for either stemline or transom or suspected transom cross-section
if eqRotation(self.rotation,yzPlane):
# if we have the stemline then wait to give it the foremost placement
FreeCAD.Console.PrintMessage("Stemline identified '" + self.label + "'\n")
self.stemlineFlag = True
else:
self.stemlineFlag = False
if eqRotation(self.rotation,xyPlane):
# if we have the transom then wait to give it the aftmost placement
FreeCAD.Console.PrintMessage("Transom identified '" + self.label + "'\n")
self.transomFlag = True
else:
self.transomFlag = False
if eqRotation(self.rotation,xzPlane):
# the most numerous sketches will be the cross-sections, so don't flag it
self.possibleTransomCS = False
else:
# it's not lying in any of the 3 planes so it's either an error
# or it could be an inclined cross-section for the transom
# (although there should only be one or none of these)
# flag it as such and sort it out later once all the other
# cross-sections are placed
if not (self.stemlineFlag or self.transomFlag):
FreeCAD.Console.PrintMessage("Possible Transom cross-section identified '" + self.label + "'\n")
self.possibleTransomCS = True
self.defineGeometries() # make S & P & C geometries from the geometry of the supplied Sketch
def defineGeometries(self):
# the supplied geometry is for the starboard side and is part of the user supplied Sketch
# - make a direct copy for the starboard half-hull
# - negate the X coordinates for the Port side
# - append a negated reversed copy to each starboard piece for the complete hull
self.geometryS = list()
self.geometryP = list()
self.geometryC = list()
#grab the endPoint which will be used for covering the half-hull model
epX = max(self.sketch.Geometry[-1].StartPoint.x, self.sketch.Geometry[-1].EndPoint.x)
epY = self.yPos
epZ = max(self.sketch.Geometry[-1].StartPoint.y, self.sketch.Geometry[-1].EndPoint.y)
self.endPoint = Base.Vector(epX,epY,epZ)
# first pass through segment of sketch is to determine the min and max X & Y values
for seg in self.sketch.Geometry:
# determine the minimum X & Y values
self.xMin = min(self.xMin, seg.StartPoint.x, seg.EndPoint.x)
self.yMin = min(self.yMin, seg.StartPoint.y, seg.EndPoint.y)
self.xMax = max(self.xMax, seg.StartPoint.x, seg.EndPoint.x)
self.yMax = max(self.yMax, seg.StartPoint.y, seg.EndPoint.y)
# second pass is to create the S, P and starboard side of the C geometries
for seg in self.sketch.Geometry:
# extract the X,Y,Z for start and end
segStartX = seg.StartPoint.x
segStartY = seg.StartPoint.y
segStartZ = seg.StartPoint.z
segEndX = seg.EndPoint.x
segEndY = seg.EndPoint.y
segEndZ = seg.EndPoint.z
absMinX = abs(self.xMin)
# normalise segments within drawing to X axis if not stemline
if not self.stemlineFlag:
if abs(segStartX) == absMinX:
if 0<absMinX<1:
segStartX = 0
elif absMinX >= 1:
FreeCAD.Console.PrintMessage("Move to Y axis of '" + self.label + "'\n")
if segStartX<0:
segStartX = segStartX + absMinX
elif segEndX>0:
segStartX = segStartX - absMinX
if abs(segEndX) == absMinX:
if 0<absMinX<1:
segEndX = 0
elif absMinX >= 1:
FreeCAD.Console.PrintMessage("Move to Y axis of '" + self.label + "'\n")
if segEndX<0:
segEndX = segEndX + absMinX
elif segEndX>0:
segEndX = segEndX - absMinX
# now create starboard, port, complete geometries
self.geometryS.append(Part.Line(
Base.Vector(segStartX, segStartY, segStartZ),
Base.Vector(segEndX, segEndY, segEndZ)))
if self.stemlineFlag:
# stemline is on the YZ axis and is common to both half-hulls
# so don't flip it's X coordinates
multiplicand = 1
else:
multiplicand = -1
self.geometryP.append(Part.Line(
Base.Vector(segStartX*multiplicand, segStartY, segStartZ),
Base.Vector(segEndX*multiplicand, segEndY, segEndZ)))
# starboard geometry is first half of complete hull geometry
self.geometryC.append(self.geometryS[-1])
# third pass is to create the Complete geometry so the segments have
# constant direction from starboard to port:
# 1) reverse segment order of starboard side
# 2 copy port side geometry
segCnt = len(self.sketch.Geometry)
completeGeometry = list()
for i in range(segCnt):
completeGeometry.append(self.reverseLineDirection(self.geometryS[segCnt-1-i]))
for i in range(segCnt):
completeGeometry.append(self.reverseLineDirection(self.geometryP[i]))
self.geometryC = completeGeometry
def reverseLineDirection(self,aLine):
return Part.Line(aLine.EndPoint, aLine.StartPoint)

# Function definitions

def createBottle(aKeelFlag):
# create a bottle around the hull
bottleRadius = 250
neckRadius = 75
bottleBottom = -1000
bottleTop = 400
neckBottom = 800
neckTop = 1150
corkHeight = 100
# get some dimensions for the plaque based on the size of the hull or half-hull
minusX = crossSections[-2].yPos * 1.1
plusX = (crossSections[0].yPos + crossSections[0].yMax) * 1.1
minusY = 0; plusY = 0
for cs in crossSections:
#minusY = max(minusY, abs(cs.yMin))
plusY = max(plusY, cs.yMax)
minusY = plusY * -0.75
plusY = plusY * 1.1
#print minusX, " ", plusX, " ", minusY, " ", plusY
bs0 = FreeCAD.ActiveDocument.addObject("Part::Vertex","Ring0")
bs0.Label='Ring0'
bs0.X=0.00
bs0.Y=bottleBottom*0.97
bs0.Z=0.00
bs0.Placement = Base.Placement( Base.Vector(0.00,0.00,0.00),
Base.Rotation(0.00,0.00,0.00,1.00))
bs0.ViewObject.Visibility=False
#
bs1 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring1')
bs1.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), bottleRadius))
bs1.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,bottleBottom,0.0),
FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
#
bs2 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring2')
bs2.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), bottleRadius))
bs2.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,bottleTop,0.0),
FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
#
bs3 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring3')
bs3.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), neckRadius))
bs3.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,neckBottom,0.0),
FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
#
bs4 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring4')
bs4.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), neckRadius))
bs4.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,neckTop,0.0),
FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
#
bot0 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft0')
bot0.Sections=[bs0, bs1,]
bot0.Solid=False; bot0.Ruled=False; bot0.Closed=False
#
bot1 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft1')
bot1.Sections=[bs1, bs2,]
bot1.Solid=False; bot1.Ruled=False; bot1.Closed=False
#
bot2 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft2')
bot2.Sections=[bs2, bs3,]
bot2.Solid=False; bot2.Ruled=False; bot2.Closed=False
#
bot3 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft3')
bot3.Sections=[bs3,bs4,]
bot3.Solid=False; bot3.Ruled=False; bot3.Closed=False
#
bottle = FreeCAD.activeDocument().addObject("Part::MultiFuse","Bottle")
bottle.Shapes = [bot0,bot1,bot2,bot3,]
bot1.ViewObject.Visibility=False
bot2.ViewObject.Visibility=False
bot3.ViewObject.Visibility=False
#bottle.ViewObject.ShapeColor=Gui.ActiveDocument.Loft1.ShapeColor
bottle.ViewObject.DisplayMode="Shaded"
bottle.ViewObject.Transparency=80
bottle.ViewObject.ShapeColor=(0.4, 0.8, 0.5, 0.0)
FreeCAD.ActiveDocument.recompute()
#
cork = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder")
cork.Label = "Cork"
cork.Radius = neckRadius-1
cork.Height = corkHeight
cork.Placement = FreeCAD.Placement( FreeCAD.Vector(0.0,neckTop-(corkHeight/2),0.0),
FreeCAD.Rotation(FreeCAD.Vector(1,0,0),-90))
cork.ViewObject.ShapeColor=(0.78, 0.65, 0.35, 0.0)
cork.ViewObject.DisplayMode = "Shaded"
#
FreeCADGui.activeDocument().activeView().viewAxometric()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCAD.ActiveDocument.recompute()

def createBulkheads(aDictionary):
userAction = None

docKey = findOpenDocumentName(outputWorkspaceB)
print docKey
if docKey!=None:
reply = QtGui.QMessageBox.question(None, "",
"The previous 'Bulkheads' output file is still open, close it",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
FreeCAD.closeDocument(outputWorkspaceB)
else:
userAction = userCancelled

if userAction!=userCancelled:
# bring in values from user dialogue
coachhouseFlag = aDictionary["coachhouseDeckBulkheadsFlag"]
forwardBulkheadsToSkip = aDictionary["forwardBulkheadsToSkip"]
aftBulkheadsToSkip = aDictionary["aftBulkheadsToSkip"]
deckWidth = aDictionary["deckWidth"]
deckThrow = aDictionary["deckThrow"]
coachhouseRise = aDictionary["coachhouseRise"]
coachhouseIncline = aDictionary["coachhouseIncline"]
# set up output workspaces
doc = FreeCAD.newDocument(outputWorkspaceB)
FreeCAD.setActiveDocument(outputWorkspaceB)
FreeCAD.ActiveDocument = FreeCAD.getDocument(outputWorkspaceB)
FreeCADGui.ActiveDocument = FreeCADGui.getDocument(outputWorkspaceB)
for i in range(forwardBulkheadsToSkip,len(crossSections)-aftBulkheadsToSkip):
# add new bulkhead
cs = crossSections[i]
newSketch = FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
#place bulkhead along keel
newSketch.Placement = FreeCAD.Placement(
FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
# insert geometry segments from both half-hulls plus bulkhead into new Sketch
for seg in cs.geometryC:
newSketch.addGeometry(
Part.Line(FreeCAD.Vector(seg.StartPoint.x,
seg.StartPoint.y, 0),
FreeCAD.Vector(seg.EndPoint.x,
seg.EndPoint.y,0)))
FreeCAD.ActiveDocument.recompute()
xMin = cs.xMax * -1
if coachhouseFlag:
# user wants a coachhouse bulkhead
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(xMin, cs.yMax, 0),
FreeCAD.Vector(xMin+deckWidth, cs.yMax+deckThrow, 0)))
FreeCAD.ActiveDocument.recompute()
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(xMin+deckWidth, cs.yMax+deckThrow, 0),
FreeCAD.Vector(xMin+deckWidth+coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0)))
FreeCAD.ActiveDocument.recompute()
#
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(xMin+deckWidth+coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0),
FreeCAD.Vector(cs.xMax-deckWidth-coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0)))
FreeCAD.ActiveDocument.recompute()
# focus at about -800
#newSketch.addGeometry(
# Part.ArcOfCircle(Part.Circle(App.Vector(0.0,-190,0),App.Vector(0,0,1),240.0),1.078868,2.064096))
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(cs.xMax-deckWidth-coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0),
FreeCAD.Vector(cs.xMax-deckWidth, cs.yMax+deckThrow, 0)))
FreeCAD.ActiveDocument.recompute()
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(cs.xMax-deckWidth, cs.yMax+deckThrow, 0),
FreeCAD.Vector(cs.xMax, cs.yMax, 0)))
else:
# generate bulkheads for flush deck
newSketch.addGeometry(
Part.Line( FreeCAD.Vector(xMin, cs.yMax, 0),
FreeCAD.Vector(cs.xMax, cs.yMax, 0)))
FreeCAD.ActiveDocument.recompute()
newPad = App.activeDocument().addObject("PartDesign::Pad","Bulkhead")
newPad.Sketch = newSketch
newPad.Length = 1.0
newPad.Sketch.ViewObject.hide()
FreeCAD.ActiveDocument.recompute()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCADGui.activeDocument().activeView().viewAxometric()

def createPlaque(aSideFlag, aKeelFlag):
# create plaque to mount half-hull on
# get some dimensions for the plaque based on the size of the hull or half-hull
# note: the X & Y in this routine are to do with the XY of the plaque, not the Sketches
woodColour = (0.53, 0.42, 0.23, 0.0)
# find the overall max & min for X & Y
minusY = crossSections[1].yMin; plusY = crossSections[1].yMin
# get the plaque's Y min & max for the cross-sections (not the stemline or transom)
for i in range(1,len(crossSections)-1):
minusY = min(minusY, crossSections[i].yMin)
plusY = max(plusY, crossSections[i].yMax)
# now allow for the extent of the stemline
minusY = min(minusY, crossSections[0].yMin)
plusY = max(plusY, crossSections[0].yMax)
# get extent of aftmost cross-section and add what the transom sticks out
minusX = crossSections[-2].yPos + crossSections[-1].yMin - crossSections[-1].yMax
# get the placement of the stemline and add what the stemline extends forward
plusX = crossSections[0].yPos + crossSections[0].xMax
# some scaling factors to provide margin space around the half-hull
minusX = minusX * 1.1
plusX = plusX * 1.1
minusY = minusY * 1.5
plusY = plusY * 1.25

ps = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','PlainPlaqueSketch')
ps.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,0.0,0.0),
FreeCAD.Rotation(0.5,0.5,0.5,0.5))
if aSideFlag==starboardSideFlag: ps.Placement.Base.x = -10
ps.addGeometry(Part.Line(FreeCAD.Vector(minusX,plusY,0),FreeCAD.Vector(plusX,plusY,0)))
ps.addGeometry(Part.Line(FreeCAD.Vector(plusX,plusY,0),FreeCAD.Vector(plusX,minusY,0)))
ps.addGeometry(Part.Line(FreeCAD.Vector(plusX,minusY,0),FreeCAD.Vector(minusX,minusY,0)))
ps.addGeometry(Part.Line(FreeCAD.Vector(minusX,minusY,0),FreeCAD.Vector(minusX,plusY,0)))
#
ps.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1))
ps.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1))
ps.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1))
ps.addConstraint(Sketcher.Constraint('Coincident',3,2,0,1))
ps.addConstraint(Sketcher.Constraint('Horizontal',0))
ps.addConstraint(Sketcher.Constraint('Horizontal',2))
ps.addConstraint(Sketcher.Constraint('Vertical',1))
ps.addConstraint(Sketcher.Constraint('Vertical',3))
FreeCAD.ActiveDocument.recompute()
#
pad = FreeCAD.activeDocument().addObject("PartDesign::Pad","PlainPlaquePad")
pad.Sketch = ps
pad.Length = 10.0
pad.Sketch.ViewObject.hide()
pad.ViewObject.ShapeColor = woodColour
FreeCAD.ActiveDocument.recompute()
#
cyl1 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder1")
cyl1.Label = "Cylinder1"
cyl1.Radius = plusY/5
cyl1.Placement = FreeCAD.Placement(FreeCAD.Vector(0,plusX,plusY),
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
if aSideFlag==starboardSideFlag: cyl1.Placement.Base.x = -10
cut1 = FreeCAD.activeDocument().addObject("Part::Cut","Cut1")
cut1.Base = App.activeDocument().PlainPlaquePad
cut1.Tool = App.activeDocument().Cylinder1
cut1.ViewObject.ShapeColor = woodColour
FreeCAD.ActiveDocument.recompute()
#
cyl2 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder2")
cyl2.Label = "Cylinder2"
cyl2.Radius = plusY/5
cyl2.Placement = FreeCAD.Placement(FreeCAD.Vector(0,plusX,minusY),
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
if aSideFlag==starboardSideFlag: cyl2.Placement.Base.x = -10
cut2 = FreeCAD.activeDocument().addObject("Part::Cut","Cut2")
cut2.Base = App.activeDocument().Cut1
cut2.Tool = App.activeDocument().Cylinder2
cut2.ViewObject.ShapeColor = woodColour
FreeCAD.ActiveDocument.recompute()
#
cyl3 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder3")
cyl3.Label = "Cylinder3"
cyl3.Radius = plusY/5
cyl3.Placement = FreeCAD.Placement(FreeCAD.Vector(0,minusX,minusY),
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
if aSideFlag==starboardSideFlag: cyl3.Placement.Base.x = -10
cut3 = FreeCAD.activeDocument().addObject("Part::Cut","Cut3")
cut3.Base = App.activeDocument().Cut2
cut3.Tool = App.activeDocument().Cylinder3
cut3.ViewObject.ShapeColor = woodColour
FreeCAD.ActiveDocument.recompute()
#
cyl4 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder4")
cyl4.Label = "Cylinder4"
cyl4.Radius = plusY/5
cyl4.Placement = FreeCAD.Placement(FreeCAD.Vector(0,minusX,plusY),
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
if aSideFlag==starboardSideFlag: cyl4.Placement.Base.x = -10
cut4 = FreeCAD.activeDocument().addObject("Part::Cut","Cut4")
cut4.Base = App.activeDocument().Cut3
cut4.Tool = App.activeDocument().Cylinder4
cut4.ViewObject.ShapeColor = woodColour
FreeCAD.ActiveDocument.recompute()
#
cham = FreeCAD.ActiveDocument.addObject("Part::Chamfer","Plaque")
cham.Base = FreeCAD.ActiveDocument.Cut4
edges = []
if aSideFlag == starboardSideFlag:
edges.append((3,3.00,3.00)); edges.append((13,3.00,3.00)); edges.append((14,3.00,3.00)); edges.append((15,3.00,3.00))
edges.append((16,3.00,3.00)); edges.append((17,3.00,3.00)); edges.append((18,3.00,3.00)); edges.append((19,3.00,3.00))
else:
edges.append((1,3.00,3.00)); edges.append((5,3.00,3.00)); edges.append((6,3.00,3.00)); edges.append((7,3.00,3.00))
edges.append((8,3.00,3.00)); edges.append((9,3.00,3.00)); edges.append((10,3.00,3.00)); edges.append((11,3.00,3.00))
cham.Edges = edges
cham.Base.ViewObject.Visibility = False
cham.ViewObject.ShapeColor = woodColour

createPlaqueCover()
if aSideFlag == starboardSideFlag:
FreeCADGui.activeDocument().activeView().viewRight()
else:
FreeCADGui.activeDocument().activeView().viewLeft()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCAD.ActiveDocument.recompute()

def createPlaqueCover():
# get upper bow point UBP
# get upper stern point USP
# get number of sections NS
# divide the line UBP-USP into NS pieces
#---
# for each sketch, get the top point TP
# make segments between each consecutive points
#---
# make RuledSurface between corresponding segments
FreeCAD.zot = crossSections
if len(crossSections)<5:
FreeCAD.Console.PrintMessage("Insufficient cross-sections for plaque cover")
else:
# the number of cross-section and therefore chines determines how many
# segments will be in the cover for the half-hull model
segmentCount = len(crossSections)-2
# determine endpoints for the line segments along the plaque
bowPoint = Base.Vector(0,
max(crossSections[0].geometryS[-1].StartPoint.x, crossSections[0].geometryS[-1].EndPoint.x),
max(crossSections[0].geometryS[-1].StartPoint.y, crossSections[0].geometryS[-1].EndPoint.y))
sternPoint = Base.Vector(0,
crossSections[-2].yPos,
max(crossSections[-2].geometryS[-1].StartPoint.y, crossSections[-2].geometryS[-1].EndPoint.y))
plaquePoints = [bowPoint, sternPoint]
lineAlongPlaqueToSplit = Part.Line(bowPoint,sternPoint)
lapSection = lineAlongPlaqueToSplit.length()/segmentCount

# build a list of the points that start/end the segments
pointList = []
pointList.append(lineAlongPlaqueToSplit.StartPoint)
print lineAlongPlaqueToSplit.StartPoint
for i in range(1, segmentCount):
pointList.append(lineAlongPlaqueToSplit.value((i)*lapSection))
print lineAlongPlaqueToSplit.value((i)*lapSection)
pointList.append(lineAlongPlaqueToSplit.EndPoint)
print lineAlongPlaqueToSplit.EndPoint
print
# iterate the list of points from the first segment to the last
# do stemline first as it is in the YZ plane (cross-sections are in the XZ plane)
a=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs1k')
a.X1=0; a.Y1=pointList[0].y; a.Z1=pointList[0].z
a.X2=0; a.Y2=pointList[1].y; a.Z2=pointList[1].z
b=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs1h')
# B1 wrong
b.X1=0; b.Y1=crossSections[0].endPoint.x; b.Z1=crossSections[0].endPoint.z
b.X2=crossSections[1].endPoint.x; b.Y2=crossSections[1].endPoint.y; b.Z2=crossSections[1].endPoint.z
FreeCAD.ActiveDocument.addObject('Part::RuledSurface', 'coverSeg1')
FreeCAD.ActiveDocument.ActiveObject.Curve1=(a,[''])
FreeCAD.ActiveDocument.ActiveObject.Curve2=(b,[''])
FreeCAD.ActiveDocument.recompute()
# now do cross=sections
for i in range(1, segmentCount):
a=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs'+str(i+1)+'k')
a.X1=0; a.Y1=pointList[i].y; a.Z1=pointList[i].z
a.X2=0; a.Y2=pointList[i+1].y; a.Z2=pointList[i+1].z
b=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs'+str(i+1)+'h')
b.X1=crossSections[i].endPoint.x; b.Y1=crossSections[i].endPoint.y; b.Z1=crossSections[i-1].endPoint.z
b.X2=crossSections[i+1].endPoint.x; b.Y2=crossSections[i+1].endPoint.y; b.Z2=crossSections[i].endPoint.z
FreeCAD.ActiveDocument.addObject('Part::RuledSurface', 'coverSeg'+str(i+1))
FreeCAD.ActiveDocument.ActiveObject.Curve1=(a,[''])
FreeCAD.ActiveDocument.ActiveObject.Curve2=(b,[''])
FreeCAD.ActiveDocument.recompute()
FreeCAD.ActiveDocument.recompute()

def displayChinePanels(crossSectionLabelA, crossSectionLabelB, aSideFlag):
# accept 2 sketch labels and generate a ruled surface between them
#print sketchLabelA
placeholder = "_"
csA = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelA)[0]
csB = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelB)[0]
labelA = csA.Label
labelB = csB.Label
lblA = labelA.split('_', 1)[0]
lblB = labelB.split('_', 1)[0]
if aSideFlag == portSideFlag:
sideText = " Port"
elif aSideFlag == starboardSideFlag:
sideText = " Starboard"
else:
sideText = ""
#print str(sketchLabelA) + " " + str(sketchLabelB)
FreeCAD.ActiveDocument.addObject('Part::RuledSurface', placeholder+lblA+placeholder+lblB+sideText)
csObjectA = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelA)[0]
csObjectB = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelB)[0]
#print "> " + str(sketchObjectA) + " " + str(sketchObjectB)
FreeCAD.ActiveDocument.ActiveObject.Curve1=(csObjectA,[''])
FreeCAD.ActiveDocument.ActiveObject.Curve2=(csObjectB,[''])

def displayCompleteHull():
userAction = None

docKey = findOpenDocumentName(outputWorkspaceC)
if docKey!=None:
reply = QtGui.QMessageBox.question(None, "",
"The previous 'Complete Hull' output file is still open, close it",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
FreeCAD.closeDocument(outputWorkspaceC)
else:
userAction = userCancelled

if userAction!=userCancelled:
doc = App.newDocument(outputWorkspaceC)
App.setActiveDocument(outputWorkspaceC)
App.ActiveDocument=App.getDocument(outputWorkspaceC)
docComplete = App.ActiveDocument
Gui.ActiveDocument=Gui.getDocument(outputWorkspaceC)
for cs in crossSections:
# add new Sketch object
newSketch = FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
newSketch.Placement = FreeCAD.Placement(
FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
Gui.activeDocument().setEdit(cs.altLabel)
# insert geometry segments from both half-hulls into new Sketch
for seg in cs.geometryC:
newSketch.addGeometry(
Part.Line(FreeCAD.Vector(seg.StartPoint.x,
seg.StartPoint.y, 0),
FreeCAD.Vector(seg.EndPoint.x,
seg.EndPoint.y,0)))
Gui.getDocument(doc.Label).resetEdit()
#print obj.Name + " " + obj.Label
FreeCAD.cs = crossSections # debug statement
for i in range(0, len(crossSections)-1):
displayChinePanels(crossSections[i].altLabel,crossSections[i+1].altLabel, "C")
# now draw the bow sections going to the stemline
#displayChinePanels(crossSections[].altLabel,crossSections[i+1].altLabel, "C")
FreeCAD.activeDocument().recompute()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCADGui.activeDocument().activeView().viewAxometric()

def displayHalfHull(aSideFlag):
userAction = None
# create output workspace for one side
if aSideFlag == starboardSideFlag:
query = "The previous 'Port Half-Hull' output file is still open, close it"
selectedOutputWorkspace = outputWorkspaceS
outputWorkspace = outputWorkspaceS
sideFlag = starboardSideFlag
else:
query = "The previous 'Starboard Half-Hull' output file is still open, close it"
selectedOutputWorkspace = outputWorkspaceP
outputWorkspace = outputWorkspaceP
sideFlag = portSideFlag

docKey = findOpenDocumentName(selectedOutputWorkspace)
if docKey==None:
doc = FreeCAD.newDocument(selectedOutputWorkspace)
else:
reply = QtGui.QMessageBox.question(None, "",
query,
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
FreeCAD.closeDocument(selectedOutputWorkspace)
doc = FreeCAD.newDocument(selectedOutputWorkspace)
else:
userAction = userCancelled

if userAction!=userCancelled:
FreeCAD.setActiveDocument(outputWorkspace)
FreeCAD.ActiveDocument=FreeCAD.getDocument(outputWorkspace)
FreeCADGui.ActiveDocument=FreeCADGui.getDocument(outputWorkspace)
# place the segments in the document
for cs in crossSections:
# add new Sketch object
FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
newSketch = FreeCAD.ActiveDocument.getObject(cs.altLabel)
newSketch.Placement = FreeCAD.Placement(
FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
Gui.activeDocument().setEdit(cs.altLabel)
# insert geometry segments into new Sketch
if sideFlag == "S":
geom = cs.geometryS
else:
geom = cs.geometryP
for seg in geom:
newSketch.addGeometry(
Part.Line(FreeCAD.Vector(seg.StartPoint.x,
seg.StartPoint.y, 0),
FreeCAD.Vector(seg.EndPoint.x,
seg.EndPoint.y,0)))
Gui.getDocument(doc.Label).resetEdit()
for i in range(0, len(crossSections)-1):
displayChinePanels(crossSections[i].altLabel,crossSections[i+1].altLabel, sideFlag)
FreeCAD.activeDocument().recompute()
FreeCADGui.SendMsgToActiveView("ViewFit")
FreeCADGui.activeDocument().activeView().viewAxometric()

def eqRotation(rotationA, rotationB, eps=0.0001):
"takes 2 Rotations and compares for equality"
eqFlag = True
for i in range(0, 4):
#print str(rotationA.Q[i]) + "#" + str(rotationB.Q[i])
if rotationA.Q[i] == 0:
if rotationB.Q[i] <> 0:
eqFlag = False
elif abs(abs(rotationA.Q[i])-abs(rotationB.Q[i]))/abs(rotationA.Q[i]) > eps:
eqFlag = False
return eqFlag

def resetSketchesVisibility():
# set Visibility on all Sketches to False
openWindows = list()
if starboardHHFlag:
openWindows.append(outputWorkspaceS)
if portHHFlag:
openWindows.append(outputWorkspaceP)
if completeHullFlag:
openWindows.append(outputWorkspaceC)
#if fullHullBulkheadsFlag:
#openWindows.append(outputWorkspaceB)

for ws in openWindows:
FreeCAD.setActiveDocument(ws)
FreeCAD.ActiveDocument=FreeCAD.getDocument(ws)
FreeCADGui.ActiveDocument=FreeCADGui.getDocument(ws)
for obj in FreeCAD.ActiveDocument.Objects:
if obj.TypeId == 'Sketcher::SketchObject':
vo = FreeCADGui.ActiveDocument.getObject(obj.Name)
vo.Visibility=False

def sortOutFilesAndDocuments():
global keepSourceOpenFlag
# this routine uses the following variables from the main handler:
# docSrc (returns)
# fileName (reads)
# keepSourceOpenFlag (global, writes)
# it determines if the 'user selected input document' is already open,
# if it is then is is made the ActiveDocument, otherwise it is
# Opened which also sets it to the ActiveDocument
if len(FreeCAD.listDocuments())==0:
# no documents open
return FreeCAD.open(fileName)
else:
# some document(s) open so check if 'user selected input document' is already open
"""docKey = None
for key in FreeCAD.listDocuments():
if FreeCAD.listDocuments()[key].FileName==fileName:
docKey = key"""
docKey = findOpenDocumentFileSpec(fileName)
if docKey==None:
# 'user selected input document' is not open
return FreeCAD.open(fileName)
else:
# 'user selected input document' is among open documents
# set the 'user selected input document' to the active document (in case it isn't)
FreeCAD.setActiveDocument(docKey)
# user started with 'user selected input document' open, so this flag will allow
# us to leave it open when we finish
keepSourceOpenFlag = True
return FreeCAD.ActiveDocument

def findOpenDocumentFileSpec(aDocumentFileSpec):
# check if supplied document is already open
# return document name or None
docKey = None
for key in FreeCAD.listDocuments():
if FreeCAD.listDocuments()[key].FileName==aDocumentFileSpec:
docKey = key
return docKey
def findOpenDocumentName(aDocumentName):
# check if supplied document is already open
# return document name or None
docKey = None
for key in FreeCAD.listDocuments():
if FreeCAD.listDocuments()[key].Name==aDocumentName:
docKey = key
return docKey
# Constant definitions
outputWorkspaceS = "hull_starboard"
outputWorkspaceP = "hull_port"
outputWorkspaceC = "hull_complete"
outputWorkspaceB = "bulkheads"
xyPlane = Base.Rotation(0.0, 0.0, 0.0, 1.0) # transom
xzPlane = Base.Rotation(0.7071067811865475, 0.0, 0.0, 0.7071067811865476) # cross section profile
yzPlane = Base.Rotation(0.5,0.5,0.5,0.5) # stemline
infinity = +99999999999999.9 # will hold min Y value in Sketch
infinityNegative = -99999999999999.9 # will hold max X value in Sketch
global starboardSideFlag, portSideFlag, userCancelled, userLastFile, userOK
starboardSideFlag = "S"
portSideFlag = "F"
completeSidesFlag = "C"
userCancelled = "Cancelled"
userLastFile = "Last File"
userOK = "OK"
defaultDir = FreeCAD.ConfigGet("UserHomePath")

# code ***********************************************************************************
allCrossSections = {}
crossSections = list()
docSrc = None
global keepSourceOpenFlag
keepSourceOpenFlag = False
starboardHHFlag = False; starboardHHPlaqueFlag = False; starboardHHPKeelFlag = False
portHHFlag = False; portHHPlaqueFlag = False; portHHPKeelFlag = False
completeHullFlag = False; completeHullBottleFlag = False; completeHullKeelFlag = False
bulkheadsFlag = False; flushDeckBulkheadsFlag = True; coachhouseDeckBulkheadsFlag = True

form = GetConfigParams()
form.exec_()
configParams = form.configParams

if configParams.result==userLastFile:
if hasattr(FreeCAD,"MacroHalfHullConfigParams"):
# global in FreeCAD exists so use the parameters stored there
#if type(FreeCAD.MacroHalfHullConfigParams)=='GetConfigParams':
configParams = FreeCAD.MacroHalfHullConfigParams
configParams.result = userLastFile
else:
# user requested to re-use last file but there isn't one
# so reset the choice to pick a file
configParams.result = userOK
if configParams.result != userCancelled:
# transfer results to control flags
starboardHHFlag = configParams.cb1a
starboardHHPlaqueFlag = configParams.cb1b
starboardHHPKeelFlag = configParams.cb1c
portHHFlag = configParams.cb2a
portHHPlaqueFlag = configParams.cb2b
portHHPKeelFlag = configParams.cb2c
completeHullFlag = configParams.cb3a
completeHullBottleFlag = configParams.cb3b
completeHullKeelFlag = configParams.cb3c
bulkheadsFlag = configParams.cb4a
# transfer bulkhead parameters
bulkheadParams = {}
bulkheadParams["coachhouseDeckBulkheadsFlag"] = configParams.rb5b
bulkheadParams["forwardBulkheadsToSkip"] = int(configParams.skipAtBow)
bulkheadParams["aftBulkheadsToSkip"] = int(configParams.skipAtStern)
bulkheadParams["deckWidth"] = float(configParams.deckWidth.text())
bulkheadParams["deckThrow"] = float(configParams.deckThrow.text())
bulkheadParams["coachhouseRise"] = float(configParams.coachhouseRise.text())
bulkheadParams["coachhouseIncline"] = float(configParams.coachhouseIncline.text())
defaultDir = expanduser("~")
defaultDir = "/Data Pool/Coding/FreeCAD/work items/hull mirroring/half-hull/"
if configParams.result==userOK:
# user wants to select file
fileName = QtGui.QFileDialog.getOpenFileName(dir=defaultDir, caption = "Select a 'Hull Profile' to Load", filter="*.FCStd")[0]
configParams.documentFileName = fileName
else:
# file is coming out of FreeCAD global
fileName = FreeCAD.MacroHalfHullConfigParams.documentFileName

if len(fileName) != 0:
docSrc = sortOutFilesAndDocuments()
# read all the objects, saving the Sketcher objects
for obj in FreeCAD.ActiveDocument.Objects:
if obj.TypeId == 'Sketcher::SketchObject':
# ignore anything except Sketches
newObj = HullCrossSection(obj)
if newObj.transomFlag:
# if we have the transom sketch then wait to give
# it a placement further aft than anything else
transomObject = newObj
elif newObj.stemlineFlag:
# if we have the stemline sketch then wait
# to give it the first placement
stemlineObject = newObj
stemlineSegmentCount = stemlineObject.sketch.GeometryCount
else:
# must be a regular cross section profile
# so add object to our collection
allCrossSections[newObj.key] = newObj
#maxY = max(maxY, newObj.yPos)

# discard sketches with wrong number of points
# i.e. cross-sections that have a different number
# of points than the stemline
for key, cs in allCrossSections.items():
if cs.geometryCount <> stemlineSegmentCount:
FreeCAD.Console.PrintMessage("Discard for wrong number of segments for '" + str(allCrossSections[key].label) + "'\n")
del allCrossSections[key]
# construct a list with cross sections in order from stemline to transom
crossSections.append(stemlineObject)
for sk in sorted(allCrossSections.keys(), reverse=True):
crossSections.append(allCrossSections[sk])
transomObject.zPos = crossSections[-1].zPos+crossSections[-1].yMax
crossSections.append(transomObject)

FreeCAD.cs = crossSections # debug statement
# now set the transom elevation at the top of the last cross-section
crossSections[-1].zPos = crossSections[-2].zPos + crossSections[-2].yMax
FreeCAD.cs = crossSections # debug statement

# depending on user flags, invoke appropriate modules
if starboardHHFlag: displayHalfHull(starboardSideFlag)
if starboardHHPlaqueFlag: createPlaque(starboardSideFlag, starboardHHPKeelFlag)
if portHHFlag: displayHalfHull(portSideFlag)
if portHHPlaqueFlag: createPlaque(portSideFlag, portHHPKeelFlag)
if completeHullFlag: displayCompleteHull()
if completeHullBottleFlag: createBottle(completeHullKeelFlag)
if bulkheadsFlag: createBulkheads(bulkheadParams)

# save Config Params to FreeCAD global in case user wants to use it next run
FreeCAD.MacroHalfHullConfigParams = configParams
if not keepSourceOpenFlag:
FreeCAD.closeDocument(docSrc.Name)
resetSketchesVisibility()
}}

Latest revision as of 13:20, 24 July 2019

Other languages:

Macro Half-Hull Model

Description
This macro generates both three dimensional half-hull and full-hull models from a series of 2D line drawings.
This macro makes simple models of the hulls of boats and ships. It is intended to aid people modeling or designing hulls by providing the hull surface in a straight forward and timely fashion so they can avoid the time consuming full process.

Macro version: 1.0
Last modified: 2016-01-25
FreeCAD version: <= 0.16
Download: ToolBar Icon
Author: Piffpoof
Author
Piffpoof
Download
ToolBar Icon
Links
Macro Version
1.0
Date last modified
2016-01-25
FreeCAD Version(s)
<= 0.16
Default shortcut
None
See also
None

Description

This macro makes simple models of the hulls of boats and ships. It is intended to aid people modeling or designing hulls by providing the hull surface in a straight forward and timely fashion so they can avoid the time consuming full process.

Temporary code for external macro link. Do not use this code. This code is used exclusively by Addon Manager. Link for optional manual installation: Macro


# This code is copied instead of the original macro code
# to guide the user to the online download page.
# Use it if the code of the macro is larger than 64 KB and cannot be included in the wiki
# or if the RAW code URL is somewhere else in the wiki.

from PySide import QtGui, QtCore

diag = QtGui.QMessageBox(QtGui.QMessageBox.Information,
    "Information",
    "This macro must be downloaded from this link\n"
    "\n"
    "http://pastebin.com/raw/tZMpUi6F" + "\n"
    "\n"
    "Quit this window to access the download page")

diag.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
diag.setWindowModality(QtCore.Qt.ApplicationModal)
diag.exec_()

import webbrowser 
webbrowser.open("http://pastebin.com/raw/tZMpUi6F")
<class="rawcodeurl"><a href="http://pastebin.com/raw/tZMpUi6F">raw code</a>


Background

Since antiquity boat builders have had to design boats and then realise them in a three dimensional environment where very few lines are straight, very few surfaces flat and no angles constant over a distance. Hull models were used to make to-scale models which could then be used to review the design or communicate concepts to other persons. The laws of hydrodynamics demand that any vessel passing through a liquid be symmetrical in the cross-section if it is to track (move in a straight line) accurately. Consequently building one side of the hull in the model was all that was necessary - if the half-hull was perfect then its mirror image would also be perfect. In latter years half-hulls became used for symbolic purposes such as plaques, and nautical art.

This code came out of a project to build a model in FreeCAD of a 12.5 metre sailing boat. The builders no longer had drawings and the son of the original owner doubted there ever were full drawings. Consequently the hull dimensions had to be measured and then a model constructed within the software, Modern software packages like FreeCAD offer many beneficial features like symmetry constraints but when the final measurement is a model that is "easy on the eye", much "massaging" of models is required. This code was written to help automate that process.

The initial purpose of this code was to provide a symmetrical hull which was malleable so it could be fitted to the 3D model being constructed. As the software progressed it was generalised so hopefully it would be of some use to persons:

  • boat designers producing boat diagrams which can be built from
  • boat builders who are trying to model what is described on their diagrams
  • model hull builders.

Certainly commercial boat designers have all sorts of high end software to assist their work, this isn't supposed to replace that. Rather this is for the hobbyist or home builder to tinker around with.

Nautical blueprints have a distinct history with respect to buildings and so are somewhat different in presentation. This is an example of a sailing boat which is more than a century old:

One of the end goals of this software is to use the Draft workbench to generate some of these diagrams using the model to generate the lines.

Note on Units in FreeCAD:

At present there isn't really any units management system within FreeCAD, but obviously a boat builder or modeller will need an accurate dimensioning system. To use this macro, decide to set the FreeCAD grid size to what ever is appropriate for your work (e.g. mm, cm, inch, foot). FreeCAD is consistent, one FreeCAD unit will constantly equal to one FreeCAD unit. And if you have decided to have a FreeCAD unit equal a specific physical length, then your drawings will remain consistently dimensioned. At present there is work on a units system for FreeCAD so this situation may change soon.

Description

For this macro the hull shapes are defined by a minimum of 3 FreeCAD Sketches: one in the YZ plane, one or more in the XZ plane, one in the XY plane. Here is the minimum hull supported by this macro, it has only 3 Sketches:

Note: in the above diagram we are looking directly at the stern, the bow is pointing away from the viewpoint.

From front to back (bow to stern) the 3 Sketches are:

stemline
YZ plane
red line in sketch
cross-section
XZ plane
green line in Sketch
transom
XY plane
blue line in Sketch


Perhaps it is easier to see with 7 Sketches (one in YZ plane, one in XY plane and 5 in the XZ plane):

With 5 Sketches in the XZ plane it is becoming easier to perceive the shape of the hull. The next 2 pictures show the Sketch lines superimposed on the model FreeCAD constructs,

the second is the same model rotated 90 degrees so the bow is in the foreground:

Some points to consider:

  • the Sketches are only for:
    • the stemline (or bow line) in the YZ plane (red in the above diagram);
    • the top of the transom in the XY plane (blue in the above diagram);
    • multiple cross-sections of the hull in the XZ plane (green in the above diagram)
  • only the starboard side of the hull is drawn in the Sketches, the port side will be generated as a mirror image
  • each multiple-segment line must be in a separate Sketch
  • each Sketch must have the same number of line segments (which is 3 in the previous examples)
  • the more line segments in each Sketch, the closer the FreeCAD generated model will approximate a curved hull
  • there is no limit to the number of line segment in each Sketch, any number from one up
  • there is no limit to the number of Sketches in the XZ plane (i.e. cross-sections), any number from one up

With enough Sketches the model generated may even approach:

The HalfHull macro generates 4 models:

  • starboard half-hull
  • port half-hull
  • complete hull
  • bulkheads for the complete hull, either with a flush deck or with a coachhouse

These models are all output in the unified location space of FreeCAD so they can be fitted together - for example the bulkheads can be inserted into the complete hull seamlessly. This is a picture of bulkheads in a boat model during construction:

As well as the bulkheads generated by the macro (note that these bulkheads are for a coachhouse rather than a flush deck):

The composite image below shows the main outputs from this software (the port side half-hull is actually not shown but it is the mirror of the starboard half-hull which is shown). The outputs are in clockwise order from the upper left corner:

  • starboard half-hull
  • complete hull
  • bulkheads (for flush deck, the bulkheads in the previous image were for a coachhouse deck)
  • the complete hull with the bulkheads inserted

As novelty features, the macro will also optionally produce plaques for the half-hulls, and even a bottle for the complete hull:

Installation

All the code for halfHullModel.FCMacro is in one macro. So installation is comprised of copying the code to the appropriate Macro directory and invoking the Build Utility from the Macro menu, the Python console or a toolbar button (the preferred method).

Usage

The FreeCAD operations involved in generating the hull model are rather complex and numerous. Things like the direction a line is drawn can cause the FreeCAD construction of the hull to either abort or turn out like:

Consequently the steps below need to be followed closely. The macro does allow for certain data inconsistencies but generally if the data (i.e. the Sketches) are incorrect then the output will look like a cheese grater or the macro fill fail with an error.

The following instructions refer to the quadrants of the XY graph, this refers to the 4 quarters of the XY graph and they are labelled as follows:

Create a New Document

The very first thing is to create a new document in FreeCAD . This document will hold all the Sketches that make up your hull definition.

Create the Stemline

The first step is one of creating data for the hull model to be made from. The data is supplied in the form of Sketches within FreeCAD. After the hull model is generated, if changes are to be made then the Sketches are simply edited, and the second step of running the macro repeatedly.

  1. create a new sketch , in the YZ-Plane
  2. start at origin (0,0) and draw upwards into Quadrant I
  3. the bottom end of the stemline is at the origin (0,0) - this will be the point from where the placement of all the cross-sections and transom will be made
  4. the number of line segments in this Sketch determines the number which will be required in each other Sketch
  5. save sketch
  6. for ease of identification it is probably worth naming the Sketch something like "stemline sketch"

Create the Cross-section Sketch(es)

  1. create a new sketch , in the XZ-Plane
  2. the following dialog will appear:

    The dialog is asking how far from the origin the Sketch should be placed. This will refer to how far the cross-section is from the bottom of the stemline (which was placed at (0,0)). The cross-sections can be equally spaced but need not be. The forward-most cross-section will be at Y=0 (i.e. the origin where the stemline bottom ends) or at Y<=0. The cross-sections will be at increasingly negative Y values until the transom is at the most negative Y value. In the example above, the cross-section Sketch will be placed 50 FreeCAD units from the origin on the negative Y axis.
  3. start on the Y axis and draw upwards into Quadrant I
    - the first (i.e. foremost) cross-section should start at origin (0,0) (or it will look odd as the stemline ends at 0,0) but other cross-sections need only start on the Y axis.
  4. use the same number of line segments as in the Stemline Sketch
  5. save sketch
  6. repeat as this step necessary, it may be quicker to copy this sketch and then space the copies on the Y-axis, modifications may be made to the individual Sketches as required
  7. for naming, it will make things easier to give some sort of sequence to the cross-sections, starting at the bow (i.e. the stemline) and increasing towards the stern (i.e. the transom)

Create the Transom Sketch in the XY Plane

  1. create a new sketch , in the XY-Plane
  2. start on the Y axis between quadrant III and IV, and draw upwards into Quadrant IV so that the end point is coincident with the rightmost point of the lowest cross-section Sketch in the YZ-plane
  3. use the same number of line segments as in the stemline Sketch
  4. save sketch
  5. for ease of identification it is probably worth naming the Sketch something like "transom sketch"

Save the New Document

Now save the document which contains the Sketches that will define the hull shape, giving it some name that is descriptive

Once the Sketches have all been created and positioned, the document should look like this from the top view :

The principal limitations for constructing the model are:

  • the stemline bottom should end at (0,0)
  • the bottom centremost end of each cross-section should end at the Y axis - note that it can have any Z value

This concludes the first step which is one of creating the data which the macro will use to create both half-hulls and complete-hulls. The second step is described in the following section which is titled User Interface

User Interface

In this step the macro will gather some information from the user and then process the input Sketches to generate the desired hull models. This is the only GUI image for the macro and is primarily configuration details for the production of the hull models from the Sketches:

The choices on the GUI window are:

  • Starboard half-hull
    - checking this will cause the macro to produce a starboard half-hull model
    • Mounting plaque
      - if checked the macro will mount the half-hull on a plaque
      • Allow space for keel
        - if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
  • Port half-hull
    - checking this will cause the macro to produce a port half-hull model
    • Mounting plaque
      - if checked the macro will mount the half-hull on a plaque
      • Allow space for keel
        - if checked will cause the half hull to be mounted higher on the plaque than the centre position, this is so a separately generated keel could be placed below the hull
  • Complete hull
    - checking this will cause the macro to produce a complete model
    • Bottle for complete hull
      - if checked the macro will place the complete hull inside a transparent bottle (complete with cork)
      • Allow space for keel
        - if checked will cause the half hull to be positioned higher in the bottle than the centre position, this is so a separately generated keel could be placed below the hull
  • Bulkheads for flush deck
    - checking this will cause the macro to produce bulkheads whose tops are level with the top of the hull, bulkheads will not be generated for the most forward 2 cross-sections or the aft-most 2 cross-sections
  • Bulkheads for coachhouse
    - checking this will cause the macro to produce bulkheads whose tops are possibly above the top of the hull.
    • Bulkheads to skip at bow determines how many cross-sections will be left without a bulkhead at the bow
    • Bulkheads to skip at stern determines how many cross-sections will be left without a bulkhead at the stern
  • The dimensions of the top of the bulkheads will be configured as per the following diagram:
  • Cancel button
    - the execution is halted and the window closes
  • Re-Use Last File button
    - the execution uses the data file AND SETTINGS from the the last run, any changes to settings are ignored
  • Select File button
    - the standard Open File window is opened where the user can either select a file or Cancel and exit

When the macro runs it takes configuring data from the user and then reads Sketcher sketches in the selected input file.

Note: As the macro works through the Sketches it prints out any exceptions as well as some milestones on the Report View. If you get unexpected results or some parts are missing, that is probably the first place to check.

Options

There are various types of bows and sterns for boats, with sterns having much more variety than bows. Here are examples of transoms and bows from the real world alongside the similar macro output:

Sheer Stern

Probably the most common stern, typical of all sizes of vessels from commercial ships through to rowing boats.

  • the XY transom should be as close to the aftmost cross-section as is possible.

Sugar-Scoop Stern

Most likely to be found on a sailing yacht, it is a product of designing to maximise the length of the waterline to benefit from class rules for racing under sail.

  • place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees (or whatever is called for) around the X-axis

Canoe Stern

Found on all sizes of water craft, power and sail, pleasure and commercial.

  • place the aftmost 2 cross-sections as close as is feasible, then rotate the aftmost of the two through to an angle of 45 degrees around the X-axis

Normal Bow

There is a lot less variety in bow shapes than with transoms:

Trireme Bow

Although not seen very frequently in the last 2 millenia, this was once the definitive bow profile for war-making vessels:

  • in order for the bow to be correct the poly-line for the stemline needs to be drawn from the bottom to the top which will mean right to left in the Sketcher

Sample Files

These files are samples of Sketch data to use with the macro, mainly they are the models for the screen snapshots in the Options section above. The files work with the Macro and so can be downloaded and played with to adapt to your specific requirements. The prefix of 5x3 (for example) means the model has 5 cross-sections and 3 line segments per cross-section (i.e. sketch)

To use one of the example files, right-click on the file link and select Save File As... from the menu. The filename will be specified, choose the desired folder/directory to hold the example file.

Remarks

  • almost all the examples on this page are generated with only 3 line segments defining the side of the hull which gives a very faceted appearance, increasing the number of segments in each Sketch would generate a much smoother surface which would increase the realism
  • doesn't do keels, skegs or rudders, in other words, it doesn't do any of the wet area
  • doesn't do square bows like push-boats or towed barges
  • doesn't do submarines (although it will do the lower half of a submarine)

Known Problems

The 'Ruled Surface' feature of FreeCAD is used to generate the hull sections from the Sketches. It can sometimes generate the wrong result and display a grater like surface instead of a smooth planar one. This will typically occur when the Sketches are rotated such as when a Sugar Scoop stern is modeled. Also angling a Sheer Transom stern can cause this. If it occurs then typically it will do so in either the half-hull models or the complete hull model - it never seems to occur in all three models for the same hull. Also it usually only happens at the extreme bow or stern. If it happens to section in the middle of the boat then most likely one of the Sketches was drawn in the wrong direction (i.e. either random sequence or top-down where as all lines should be drawn bottom-up)

It can usually be removed by using the following steps:

  • in the Model tab of the Combo View, click on the faulty segment to select it, the faulty segment will show as highlighted on the display
  • select the Data tab on the bottom half of the Combo View, the lower part of the window will have a Label "Ruled Surface" with a single parameter 'Orientation'
  • there is a popup menu to the right which has the values 'Automatic', 'Forward', 'Reversed', it will initially be set to 'Automatic'
  • try one of the other settings (remembering the faulty segment must still be selected in the upper part of the Combo View) which will usually correct the problem

The following screen snapshot shows the relevant portion of the screen:

Future Possibilities

  • replace line segments of cross-sections with curved lines
    - this is just at the idea stage but would give a much smoother surface in the vertical dimension, however the horizontal surface would still be faceted as it is now
  • integrate with Draft workbench to produce drawings from models
    - an initial goal, but the feasibility has not been investigated
  • handle keels, skegs and rudders
    - one work around for keels with the present system is to model the keel as a half-hull on it's own and then assemble it onto the bottom of the main hull; this would still do nothing for rudders and skegs though

Glossary

As with any ancient and practiced trade, a rich and sometimes confusing vocabulary has developed around ships, boats and nautical practices. In describing this macro it is both awkward and inefficient to describe the process without using the correct and accurate terms. The obvious problem is that the average lay person will be unfamiliar with such terminology, hence this vocabulary:

aft the rear aspect of anything on a boat


chine a planar facet of a hull, can be used to approximate a curved surface or as a finished building technique


coachhouse the part of the central deck which is raised above the deck level - usually to accommodate increased headroom in the interior of the boat


flush deck a deck that runs smoothly from the top of one side of the hull to the other, the converse to a coachhouse deck


forward also 'fore'; the front aspect of anything on a boat


port lefthand side looking forward


starboard righthand side looking forward


stemline the sloped vertical edge which is the bow of a hull


transom the curved top edge of the stern face which can be flat or curved

Links

Script

ToolBar Icon

Macro_Half_Hull_Model.FCMacro

This script is running bug free. But due to the large range of possible inputs it may fail for some inputs. If so please report it.

The script is too long for the Wiki to display so it must be copied or downloaded from unabbreviated script on pastebin.com