Merge branch 'unstable' into issue-1894

This commit is contained in:
SiboVG 2023-01-16 23:20:27 +01:00
commit ebb9fd0e3c
180 changed files with 2842 additions and 844 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
open_collective: openrocket

View File

@ -17,7 +17,7 @@
<classpathentry kind="lib" path="lib/javax.inject.jar"/>
<classpathentry kind="lib" path="lib/javax.json-1.1.3.jar"/>
<classpathentry kind="lib" path="lib/javax.json-api-1.1.3.jar"/>
<classpathentry kind="lib" path="lib/javax.activation-api.2.3.1.jar"/>
<classpathentry kind="lib" path="lib/javax.activation-api-2.3.1.jar"/>
<classpathentry kind="lib" path="lib/aopalliance.jar"/>
<classpathentry kind="lib" path="lib/commonmark-0.19.0.jar"/>
<classpathentry kind="lib" path="lib/slf4j-api-1.7.30.jar"/>
@ -36,5 +36,8 @@
<classpathentry kind="lib" path="lib/jaxb-api.2.3.1.jar"/>
<classpathentry kind="lib" path="lib/jaxb-runtime.2.3.1.jar"/>
<classpathentry kind="lib" path="lib/guava-26.0-jre.jar"/>
<classpathentry kind="lib" path="lib/istack-commons-runtime.jar"/>
<classpathentry kind="lib" path="lib/graal-sdk-22.1.0.1.jar"/>
<classpathentry kind="lib" path="lib/js-scriptengine-22.1.0.1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

View File

@ -50,6 +50,7 @@ RocketActions.MoveDownAct.ttip.Movedown = Move this component downwards.
! RocketPanel
RocketPanel.FigTypeAct.SideView = Side view
RocketPanel.FigTypeAct.TopView = Top view
RocketPanel.FigTypeAct.BackView = Back view
RocketPanel.FigTypeAct.Figure3D = 3D Figure
RocketPanel.FigTypeAct.Finished = 3D Finished
@ -562,6 +563,7 @@ SimuRunDlg.msg.Unabletosim = Unable to simulate:
SimuRunDlg.msg.errorOccurred = An error occurred during the simulation:
BasicEventSimulationEngine.error.noMotorsDefined = No motors defined in the simulation.
BasicEventSimulationEngine.error.cantCalculateStability = Can't calculate rocket stability.
BasicEventSimulationEngine.error.earlyMotorBurnout = Motor burnout without liftoff.
BasicEventSimulationEngine.error.noConfiguredIgnition = No motors configured to ignite at liftoff
BasicEventSimulationEngine.error.noIgnition = No motors ignited.
@ -718,6 +720,11 @@ simplotpanel.RIGHT_NAME = Right
simplotpanel.CUSTOM = Custom
SimulationPlotPanel.error.noPlotSelected = Please add one or more variables to plot on the Y-axis.
SimulationPlotPanel.error.noPlotSelected.title = Nothing to plot
simplotpanel.MarkerStyle.lbl.MarkerStyle = Marker style:
simplotpanel.MarkerStyle.lbl.MarkerStyle.ttip = Style of the flight event marker (how it's drawn in the simulation plot)
simplotpanel.MarkerStyle.btn.VerticalMarker = Vertical line
simplotpanel.MarkerStyle.btn.Icon = Icon
simplotpanel.MarkerStyle.OnlyInTime = Only available for time domain, other domains only support icon markers
! Component add buttons
compaddbuttons.AxialStage = Stage
@ -819,7 +826,6 @@ ringcompcfg.Radialdirection = Radial direction:
ringcompcfg.radialdirectionfrom = The radial direction from the rocket centerline
ringcompcfg.but.Reset = Reset
ringcompcfg.but.Resetcomponant = Resets the component to the rocket centerline.
ringcompcfg.EngineBlock.desc = <html>An <b>engine block</b> stops the motor from moving forwards in the motor mount tube.<br><br>In order to add a motor, create a <b>body tube</b> or <b>inner tube</b> and mark it as a motor mount in the <em>Motor</em> tab.
ringcompcfg.note.desc = Note: An inner tube will not affect the aerodynamics of the rocket even if it is located outside of the body tube.
@ -972,6 +978,37 @@ RocketCompCfg.tab.Outside = Outside
RocketCompCfg.tab.Inside = Inside
RocketCompCfg.tab.RightSide = Right Side
RocketCompCfg.tab.LeftSide = Left Side
RocketCompCfg.btn.OK.ttip = Keep changes and close the dialog
RocketCompCfg.btn.Cancel.ttip = Discard changes and close the dialog
RocketCompCfg.CancelOperation.msg.discardChanges = <html>Are you sure you want to <b>discard</b> your <b>changes</b> to this component?</html>
RocketCompCfg.CancelOperation.msg.undoAdd = <html>Are you sure you want to <b>undo adding</b> this component?</html>
RocketCompCfg.CancelOperation.title = Cancel operation
RocketCompCfg.CancelOperation.checkbox.dontAskAgain = Don't ask me again
RocketCompCfg.btn.ComponentInfo.ttip = Show/hide informative text about this component.
! ComponentInfo
ComponentInfo.Rocket = This is your rocket. Nothing more to say about it. Have fun designing and building it! :)
ComponentInfo.AxialStage = A <b>stage</b> is a <b>section</b> of the model rocket that contains <b>motors</b> which ignite successively and <b>separate</b> after motor burnout.<br>Each stage must safely descend after separation. The main stage, often called the <b>'Sustainer'</b>, usually descends with a <b>recovery device</b>.
ComponentInfo.ParallelStage = A <b>booster</b> is a fictional component that acts as an <b>attachment</b> point to a <b>body tube</b> and can <b>separate</b> from that body tube at a specified time during flight.<br>The most common use of a booster is the attachment of a <b>strap-on booster</b>.
ComponentInfo.PodSet = A <b>pod</b> is a fictional component that acts as an <b>attachment</b> point to a <b>body tube</b>.<br>Once defined, other components can be attached to the pod to create complex rocket geometries.
ComponentInfo.NoseCone = A <b>nose cone</b> is the <b>front end</b> of the rocket airframe. Nose cones vary widely in shape.<br>The back end can be cut down to form an <b>internal shoulder</b> that slides inside of a body tube to hold the nose cone in place.
ComponentInfo.BodyTube = A <b>body tube</b> is primarily used for the <b>structure</b> of the exterior body of the model rocket.
ComponentInfo.Transition = A <b>transition</b> is the part of the airframe that <b>connects</b> two <b>body tubes</b> having <b>different</b> outside <b>diameters</b>.<br>Both ends of a transition are cut down to form an internal shoulder that slides inside of each body tube to hold the transition in place at both ends.
ComponentInfo.TrapezoidFinSet = Fins help <b>stabilize</b> the rocket. The most common fin shape is <b>trapezoidal</b>, four sides with parallel root and tip chords (the edge that glues to the body tube and the outer edge).
ComponentInfo.EllipticalFinSet = Fins help <b>stabilize</b> the rocket. The most efficient fin shape is <b>elliptical</b>. This shape induces the <b>least amount of drag</b> of any fin shape. It is commonly used for competition flying events.
ComponentInfo.FreeformFinSet = Fins help <b>stabilize</b> the rocket. The most versatile fin component is the <b>freeform fin</b>. This fin component allows the creation of virtually any solid fin shape, with the ability to manually enter data points or import a shape from an image file.
ComponentInfo.TubeFinSet = <b>Tube fins</b> are used to keep the model rocket going <b>straight after launch</b>. Tube fins vary in length and diameter, and may have either straight or curved ends.<br>Currently, OpenRocket only supports straight-perpendicular cut ends.
ComponentInfo.RailButton = <b>Rail buttons</b> keep the model rocket from <b>changing orientation</b> when it launches. Rail buttons are most commonly used on larger model rockets, in pairs, <b>instead</b> of <b>launch lugs</b>.<br>They can be mounted to the outside of the body tube with a screw and internal nut.<br>A rail button looks like two washers with a spacer in between; one washer is against the body tube and the other slides inside the launch rail.
ComponentInfo.LaunchLug = A <b>launch lug</b> is the most common guide used to keep smaller model rockets from <b>changing orientation</b> until the rocket leaves the end of the launch rod.
ComponentInfo.InnerTube = <b>Inner tubes</b> are most commonly used for <b>motor tubes</b>, but may also be part of other internal systems, such as an ejection baffles and adjustable weight designs.<br>Inner tubes may be positioned radially inside of a body tube and, when used as a motor tube, may be clustered.
ComponentInfo.TubeCoupler = A <b>coupler</b> is a short tube that <b>connects</b> two <b>tubes</b> with the <b>same diameters</b>.<br>In model rockets that use electronics, a coupler may be used for the <b>avionics bay</b>.<br>The tubes are often glued to the coupler to stop them from sliding.
ComponentInfo.CenteringRing = <b>Centering rings</b> are used to <b>position</b> one or more <b>inner tubes</b> inside of a <b>body tube</b>.<br>Fin sets have an <b>automatic fin tab</b> feature for determining an ideal fin tab configuration based on an inner tube positioned below the root chord of the fins. This also requires two centering below the root chord.
ComponentInfo.Bulkhead = A <b>bulkhead</b> is a disk that may or may not have a small hole in the center. Bulkheads are used to <b>seal</b> one area of a model rocket from another or to mount recovery system eyes for <b>shock cord attachment</b>, such as nose cone caps and avionics bay lids.
ComponentInfo.EngineBlock = An <b>engine block stops</b> the <b>motor</b> from <b>moving forward</b> in the motor mount tube.<br>In order to add a motor, create a <b>body tube</b> or <b>inner tube</b> and mark it as a motor mount in the <em>Motor</em> tab.
ComponentInfo.Parachute = The <b>parachute</b> is the most common <b>recovery device</b>. Smal model rocket parachutes are made of thin, light-weight materials, such as polyethylene or Mylar, while larger parachutes are most commonly made of rip-stop nylon. Parachutes may be decorated in bright and contrasting colors/patterns to improve visibility.<br><b>Shroud lines</b> attach the parachute <b>canopy</b> to the <b>shock cord</b>. The shroud lines are sometimes brought together and attached using a swivel.
ComponentInfo.Streamer = A <b>streamer</b> is a <b>recovery device</b> that, when ejected, unrolls and <b>slows</b> the model <b>rocket</b> during descent.<br>A streamer is a long, narrow, <b>rectangular strip</b> of crepe paper or thin plastic film. The width and length of the streamer are adjusted to match the weight of the rocket, but a <b>10 to 1 length to width ratio</b> is considered the best for a streamer.
ComponentInfo.ShockCord = A <b>shock cord</b> attaches the <b>nose cone</b> and <b>recovery device</b> to the <b>body tube</b>, keeping the model rocket together during <b>descent</b>.<br>Smaller rockets commonly have short elastic shock cords, while larger rockets have long shock cords made from materials such as Nylon or heat resistant Kevlar webbing.
ComponentInfo.MassComponent = A <b>mass component</b> may be used to simulate a <b>component</b> that is <b>not otherwise listed</b>.<br>In OpenRocket, this component type may be designated for instance as an <b>altimeter</b>, a <b>flight computer</b>, a <b>battery</b>\u2026 Each designation has its own unique icon to ease identification.
! BulkheadConfig
BulkheadCfg.tab.Diameter = Diameter:
@ -1148,6 +1185,8 @@ NoseConeCfg.tab.General = General
NoseConeCfg.tab.ttip.General = General properties
NoseConeCfg.tab.Shoulder = Shoulder
NoseConeCfg.tab.ttip.Shoulder = Shoulder properties
NoseConeCfg.checkbox.Flip = Flip to tail cone
NoseConeCfg.checkbox.Flip.ttip = Flips the nose cone direction to a tail cone.
! ParachuteConfig
Parachute.Parachute = Parachute
@ -1847,6 +1886,7 @@ PlotConfiguration.Groundtrack = Ground track
Warning.LargeAOA.str1 = Large angle of attack encountered.
Warning.LargeAOA.str2 = Large angle of attack encountered (
Warning.DISCONTINUITY = Discontinuity in rocket body diameter
Warning.OPEN_AIRFRAME_FORWARD = Forward end of airframe is open (radius is > 0)
Warning.THICK_FIN = Thick fins may not simulate accurately.
Warning.JAGGED_EDGED_FIN = Jagged-edged fin predictions may be inaccurate.
Warning.LISTENERS_AFFECTED = Listeners modified the flight simulation
@ -2150,7 +2190,9 @@ ExportDecalDialog.source.exception = Could not find decal source file ''{0}''.<b
ComponentPresetChooserDialog.title = Choose component preset
ComponentPresetChooserDialog.filter.label = Filter by text:
ComponentPresetChooserDialog.checkbox.filterAftDiameter = Match aft diameter
ComponentPresetChooserDialog.checkbox.filterAftDiameter.ttip = Search for components with the same aft diameter as the next component
ComponentPresetChooserDialog.checkbox.filterForeDiameter = Match fore diameter
ComponentPresetChooserDialog.checkbox.filterForeDiameter.ttip = Search for components with the same fore diameter as the previous component
ComponentPresetChooserDialog.menu.sortAsc = Sort Ascending
ComponentPresetChooserDialog.menu.sortDesc = Sort Descending
ComponentPresetChooserDialog.menu.units = Units

View File

@ -817,7 +817,6 @@ ringcompcfg.Radialdirection = :الإتجاه الشعاعي
ringcompcfg.radialdirectionfrom = :الإتجاه الشعاعي من خط الوسط للصاروخ
ringcompcfg.but.Reset = إعادة ضبط
ringcompcfg.but.Resetcomponant = .يعيد تعيين المكون إلى خط الوسط للصاروخ
ringcompcfg.EngineBlock.desc = <html>.كتلة المحرك تمنعه من التحرك للأمام في أنبوب الحامل للمحرك<br><br>.من أجل إضافة محرك ، قم بإنشاء أنبوب جسم أو أنبوب داخلي وقم بتمييزه على أنه حامل محرك في علامة تبويب المحرك
ringcompcfg.note.desc = .ملاحظة: الأنبوب الداخلي لن يؤثر على الديناميكا الهوائية للصاروخ حتى لو كان موجودًا خارج أنبوب الجسم
@ -971,6 +970,9 @@ RocketCompCfg.tab.Inside = من الداخل
RocketCompCfg.tab.RightSide = الجانب اليميني
RocketCompCfg.tab.LeftSide = الجانب اليساري
! ComponentInfo
ComponentInfo.EngineBlock = .كتلة المحرك تمنعه من التحرك للأمام في أنبوب الحامل للمحرك<br><br>.من أجل إضافة محرك ، قم بإنشاء أنبوب جسم أو أنبوب داخلي وقم بتمييزه على أنه حامل محرك في علامة تبويب المحرك
! BulkheadConfig
BulkheadCfg.tab.Diameter = :القطر
BulkheadCfg.tab.Thickness = :السماكة

View File

@ -568,7 +568,6 @@ ringcompcfg.Radialdirection = Radi
ringcompcfg.radialdirectionfrom = Radiální vzdálenost od smeru osy
ringcompcfg.but.Reset = Reset
ringcompcfg.but.Resetcomponant = Resetuj komponentu od osy rakety
ringcompcfg.EngineBlock.desc = <html>An <b>blok motoru</b> zastaví motor v pohybu vzad v motorové trubici.<br><br>Pokud se pridá motor vytvorí <b>telo</b> nebo <b>skrytou trubku tube</b> a oznací ji jako montá\u017E motoru v<em>Motoru</em> tab.
ringcompcfg.note.desc = Poznámka: Vnitrní trubka nemá effekt na aerodynamiku motoru i kdy\u017E je umístena mimo telo rakety.
@ -662,6 +661,9 @@ RocketCompCfg.title.Aftshoulder = Dr\u017E
RocketCompCfg.border.Foreshoulder = Dr\u017Eák prídi
!RocketCompCfg.lbl.Length = Délka:
! ComponentInfo
ComponentInfo.EngineBlock = An <b>blok motoru</b> zastaví motor v pohybu vzad v motorové trubici.<br><br>Pokud se pridá motor vytvorí <b>telo</b> nebo <b>skrytou trubku tube</b> a oznací ji jako montá\u017E motoru v<em>Motoru</em> tab.
! BulkheadConfig
BulkheadCfg.tab.Diameter = Prumer:
BulkheadCfg.tab.Thickness = Tlou\u0161tka:

View File

@ -625,7 +625,6 @@ ringcompcfg.Radialdirection = Radiale Richtung
ringcompcfg.radialdirectionfrom = Die radiale Richtung von der Raketenmittellinie
ringcompcfg.but.Reset = Zurücksetzen
ringcompcfg.but.Resetcomponant = Komponente auf die Raketenmittellinie zurücksetzen
ringcompcfg.EngineBlock.desc = <html>Eine <b>Motorhalterung</b> verhindert, dass der Motor sich im Rohr nach vorne bewegt.<br><br>Um einen Motor hinzuzufügen, ein <b>Körperrohr</b> oder ein <b>Innenrohr</b> hinzufügen und im Reiter <em>Motor</em> als Motorhalterung markieren.
ringcompcfg.note.desc = Hinweis: Innenrohre beeinflussen die Aerodynamik der Rakete nicht, auch wenn sie außerhalb des Körperohres liegen.
@ -719,6 +718,9 @@ RocketCompCfg.title.Aftshoulder = Schulter hinten
RocketCompCfg.border.Foreshoulder = Schulter vorn
!RocketCompCfg.lbl.Length = Length:
! ComponentInfo
ComponentInfo.EngineBlock = Eine <b>Motorhalterung</b> verhindert, dass der Motor sich im Rohr nach vorne bewegt.<br><br>Um einen Motor hinzuzufügen, ein <b>Körperrohr</b> oder ein <b>Innenrohr</b> hinzufügen und im Reiter <em>Motor</em> als Motorhalterung markieren.
! BulkheadConfig
BulkheadCfg.tab.Radius = Radius:
BulkheadCfg.tab.Thickness = Wandstärke:

View File

@ -869,6 +869,9 @@ RocketCompCfg.title.Noseconeshoulder = Acople de la ojiva
RocketCompCfg.ttip.Endcapped = Si el extremo del soporte est\u00e1 truncado.
RocketCompCfg.lbl.Componentname.ttip = El nombre del componente.
! ComponentInfo
ComponentInfo.EngineBlock = Un <b>ret\u00e9n de motor</b> impide que el motor se desplace hacia delante, por dentro del tubo porta motor.<br><br>Para a\u00f1adir un motor, cree un <b>Cuerpo tubular</b> o <b>Tubo interior</b> y des\u00edgnelo como porta motor en la pesta\u00f1a <em>Motor</em>.
RocketComponent.Position.ABSOLUTE = Extremo de la ojiva
RocketComponent.Position.AFTER = Despu\u00e9s del componente
RocketComponent.Position.BOTTOM = Extremo inferior del componente
@ -1705,7 +1708,6 @@ update.dlg.latestVersion = Usted est\u00e1 utilizando la \u00faltima versi\u00f3
ringcompcfg.Automatic = Autom\u00e1tico
ringcompcfg.Distancefrom = Distancia desde la l\u00ednea central del cohete:
ringcompcfg.EngineBlock.desc = <html>Un <b>ret\u00e9n de motor</b> impide que el motor se desplace hacia delante, por dentro del tubo porta motor.<br><br>Para a\u00f1adir un motor, cree un <b>Cuerpo tubular</b> o <b>Tubo interior</b> y des\u00edgnelo como porta motor en la pesta\u00f1a <em>Motor</em>.
ringcompcfg.InnerRadius = Radio interior:
ringcompcfg.Length = Longitud:
! Ring Component Config

View File

@ -861,6 +861,9 @@ RocketCompCfg.title.Noseconeshoulder = Epaulement du c\u00F4ne
RocketCompCfg.ttip.Endcapped = Pr\u00E9cise si l'arri\u00E8re du c\u00F4ne est clos.
RocketCompCfg.lbl.Componentname.ttip = Le nom de la pi\u00E8ce.
! ComponentInfo
ComponentInfo.EngineBlock = Un <b>bloc moteur </b> emp\u00EAche le moteur de se d\u00E9placer vers l'avant dans le tube porte moteur.<br><br>Pour ajouter un moteur, cr\u00E9er un <b>tube</b> ou un <b>tube interne</b> et marquer le comme porte moteur dans l'onglet <em>Moteur</em>.
RocketComponent.Position.ABSOLUTE = Pointe de l'ogive
RocketComponent.Position.AFTER = Apr\u00E8s la pi\u00E8ce parente
RocketComponent.Position.BOTTOM = Bas de la pi\u00E8ce parente
@ -1696,7 +1699,6 @@ update.dlg.latestVersion = Vous utilisez la derni\u00E8re version d'OpenRocket,
ringcompcfg.Automatic = Automatique
ringcompcfg.Distancefrom = Distance de l'axe central de la fus\u00E9e
ringcompcfg.EngineBlock.desc = <html>Un <b>bloc moteur </b> emp\u00EAche le moteur de se d\u00E9placer vers l'avant dans le tube porte moteur.<br><br>Pour ajouter un moteur, cr\u00E9er un <b>tube</b> ou un <b>tube interne</b> et marquer le comme porte moteur dans l'onglet <em>Moteur</em>.
ringcompcfg.InnerRadius = Diam\u00E8tre int\u00E9rieur
ringcompcfg.Length = Longueur
! Ring Component Config

View File

@ -626,7 +626,6 @@ ringcompcfg.Radialdirection = direzione Radiale:
ringcompcfg.radialdirectionfrom = La direzione radiale dall linea centrale del razzo
ringcompcfg.but.Reset = Azzera
ringcompcfg.but.Resetcomponant = Azzera il componente alla linea centrale del razzo
ringcompcfg.EngineBlock.desc = <html>Un <b>blocca motore</b> ferma il motore dal movimento in avanti nel tubo di montaggio del motore.<br><br>Per aggiungere un motore, crea un <b>corpo</b> o un <b>tubo interno</b> e segnalo come tubo di montaggio motore nella scheda <em>Motore</em> .
ringcompcfg.note.desc = Nota: Un tubo interno non modifica l'aerodinamica del razzo anche se e' posto all'esterno del corpo.
@ -720,6 +719,9 @@ RocketCompCfg.title.Aftshoulder = Spalla posteriore
RocketCompCfg.border.Foreshoulder = Spalla anteriore
!RocketCompCfg.lbl.Length = Lunghezza:
! ComponentInfo
ComponentInfo.EngineBlock = Un <b>blocca motore</b> ferma il motore dal movimento in avanti nel tubo di montaggio del motore.<br><br>Per aggiungere un motore, crea un <b>corpo</b> o un <b>tubo interno</b> e segnalo come tubo di montaggio motore nella scheda <em>Motore</em> .
! BulkheadConfig
BulkheadCfg.tab.Diameter = Diametro:
BulkheadCfg.tab.Thickness = Spessore:

View File

@ -656,7 +656,6 @@ ringcompcfg.Radialdirection = \u534A\u5F84\u65B9\u5411\u5411\u304D
ringcompcfg.radialdirectionfrom = \u30ED\u30B1\u30C3\u30C8\u30BB\u30F3\u30BF\u30FC\u30E9\u30A4\u30F3\u304B\u3089\u306E\u534A\u5F84\u65B9\u5411\u306E\u5411\u304D
ringcompcfg.but.Reset = \u30EA\u30BB\u30C3\u30C8
ringcompcfg.but.Resetcomponant = \u90E8\u54C1\u3092\u30ED\u30B1\u30C3\u30C8\u30BB\u30F3\u30BF\u30FC\u30E9\u30A4\u30F3\u306B\u30EA\u30BB\u30C3\u30C8\u3059\u308B
ringcompcfg.EngineBlock.desc = <html><b>\u30A8\u30F3\u30B8\u30F3\u30D6\u30ED\u30C3\u30AF</b>\u306F\u30E2\u30FC\u30BF\u30FC\u30DE\u30A6\u30F3\u30C8\u30C1\u30E5\u30FC\u30D6\u306E\u4E2D\u3067\u30E2\u30FC\u30BF\u30FC\u304C\u524D\u306B\u52D5\u304F\u306E\u3092\u6B62\u3081\u308B\u5F79\u5272\u3002<br><br>\u30E2\u30FC\u30BF\u30FC\u3092\u8FFD\u52A0\u3059\u308B\u306B\u306F <b>\u30DC\u30C7\u30A3\u30C1\u30E5\u30FC\u30D6</b>\u3082\u3057\u304F\u306F<b>\u30A4\u30F3\u30CA\u30FC\u30C1\u30E5\u30FC\u30D6</b>\u3092\u4F5C\u3063\u3066 and mark it as a motor mount in the <em>\u30E2\u30FC\u30BF\u30FC</em>\u30BF\u30D6\u3067\u30E2\u30FC\u30BF\u30FC\u30DE\u30A6\u30F3\u30C8\u3068\u3057\u3066\u30C1\u30A7\u30C3\u30AF\u3059\u308B\u3002
ringcompcfg.note.desc = \u30E1\u30E2\uFF1A\u30A4\u30F3\u30CA\u30FC\u30C1\u30E5\u30FC\u30D6\u306F\u30DC\u30C7\u30A3\u30C1\u30E5\u30FC\u30D6\u306E\u5916\u5074\u306B\u51FA\u306A\u3044\u9650\u308A\u306F\u7A7A\u529B\u3078\u306E\u5F71\u97FF\u306F\u7121\u3044
@ -750,6 +749,9 @@ RocketCompCfg.title.Aftshoulder = \u5F8C\u65B9\u30B7\u30E7\u30EB\u30C0\u30FC
RocketCompCfg.border.Foreshoulder = \u524D\u65B9\u30B7\u30E7\u30EB\u30C0\u30FC
!RocketCompCfg.lbl.Length
! ComponentInfo
ComponentInfo.EngineBlock = <b>\u30A8\u30F3\u30B8\u30F3\u30D6\u30ED\u30C3\u30AF</b>\u306F\u30E2\u30FC\u30BF\u30FC\u30DE\u30A6\u30F3\u30C8\u30C1\u30E5\u30FC\u30D6\u306E\u4E2D\u3067\u30E2\u30FC\u30BF\u30FC\u304C\u524D\u306B\u52D5\u304F\u306E\u3092\u6B62\u3081\u308B\u5F79\u5272\u3002<br><br>\u30E2\u30FC\u30BF\u30FC\u3092\u8FFD\u52A0\u3059\u308B\u306B\u306F <b>\u30DC\u30C7\u30A3\u30C1\u30E5\u30FC\u30D6</b>\u3082\u3057\u304F\u306F<b>\u30A4\u30F3\u30CA\u30FC\u30C1\u30E5\u30FC\u30D6</b>\u3092\u4F5C\u3063\u3066 and mark it as a motor mount in the <em>\u30E2\u30FC\u30BF\u30FC</em>\u30BF\u30D6\u3067\u30E2\u30FC\u30BF\u30FC\u30DE\u30A6\u30F3\u30C8\u3068\u3057\u3066\u30C1\u30A7\u30C3\u30AF\u3059\u308B\u3002
! BulkheadConfig
BulkheadCfg.tab.Diameter = \u534A\u5F84\uFF1A
BulkheadCfg.tab.Thickness = \u539A\u3055\uFF1A
@ -1046,7 +1048,7 @@ TCMotorSelPan.lbl.Datapoints = \u30C7\u30FC\u30BF\u70B9\uFF1A
TCMotorSelPan.lbl.Digest = \u30C0\u30A4\u30B8\u30A7\u30B9\u30C8\uFF1A
TCMotorSelPan.title.Thrustcurve = \u63A8\u529B\u5C65\u6B74\uFF1A
TCMotorSelPan.title.Thrust = \u63A8\u529B
TCMotorSelPan.delayBox.None = None
TCMotorSelPan.delayBox.None = Plugged (none)
TCMotorSelPan.noDescription = No description available.

View File

@ -751,7 +751,6 @@ ringcompcfg.Radialdirection = Radiale richting:
ringcompcfg.radialdirectionfrom = De radiale richting vanaf de middellijn van de raket
ringcompcfg.but.Reset = Herstel
ringcompcfg.but.Resetcomponant = Zet het onderdeel terug naar de raketmiddellijn
ringcompcfg.EngineBlock.desc = <html>Een <b>motorblok</b> voorkomt dat de motor voorwaarts beweegt in de buis van de motorsteun.<br><br>Om een motor toe te voegen, maak een <b>rompbuis</b> of <b>binnenbuis</b> en markeer het als een motorbevestiging in het <em>Motortabblad</em>.
ringcompcfg.note.desc = Opmerking: Een binnenbuis heeft geen invloed op de aërodynamica van de raket, zelfs niet als hij buiten de rompbuis is geplaatst.
@ -871,6 +870,9 @@ RocketCompCfg.border.Foreshoulder = Voorschouder
RocketCompCfg.lbl.InstanceCount = Aantal instanties
RocketCompCfg.lbl.InstanceSeparation = Instantie afscheiding
! ComponentInfo
ComponentInfo.EngineBlock = Een <b>motorblok</b> voorkomt dat de motor voorwaarts beweegt in de buis van de motorsteun.<br><br>Om een motor toe te voegen, maak een <b>rompbuis</b> of <b>binnenbuis</b> en markeer het als een motorbevestiging in het <em>Motortabblad</em>.
! BulkheadConfig
BulkheadCfg.tab.Diameter = Diameter:
BulkheadCfg.tab.Thickness = Dikte:

View File

@ -570,7 +570,6 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s.
ringcompcfg.radialdirectionfrom = Kierunek prostopad\u0142y do osi centralnej rakiety
ringcompcfg.but.Reset = Reset
ringcompcfg.but.Resetcomponant = Resetuj po\u0142o\u017Cenie cz\u0119\u015Bci wzgl\u0119dem osi rakiety
ringcompcfg.EngineBlock.desc = <html><b>Blokada silnika</b> unieruchamia silnik wewn\u0105trz elementu pe\u0142ni\u0105cego funkcj\u0119 gniazda silnikowego.<br><br>Aby doda\u0107 silnik, stwórz najpierw <b>Korpus rakiety</b> lub <b>rur\u0119 wewn\u0119trzn\u0105</b> i oznacz j\u0105 jako gniazdo silnika w zak\u0142adce <em>Silnik</em>.
ringcompcfg.note.desc = Uwaga: Rura wewn\u0119trzna nie wp\u0142ywa na aerodynamik\u0119 rakiety, nawet je\u017Celi znajduje si\u0119 poza korpusem.
@ -662,7 +661,11 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s.
RocketCompCfg.title.Noseconeshoulder = Wpust g\u0142owicy
RocketCompCfg.title.Aftshoulder = Wpust tylny
RocketCompCfg.border.Foreshoulder = Wpust przedni
!RocketCompCfg.lbl.Length
!RocketCompCfg.lbl.Length
! ComponentInfo
ComponentInfo.EngineBlock = <b>Blokada silnika</b> unieruchamia silnik wewn\u0105trz elementu pe\u0142ni\u0105cego funkcj\u0119 gniazda silnikowego.<br><br>Aby doda\u0107 silnik, stwórz najpierw <b>Korpus rakiety</b> lub <b>rur\u0119 wewn\u0119trzn\u0105</b> i oznacz j\u0105 jako gniazdo silnika w zak\u0142adce <em>Silnik</em>.
! BulkheadConfig
BulkheadCfg.tab.Diameter = \u015Arednica:

View File

@ -844,6 +844,9 @@ RocketCompCfg.title.Noseconeshoulder = Ressalto da ogiva
RocketCompCfg.ttip.Endcapped = Quando a extremidade do ressalto \u00e9 limitada.
RocketCompCfg.lbl.Componentname.ttip = Nome do componente.
! ComponentInfo
ComponentInfo.EngineBlock = Um <b>bloco do motor</b> p\u00e1ra o motor de se mover para a frente no tubo de montagem do motor.<br><br>Para adicionar um motor, criar um <b>tubo de corpo</b> ou <b>tubo interno</b> e marc\u00e1-lo como uma montagem do motor na aba <em>Motor</em>.
RocketComponent.Position.ABSOLUTE = Dica da ogiva
RocketComponent.Position.AFTER = Depois do componente pai
RocketComponent.Position.BOTTOM = Parte do componente pai
@ -1650,7 +1653,6 @@ update.dlg.latestVersion = Voc\u00ea est\u00e1 executando a vers\u00e3o mais rec
ringcompcfg.Automatic = Autom\u00e1tico
ringcompcfg.Distancefrom = Dist\u00e2ncia a partir da linha de centro do foguete
ringcompcfg.EngineBlock.desc = <html>Um <b>bloco do motor</b> p\u00e1ra o motor de se mover para a frente no tubo de montagem do motor.<br><br>Para adicionar um motor, criar um <b>tubo de corpo</b> ou <b>tubo interno</b> e marc\u00e1-lo como uma montagem do motor na aba <em>Motor</em>.
ringcompcfg.InnerRadius = Raio interno
ringcompcfg.Length = Comprimento
# Ring Component Config

View File

@ -804,7 +804,6 @@ ringcompcfg.Radialdirection = \u0420\u0430\u0434\u0438\u0430\u043B\u044C\u043D\u
ringcompcfg.radialdirectionfrom = \u0420\u0430\u0434\u0438\u0430\u043B\u044C\u043D\u043E\u0435 \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043E\u0442 \u043E\u0441\u0438 \u0440\u0430\u043A\u0435\u0442\u044B
ringcompcfg.but.Reset = \u0421\u0431\u0440\u043E\u0441
ringcompcfg.but.Resetcomponant = \u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442 \u043D\u0430 \u043E\u0441\u0438 \u0440\u0430\u043A\u0435\u0442\u044B
ringcompcfg.EngineBlock.desc = <html><b>\u0423\u043F\u043E\u0440 \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F</b> \u043F\u0440\u0435\u043F\u044F\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044E \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F \u0432\u043F\u0435\u0440\u0435\u0434 \u0432 \u0442\u0440\u0443\u0431\u0435.<br><br>\u0414\u043B\u044F \u0442\u043E\u0433\u043E \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044C, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 <b>\u041A\u043E\u0440\u043F\u0443\u0441\u043D\u0443\u044E \u0442\u0440\u0443\u0431\u0443</b> \u0438\u043B\u0438 <b>\u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044E\u044E \u0442\u0440\u0443\u0431\u0443</b> \u0438 \u043E\u0442\u043C\u0435\u0442\u044C\u0442\u0435 \u044D\u0442\u043E\u0442 \u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u043A\u0430\u043A \u043A\u0440\u0435\u043F\u0435\u0436 \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 <em>\u0414\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044C</em> .
ringcompcfg.note.desc = \u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435: \u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u0442\u0440\u0443\u0431\u0430 \u043D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442 \u043D\u0430 \u0430\u044D\u0440\u043E\u0434\u0438\u043D\u0430\u043C\u0438\u043A\u0443, \u0434\u0430\u0436\u0435 \u0431\u0443\u0434\u0443\u0447\u0438 \u0440\u0430\u0437\u043C\u0435\u0449\u0435\u043D\u043D\u043E\u0439 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u043C\u0438 \u043A\u043E\u0440\u043F\u0443\u0441\u0430.
@ -949,6 +948,9 @@ RocketCompCfg.tab.Inside = \u0412\u043D\u0443\u0442\u0440\u0438
RocketCompCfg.tab.RightSide = \u0421\u043F\u0440\u0430\u0432\u0430
RocketCompCfg.tab.LeftSide = \u0421\u043B\u0435\u0432\u0430
! ComponentInfo
ComponentInfo.EngineBlock = <b>\u0423\u043F\u043E\u0440 \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F</b> \u043F\u0440\u0435\u043F\u044F\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044E \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F \u0432\u043F\u0435\u0440\u0435\u0434 \u0432 \u0442\u0440\u0443\u0431\u0435.<br><br>\u0414\u043B\u044F \u0442\u043E\u0433\u043E \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044C, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 <b>\u041A\u043E\u0440\u043F\u0443\u0441\u043D\u0443\u044E \u0442\u0440\u0443\u0431\u0443</b> \u0438\u043B\u0438 <b>\u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044E\u044E \u0442\u0440\u0443\u0431\u0443</b> \u0438 \u043E\u0442\u043C\u0435\u0442\u044C\u0442\u0435 \u044D\u0442\u043E\u0442 \u044D\u043B\u0435\u043C\u0435\u043D\u0442 \u043A\u0430\u043A \u043A\u0440\u0435\u043F\u0435\u0436 \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044F \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 <em>\u0414\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u044C</em> .
! BulkheadConfig
BulkheadCfg.tab.Diameter = \u0414\u0438\u0430\u043C\u0435\u0442\u0440:
BulkheadCfg.tab.Thickness = \u0422\u043E\u043B\u0449\u0438\u043D\u0430:

View File

@ -703,7 +703,6 @@ ringcompcfg.Radialdirection = Radial direction:
ringcompcfg.radialdirectionfrom = The radial direction from the rocket centerline
ringcompcfg.but.Reset = Reset
ringcompcfg.but.Resetcomponant = Reset the component to the rocket centerline
ringcompcfg.EngineBlock.desc = <html>An <b>engine block</b> stops the motor from moving forwards in the motor mount tube.<br><br>In order to add a motor, create a <b>body tube</b> or <b>inner tube</b> and mark it as a motor mount in the <em>Motor</em> tab.
ringcompcfg.note.desc = Note: An inner tube will not affect the aerodynamics of the rocket even if it is located outside of the body tube.
@ -822,6 +821,9 @@ RocketCompCfg.title.Aftshoulder = Aft shoulder
RocketCompCfg.border.Foreshoulder = Fore shoulder
!RocketCompCfg.lbl.Length = Length:
! ComponentInfo
ComponentInfo.EngineBlock = An <b>engine block</b> stops the motor from moving forwards in the motor mount tube.<br><br>In order to add a motor, create a <b>body tube</b> or <b>inner tube</b> and mark it as a motor mount in the <em>Motor</em> tab.
! BulkheadConfig
BulkheadCfg.tab.Diameter = Diameter:
BulkheadCfg.tab.Thickness = Thickness:
@ -1119,7 +1121,7 @@ TCMotorSelPan.lbl.Datapoints = Data points:
TCMotorSelPan.lbl.Digest = Digest:
TCMotorSelPan.title.Thrustcurve = Thrust curve:
TCMotorSelPan.title.Thrust = Thrust
TCMotorSelPan.delayBox.None = None
TCMotorSelPan.delayBox.None = Plugged (none)
TCMotorSelPan.noDescription = No description available.
TCMotorSelPan.btn.checkAll = Select All
TCMotorSelPan.btn.checkNone = Clear All

View File

@ -933,6 +933,9 @@ RocketCompCfg.title.Noseconeshoulder = \u5934\u9525\u8FDE\u63A5\u59
RocketCompCfg.ttip.Endcapped = \u8FDE\u63A5\u5904\u7EC8\u7AEF\u662F\u5426\u6709\u76D6.
RocketCompCfg.lbl.Componentname.ttip = \u7EC4\u4EF6\u540D\u79F0.
! ComponentInfo
ComponentInfo.EngineBlock = <b>\u53D1\u52A8\u673A\u5EA7</b>\u7528\u4E8E\u9632\u6B62\u53D1\u52A8\u673A\u5411\u524D\u7A9C\u51FA\u7BAD\u4F53.<br><br>\u6DFB\u52A0\u53D1\u52A8\u673A\u524D\u8BF7\u5148\u6DFB\u52A0<b>\u7BAD\u4F53</b>\u6216<b>\u5185\u7BA1</b>\u5E76\u5728<em>\u53D1\u52A8\u673A</em>\u9875\u9762\u4E0A\u6807\u8BB0\u4E3A\u53D1\u52A8\u673A\u5EA7.
RocketComponent.Position.ABSOLUTE = \u5934\u9525\u5C16\u7AEF
RocketComponent.Position.AFTER = \u7236\u7EC4\u4EF6\u4E4B\u540E
RocketComponent.Position.BOTTOM = \u7236\u7EC4\u4EF6\u5E95\u90E8
@ -1787,7 +1790,6 @@ update.dlg.latestVersion = \u60A8\u4F7F\u7528\u7684\u5DF2\u7ECF\u662FOpenRocket\
ringcompcfg.Automatic = \u81EA\u52A8
ringcompcfg.Distancefrom = \u5230\u706B\u7BAD\u4E2D\u5FC3\u7EBF\u7684\u8DDD\u79BB
ringcompcfg.EngineBlock.desc = <html><b>\u53D1\u52A8\u673A\u5EA7</b>\u7528\u4E8E\u9632\u6B62\u53D1\u52A8\u673A\u5411\u524D\u7A9C\u51FA\u7BAD\u4F53.<br><br>\u6DFB\u52A0\u53D1\u52A8\u673A\u524D\u8BF7\u5148\u6DFB\u52A0<b>\u7BAD\u4F53</b>\u6216<b>\u5185\u7BA1</b>\u5E76\u5728<em>\u53D1\u52A8\u673A</em>\u9875\u9762\u4E0A\u6807\u8BB0\u4E3A\u53D1\u52A8\u673A\u5EA7.
ringcompcfg.InnerRadius = \u5185\u76F4\u5F84
ringcompcfg.Length = \u957F\u5EA6
! Ring Component Config

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -69,7 +69,7 @@ public interface AerodynamicCalculator extends Monitorable {
public AerodynamicCalculator newInstance();
/**
* Test component assembly for continuity (esp. diameter), and post any needed warnings
* Check component assembly for geometric problems and post any needed warnings
*/
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent component, WarningSet warnings);
public void checkGeometry(FlightConfiguration configuration, final RocketComponent component, WarningSet warnings);
}

View File

@ -21,6 +21,7 @@ import net.sf.openrocket.rocketcomponent.InstanceMap;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.PolyInterpolator;
@ -44,7 +45,6 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
private double cacheDiameter = -1;
private double cacheLength = -1;
public BarrowmanCalculator() {
}
@ -252,7 +252,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
if (calcMap == null)
buildCalcMap(configuration);
testIsContinuous(configuration, configuration.getRocket(), warnings);
checkGeometry(configuration, configuration.getRocket(), warnings);
final InstanceMap imap = configuration.getActiveInstances();
@ -276,7 +276,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
}
@Override
public void testIsContinuous(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){
public void checkGeometry(FlightConfiguration configuration, final RocketComponent treeRoot, WarningSet warnings ){
Queue<RocketComponent> queue = new LinkedList<>();
for (RocketComponent child : treeRoot.getChildren()) {
// Ignore inactive stages
@ -299,14 +299,20 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
}
SymmetricComponent sym = (SymmetricComponent) comp;
prevComp = sym.getPreviousSymmetricComponent();
if( null == prevComp){
prevComp = sym;
continue;
}
// Check for radius discontinuity
if ( !MathUtil.equals(sym.getForeRadius(), prevComp.getAftRadius())) {
warnings.add( Warning.DIAMETER_DISCONTINUITY, sym + ", " + prevComp);
if (sym.getForeRadius() - sym.getThickness() > MathUtil.EPSILON) {
warnings.add(Warning.OPEN_AIRFRAME_FORWARD, sym.toString());
}
} else {
// Check for radius discontinuity
// We're going to say it's discontinuous if it is presented to the user as having two different
// string representations. Hopefully there are enough digits in the string that it will
// present as different if the discontinuity is big enough to matter.
if (!UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*sym.getForeRadius()).equals(UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*prevComp.getAftRadius()))) {
// if ( !MathUtil.equals(sym.getForeRadius(), prevComp.getAftRadius())) {
warnings.add( Warning.DIAMETER_DISCONTINUITY, sym + ", " + prevComp);
}
}
// double x = component.toAbsolute(Coordinate.NUL)[0].x;
@ -318,9 +324,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
//}
//componentX = component.toAbsolute(new Coordinate(component.getLengthAerodynamic()))[0].x;
prevComp = sym;
}else if( comp instanceof ComponentAssembly ){
testIsContinuous(configuration, comp, warnings);
checkGeometry(configuration, comp, warnings);
}
}

View File

@ -358,6 +358,9 @@ public abstract class Warning {
/** A <code>Warning</code> that the body diameter is discontinuous. */
////Discontinuity in rocket body diameter.
public static final Warning DIAMETER_DISCONTINUITY = new Other(trans.get("Warning.DISCONTINUITY"));
/** A <code>Warning</code> that a ComponentAssembly has an open forward end */
public static final Warning OPEN_AIRFRAME_FORWARD = new Other(trans.get("Warning.OPEN_AIRFRAME_FORWARD"));
/** A <code>Warning</code> that the fins are thick compared to the rocket body. */
////Thick fins may not be modeled accurately.

View File

@ -0,0 +1,102 @@
package net.sf.openrocket.aerodynamics.barrowman;
import java.util.List;
import java.lang.Math;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.rocketcomponent.RailButton;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Transformation;
public class RailButtonCalc extends RocketComponentCalc {
private final static Logger log = LoggerFactory.getLogger(RailButtonCalc.class);
// values transcribed from Gowen and Perkins, "Drag of Circular Cylinders for a Wide Range
// of Reynolds Numbers and Mach Numbers", NACA Technical Note 2960, Figure 7
private static final List<Double> cdDomain = List.of(0.0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 1.0, 1.6, 2.0, 2.8, 100.0);
private static final List<Double> cdRange = List.of(1.2, 1.22, 1.25, 1.3, 1.4, 1.5, 1.6, 2.1, 1.5, 1.45, 1.33, 1.33);
private final RailButton button;
public RailButtonCalc(RocketComponent component) {
super(component);
// need to stash the button
button = (RailButton) component;
}
@Override
public double calculateFrictionCD(FlightConditions conditions, double componentCf, WarningSet warnings) {
// very small relative surface area, and slick
return 0.0;
}
@Override
public void calculateNonaxialForces(FlightConditions conditions, Transformation transform,
AerodynamicForces forces, WarningSet warnings) {
// Nothing to be done
}
@Override
public double calculatePressureCD(FlightConditions conditions,
double stagnationCD, double baseCD, WarningSet warnings) {
// grab relevant button params
final int instanceCount = button.getInstanceCount();
final Coordinate[] instanceOffsets = button.getInstanceOffsets();
// compute button reference area
final double buttonHt = button.getTotalHeight();
final double outerArea = buttonHt * button.getOuterDiameter();
final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight();
final double refArea = outerArea - notchArea;
// accumulate Cd contribution from each rail button
double CDmul = 0.0;
for (int i = 0; i < button.getInstanceCount(); i++) {
// compute boundary layer height at button location. I can't find a good reference for the
// formula, e.g. https://aerospaceengineeringblog.com/boundary-layers/ simply says it's the
// "scientific consensus".
double x = (button.toAbsolute(instanceOffsets[i]))[0].x; // location of button
double rex = calculateReynoldsNumber(x, conditions); // Reynolds number of button location
double del = 0.37 * x / Math.pow(rex, 0.2); // Boundary layer thickness
// compute mean airspeed over button
// this assumes airspeed changes linearly through boundary layer
// and that all parts of the railbutton contribute equally to Cd,
// neither of which is true but both are plenty close enough for our purposes
double mach;
if (buttonHt > del) {
// Case 1: button extends beyond boundary layer
// Mean velocity is 1/2 rocket velocity up to limit of boundary layer,
// full velocity after that
mach = (buttonHt - 0.5*del) * conditions.getMach()/buttonHt;
} else {
// Case 2: button is entirely within boundary layer
mach = MathUtil.map(buttonHt/2.0, 0, del, 0, conditions.getMach());
}
// look up Cd as function of speed. It's pretty constant as a function of Reynolds
// number when slow, so we can just use a function of Mach number
double cd = MathUtil.interpolate(cdDomain, cdRange, mach);
// Since later drag force calculations don't consider boundary layer, compute "effective Cd"
// based on rocket velocity
cd = cd * MathUtil.pow2(mach)/MathUtil.pow2(conditions.getMach());
// add to CDmul
CDmul += cd;
}
return CDmul * stagnationCD * refArea / conditions.getRefArea();
}
}

View File

@ -220,6 +220,9 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
(type != m.getMotorType())) {
return false;
}
if (!designation.equalsIgnoreCase(m.getDesignation()))
return false;
if (!commonName.equalsIgnoreCase(m.getCommonName()))
return false;

View File

@ -384,15 +384,19 @@ public class Simulation implements ChangeSource, Cloneable {
simulatedData = simulator.simulate(simulationConditions);
t2 = System.currentTimeMillis();
log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
// Set simulated info after simulation, will not be set in case of exception
} catch (SimulationException e) {
simulatedData = e.getFlightData();
throw e;
} finally {
// Set simulated info after simulation
simulatedConditions = options.clone();
simulatedConfigurationDescription = descriptor.format( this.rocket, getId());
simulatedRocketID = rocket.getFunctionalModID();
status = Status.UPTODATE;
fireChangeEvent();
} finally {
mutex.unlock("simulate");
}
}

View File

@ -56,7 +56,7 @@ public class DirectoryIterator extends FileIterator {
@Override
protected Pair<String, InputStream> findNext() {
protected Pair<File, InputStream> findNext() {
// Check if we're recursing
if (subIterator != null) {
@ -86,7 +86,7 @@ public class DirectoryIterator extends FileIterator {
}
InputStream is = new BufferedInputStream(new FileInputStream(file));
return new Pair<String, InputStream>(file.getName(), is);
return new Pair<>(file, is);
} catch (IOException e) {
logger.warn("Error opening file/directory " + file, e);
}

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.iterator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
@ -20,10 +21,10 @@ import net.sf.openrocket.util.Pair;
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class FileIterator implements Iterator<Pair<String, InputStream>> {
public abstract class FileIterator implements Iterator<Pair<File, InputStream>> {
private static final Logger logger = LoggerFactory.getLogger(FileIterator.class);
private Pair<String, InputStream> next = null;
private Pair<File, InputStream> next = null;
private int fileCount = 0;
@Override
@ -37,7 +38,7 @@ public abstract class FileIterator implements Iterator<Pair<String, InputStream>
@Override
public Pair<String, InputStream> next() {
public Pair<File, InputStream> next() {
if (next == null) {
next = findNext();
}
@ -45,7 +46,7 @@ public abstract class FileIterator implements Iterator<Pair<String, InputStream>
throw new NoSuchElementException("No more files");
}
Pair<String, InputStream> n = next;
Pair<File, InputStream> n = next;
next = null;
fileCount++;
return n;
@ -86,10 +87,10 @@ public abstract class FileIterator implements Iterator<Pair<String, InputStream>
}
/**
* Return the next pair of file name and InputStream.
* Return the next pair of file and InputStream.
*
* @return a pair with the file name and input stream reading the file.
* @return a pair with the file and input stream reading the file.
*/
protected abstract Pair<String, InputStream> findNext();
protected abstract Pair<File, InputStream> findNext();
}

View File

@ -78,7 +78,7 @@ public class ZipDirectoryIterator extends FileIterator {
@Override
protected Pair<String, InputStream> findNext() {
protected Pair<File, InputStream> findNext() {
if (entries == null) {
return null;
}
@ -90,7 +90,7 @@ public class ZipDirectoryIterator extends FileIterator {
if (name.startsWith(directory) && filter.accept(file)) {
try {
InputStream is = zipFile.getInputStream(entry);
return new Pair<String, InputStream>(name, is);
return new Pair<>(file, is);
} catch (IOException e) {
logger.error("IOException when reading ZIP file " + zipFileName, e);
}

View File

@ -44,7 +44,7 @@ public class ZipFileMotorLoader implements MotorLoader {
@Override
public List<ThrustCurveMotor.Builder> load(InputStream stream, String filename) throws IOException {
public List<ThrustCurveMotor.Builder> load(InputStream stream, String filename) throws IOException, IllegalArgumentException {
List<ThrustCurveMotor.Builder> motors = new ArrayList<>();
ZipInputStream is = new ZipInputStream(stream);

View File

@ -10,9 +10,11 @@ import net.sf.openrocket.util.Reflection;
//// BooleanSetter - set a boolean value
class BooleanSetter implements Setter {
private final Reflection.Method setMethod;
private Object[] extraParameters = null;
public BooleanSetter(Reflection.Method set) {
public BooleanSetter(Reflection.Method set, Object... parameters) {
setMethod = set;
this.extraParameters = parameters;
}
@Override
@ -20,12 +22,23 @@ class BooleanSetter implements Setter {
WarningSet warnings) {
s = s.trim();
final boolean setValue;
if (s.equalsIgnoreCase("true")) {
setMethod.invoke(c, true);
setValue = true;
} else if (s.equalsIgnoreCase("false")) {
setMethod.invoke(c, false);
setValue = false;
} else {
warnings.add(Warning.FILE_INVALID_PARAMETER);
return;
}
if (extraParameters != null) {
Object[] parameters = new Object[extraParameters.length + 1];
parameters[0] = setValue;
System.arraycopy(extraParameters, 0, parameters, 1, extraParameters.length);
setMethod.invoke(c, parameters);
} else {
setMethod.invoke(c, setValue);
}
}
}

View File

@ -248,7 +248,9 @@ class DocumentConfig {
Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class)));
setters.put("Transition:aftshouldercapped", new BooleanSetter(
Reflection.findMethod(Transition.class, "setAftShoulderCapped", boolean.class)));
setters.put("NoseCone:isflipped", new BooleanSetter(
Reflection.findMethod(NoseCone.class, "setFlipped", boolean.class, boolean.class), false));
// NoseCone - disable disallowed elements
setters.put("NoseCone:foreradius", null);
setters.put("NoseCone:foreshoulderradius", null);

View File

@ -1,7 +1,10 @@
package net.sf.openrocket.file.openrocket.savers;
import net.sf.openrocket.rocketcomponent.NoseCone;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class NoseConeSaver extends TransitionSaver {
@ -20,8 +23,23 @@ public class NoseConeSaver extends TransitionSaver {
@Override
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
NoseCone noseCone = (NoseCone) c;
super.addParams(c, elements);
// Transition handles nose cone saving as well
if (noseCone.isBaseRadiusAutomatic())
elements.add("<aftradius>auto " + noseCone.getBaseRadiusNoAutomatic() + "</aftradius>");
else
elements.add("<aftradius>" + noseCone.getBaseRadius() + "</aftradius>");
elements.add("<aftshoulderradius>" + noseCone.getShoulderRadius()
+ "</aftshoulderradius>");
elements.add("<aftshoulderlength>" + noseCone.getShoulderLength()
+ "</aftshoulderlength>");
elements.add("<aftshoulderthickness>" + noseCone.getShoulderThickness()
+ "</aftshoulderthickness>");
elements.add("<aftshouldercapped>" + noseCone.isShoulderCapped()
+ "</aftshouldercapped>");
elements.add("<isflipped>" + noseCone.isFlipped() + "</isflipped>");
}
}

View File

@ -30,8 +30,6 @@ public class TransitionSaver extends SymmetricComponentSaver {
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
super.addParams(c, elements);
net.sf.openrocket.rocketcomponent.Transition trans = (net.sf.openrocket.rocketcomponent.Transition) c;
boolean nosecone = (trans instanceof NoseCone);
Transition.Shape shape = trans.getType();
elements.add("<shape>" + shape.name().toLowerCase(Locale.ENGLISH) + "</shape>");
@ -41,14 +39,16 @@ public class TransitionSaver extends SymmetricComponentSaver {
if (shape.usesParameter()) {
elements.add("<shapeparameter>" + trans.getShapeParameter() + "</shapeparameter>");
}
if (!nosecone) {
if (trans.isForeRadiusAutomatic())
elements.add("<foreradius>auto " + trans.getForeRadiusNoAutomatic() + "</foreradius>");
else
elements.add("<foreradius>" + trans.getForeRadius() + "</foreradius>");
// Nose cones need other parameter saving, due to the isFlipped() parameter
if (trans instanceof NoseCone) {
return;
}
if (trans.isForeRadiusAutomatic())
elements.add("<foreradius>auto " + trans.getForeRadiusNoAutomatic() + "</foreradius>");
else
elements.add("<foreradius>" + trans.getForeRadius() + "</foreradius>");
if (trans.isAftRadiusAutomatic())
elements.add("<aftradius>auto " + trans.getAftRadiusNoAutomatic() + "</aftradius>");
@ -56,16 +56,14 @@ public class TransitionSaver extends SymmetricComponentSaver {
elements.add("<aftradius>" + trans.getAftRadius() + "</aftradius>");
if (!nosecone) {
elements.add("<foreshoulderradius>" + trans.getForeShoulderRadius()
+ "</foreshoulderradius>");
elements.add("<foreshoulderlength>" + trans.getForeShoulderLength()
+ "</foreshoulderlength>");
elements.add("<foreshoulderthickness>" + trans.getForeShoulderThickness()
+ "</foreshoulderthickness>");
elements.add("<foreshouldercapped>" + trans.isForeShoulderCapped()
+ "</foreshouldercapped>");
}
elements.add("<foreshoulderradius>" + trans.getForeShoulderRadius()
+ "</foreshoulderradius>");
elements.add("<foreshoulderlength>" + trans.getForeShoulderLength()
+ "</foreshoulderlength>");
elements.add("<foreshoulderthickness>" + trans.getForeShoulderThickness()
+ "</foreshoulderthickness>");
elements.add("<foreshouldercapped>" + trans.isForeShoulderCapped()
+ "</foreshouldercapped>");
elements.add("<aftshoulderradius>" + trans.getAftShoulderRadius()
+ "</aftshoulderradius>");

View File

@ -65,7 +65,11 @@ public class PodSetDTO extends BasePartDTO implements AttachableParts {
} else if (child instanceof BodyTube) {
addAttachedPart(new BodyTubeDTO((BodyTube) child));
} else if (child instanceof NoseCone) {
addAttachedPart(new NoseConeDTO((NoseCone) child));
if (((NoseCone) child).isFlipped()) {
addAttachedPart(new TransitionDTO((NoseCone) child));
} else {
addAttachedPart(new NoseConeDTO((NoseCone) child));
}
} else if (child instanceof Transition) {
addAttachedPart(new TransitionDTO((Transition) child));
}

View File

@ -93,8 +93,12 @@ public class StageDTO {
externalPart.add(theExternalPartDTO);
}
private NoseConeDTO toNoseConeDTO(NoseCone nc) {
return new NoseConeDTO(nc);
private AbstractTransitionDTO toNoseConeDTO(NoseCone nc) {
if (nc.isFlipped()) {
return new TransitionDTO(nc);
} else {
return new NoseConeDTO(nc);
}
}
private BodyTubeDTO toBodyTubeDTO(BodyTube bt) {

View File

@ -78,8 +78,16 @@ public class ClassBasedTranslator implements Translator {
public String getBaseText(String base, String translation) {
return translator.getBaseText(base, translation);
}
@Override
public boolean checkIfKeyExists(String key) {
try {
get(key);
return true;
} catch (MissingResourceException e) {
return false;
}
}
private String findClassName() {
Throwable trace = new Throwable();

View File

@ -1,5 +1,7 @@
package net.sf.openrocket.l10n;
import java.util.MissingResourceException;
/**
* A translator implementation that returns the logical key in brackets instead
* of an actual translation. The class optionally verifies that the translation
@ -47,6 +49,15 @@ public class DebugTranslator implements Translator {
}
return translation;
}
@Override
public boolean checkIfKeyExists(String key) {
try {
translator.get(key);
return true;
} catch (MissingResourceException e) {
return false;
}
}
}

View File

@ -52,7 +52,16 @@ public class ExceptionSuppressingTranslator implements Translator {
public String getBaseText(String base, String translation) {
return translator.getBaseText(base, translation);
}
@Override
public boolean checkIfKeyExists(String key) {
try {
translator.get(key);
return true;
} catch (MissingResourceException e) {
return false;
}
}
private static synchronized void handleError(String key, MissingResourceException e) {
if (!errorReported) {

View File

@ -66,4 +66,14 @@ public class ResourceBundleTranslator implements Translator {
}
return translation;
}
@Override
public boolean checkIfKeyExists(String key) {
try {
get(key);
return true;
} catch (MissingResourceException e) {
return false;
}
}
}

View File

@ -50,5 +50,12 @@ public interface Translator {
* @return the base text, or the translation if not found.
*/
public String getBaseText(String base, String translation);
/**
* Checks whether a certain translation key exists.
* @param key the key to check
* @return true if it exists, false otherwise
*/
public boolean checkIfKeyExists(String key);
}

View File

@ -56,7 +56,7 @@ public class DefaultSimulationModifierService implements SimulationModifierServi
*/
addModifier("optimization.modifier.nosecone.length", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Length");
addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius", "isAftRadiusAutomatic");
addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "BaseRadius", "isBaseRadiusAutomatic");
addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness", "isFilled");
addModifier("optimization.modifier.transition.length", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Length");

View File

@ -241,6 +241,7 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC
@Override
public void clearConfigListeners() {
super.clearConfigListeners();
// StageSeparationConfiguration also has config listeners, so clear them as well
StageSeparationConfiguration thisConfig = getSeparationConfiguration();
thisConfig.clearConfigListeners();
}

View File

@ -549,6 +549,7 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou
@Override
public void clearConfigListeners() {
super.clearConfigListeners();
// The motor config also has listeners, so clear them as well
getDefaultMotorConfig().clearConfigListeners();
}
}

View File

@ -482,6 +482,7 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
@Override
public void clearConfigListeners() {
super.clearConfigListeners();
// The motor config also has listeners, so clear them as well
getDefaultMotorConfig().clearConfigListeners();
}

View File

@ -1,18 +1,16 @@
package net.sf.openrocket.rocketcomponent;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.Decal;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.StateChangeListener;
import java.util.EventObject;
/**
* Rocket nose cones of various types. Implemented as a transition with the
* fore radius == 0.
* <p>
* The normal nose cone can be converted to a tail cone by setting the {@link #isFlipped} parameter.
* This will flip all the aft side dimensions with the fore side dimensions.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
@ -21,6 +19,7 @@ public class NoseCone extends Transition implements InsideColorComponent {
private static final Translator trans = Application.getTranslator();
private InsideColorComponentHandler insideColorComponentHandler = new InsideColorComponentHandler(this);
private boolean isFlipped; // If true, the nose cone is converted to a tail cone
/********* Constructors **********/
public NoseCone() {
@ -29,16 +28,12 @@ public class NoseCone extends Transition implements InsideColorComponent {
public NoseCone(Transition.Shape type, double length, double radius) {
super();
this.isFlipped = false;
super.setType(type);
super.setForeRadiusAutomatic(false);
super.setForeRadius(0);
super.setForeShoulderLength(0);
super.setForeShoulderRadius(0.9 * radius);
super.setForeShoulderThickness(0);
super.setForeShoulderCapped(filled);
super.setThickness(0.002);
super.setLength(length);
super.setClipped(false);
resetForeRadius();
super.setAftRadiusAutomatic(false);
super.setAftRadius(radius);
@ -46,86 +41,227 @@ public class NoseCone extends Transition implements InsideColorComponent {
super.displayOrder_side = 1; // Order for displaying the component in the 2D side view
super.displayOrder_back = 0; // Order for displaying the component in the 2D back view
}
/********** Get/set methods for component parameters **********/
@Override
public double getForeRadius() {
return 0;
}
@Override
public void setForeRadius(double r) {
// No-op
}
@Override
public boolean isForeRadiusAutomatic() {
return false;
}
@Override
public void setForeRadiusAutomatic(boolean b) {
// No-op
/********** Nose cone dimensions **********/
/**
* Returns the base radius of the nose cone (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #getAftRadius()} because it works for both normal and flipped nose cones.
*/
public double getBaseRadius() {
return isFlipped ? getForeRadius() : getAftRadius();
}
@Override
public boolean usesPreviousCompAutomatic() {
return false;
/**
* Returns the raw base radius of the nose cone (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #getAftRadiusNoAutomatic()} because it works for both normal and flipped nose cones.
*/
public double getBaseRadiusNoAutomatic() {
return isFlipped ? getForeRadiusNoAutomatic() : getAftRadiusNoAutomatic();
}
@Override
public double getForeShoulderLength() {
return 0;
/**
* Sets the base radius of the nose cone (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftRadius(double)} because it works for both normal and flipped nose cones.
*/
public void setBaseRadius(double radius) {
if (isFlipped) {
setForeRadius(radius);
} else {
setAftRadius(radius);
}
}
@Override
public double getForeShoulderRadius() {
return 0;
/**
* Returns whether the base radius of the nose cone takes it settings from the previous/next component
* (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #isAftRadiusAutomatic()} because it works for both normal and flipped nose cones.
*/
public boolean isBaseRadiusAutomatic() {
return isFlipped ? isForeRadiusAutomatic() : isAftRadiusAutomatic();
}
@Override
public double getForeShoulderThickness() {
return 0;
/**
* Sets whether the base radius of the nose cone takes it settings from the previous/next component
* (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftRadiusAutomatic(boolean)} because it works for both normal and flipped nose cones.
*/
public void setBaseRadiusAutomatic(boolean auto) {
if (isFlipped) {
setForeRadiusAutomatic(auto);
} else {
setAftRadiusAutomatic(auto);
}
}
@Override
public boolean isForeShoulderCapped() {
return false;
/**
* Returns the shoulder length, regardless of how the nose cone is flipped (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #getAftShoulderLength()} because it works for both normal and flipped nose cones.
*/
public double getShoulderLength() {
return isFlipped ? getForeShoulderLength() : getAftShoulderLength();
}
@Override
public void setForeShoulderCapped(boolean capped) {
// No-op
/**
* Sets the shoulder length (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftShoulderLength(double)} because it works for both normal and flipped nose cones.
*/
public void setShoulderLength(double length) {
if (isFlipped) {
setForeShoulderLength(length);
} else {
setAftShoulderLength(length);
}
}
@Override
public void setForeShoulderLength(double foreShoulderLength) {
// No-op
/**
* Returns the shoulder radius (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #getAftShoulderRadius()} because it works for both normal and flipped nose cones.
*/
public double getShoulderRadius() {
return isFlipped ? getForeShoulderRadius() : getAftShoulderRadius();
}
@Override
public void setForeShoulderRadius(double foreShoulderRadius) {
// No-op
/**
* Sets the shoulder radius (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftShoulderRadius(double)} because it works for both normal and flipped nose cones.
*/
public void setShoulderRadius(double radius) {
if (isFlipped) {
setForeShoulderRadius(radius);
} else {
setAftShoulderRadius(radius);
}
}
@Override
public void setForeShoulderThickness(double foreShoulderThickness) {
// No-op
/**
* Returns the shoulder thickness (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #getAftShoulderThickness()} because it works for both normal and flipped nose cones.
*/
public double getShoulderThickness() {
return isFlipped ? getForeShoulderThickness() : getAftShoulderThickness();
}
/**
* Sets the shoulder thickness (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftShoulderRadius(double)} because it works for both normal and flipped nose cones.
*/
public void setShoulderThickness(double thickness) {
if (isFlipped) {
setForeShoulderThickness(thickness);
} else {
setAftShoulderThickness(thickness);
}
}
/**
* Returns the shoulder cap (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #isAftShoulderCapped()} because it works for both normal and flipped nose cones.
*/
public boolean isShoulderCapped() {
return isFlipped ? isForeShoulderCapped() : isAftShoulderCapped();
}
/**
* Sets the shoulder cap (independent of whether the nose cone is flipped or not).
* This method should be used over {@link #setAftShoulderCapped(boolean)} because it works for both normal and flipped nose cones.
*/
public void setShoulderCapped(boolean capped) {
if (isFlipped) {
setForeShoulderCapped(capped);
} else {
setAftShoulderCapped(capped);
}
}
/********** Other **********/
/**
* Return true if the nose cone is flipped, i.e. converted to a tail cone, false if it is a regular nose cone.
*/
public boolean isFlipped() {
return isFlipped;
}
/**
* Set the nose cone to be flipped, i.e. converted to a tail cone, or set it to be a regular nose cone.
* @param flipped if true, the nose cone is converted to a tail cone, if false it is a regular nose cone.
* @param sanityCheck whether to check if the auto radius parameter can be used for the new nose cone orientation
*/
public void setFlipped(boolean flipped, boolean sanityCheck) {
for (RocketComponent listener : configListeners) {
if (listener instanceof NoseCone) {
((NoseCone) listener).setFlipped(flipped, sanityCheck);
}
}
if (isFlipped == flipped) {
return;
}
boolean previousByPass = isBypassComponentChangeEvent();
setBypassChangeEvent(true);
if (flipped) {
setForeRadius(getAftRadiusNoAutomatic());
setForeRadiusAutomatic(isAftRadiusAutomatic(), sanityCheck);
setForeShoulderLength(getAftShoulderLength());
setForeShoulderRadius(getAftShoulderRadius());
setForeShoulderThickness(getAftShoulderThickness());
setForeShoulderCapped(isAftShoulderCapped());
resetAftRadius();
} else {
setAftRadius(getForeRadiusNoAutomatic());
setAftRadiusAutomatic(isForeRadiusAutomatic(), sanityCheck);
setAftShoulderLength(getForeShoulderLength());
setAftShoulderRadius(getForeShoulderRadius());
setAftShoulderThickness(getForeShoulderThickness());
setAftShoulderCapped(isForeShoulderCapped());
resetForeRadius();
}
setBypassChangeEvent(previousByPass);
isFlipped = flipped;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
/**
* Set the nose cone to be flipped, i.e. converted to a tail cone, or set it to be a regular nose cone.
* @param flipped if true, the nose cone is converted to a tail cone, if false it is a regular nose cone.
*/
public void setFlipped(boolean flipped) {
setFlipped(flipped, true);
}
private void resetForeRadius() {
setForeRadius(0);
setForeRadiusAutomatic(false);
setForeShoulderLength(0);
setForeShoulderRadius(0);
setForeShoulderThickness(0);
setForeShoulderCapped(false);
}
private void resetAftRadius() {
setAftRadius(0);
setAftRadiusAutomatic(false);
setAftShoulderLength(0);
setAftShoulderRadius(0);
setAftShoulderThickness(0);
setAftShoulderCapped(false);
}
@Override
public boolean isClipped() {
return false;
}
@Override
public void setClipped(boolean b) {
// No-op
}
/********** RocketComponent methods **********/
@ -136,9 +272,12 @@ public class NoseCone extends Transition implements InsideColorComponent {
@Override
protected void loadFromPreset(ComponentPreset preset) {
// We first need to unflip, because the preset loading always applies settings for a normal nose cone (e.g. aft diameter)
boolean flipped = isFlipped;
setFlipped(false);
//Many parameters are handled by the super class Transition.loadFromPreset
super.loadFromPreset(preset);
setFlipped(flipped);
}
/**

View File

@ -207,12 +207,6 @@ public class RailButton extends ExternalComponent implements AnglePositionable,
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public boolean isAerodynamic(){
// TODO: implement aerodynamics
return false;
}
@Override
public double getAngleOffset(){

View File

@ -177,6 +177,7 @@ public abstract class RecoveryDevice extends MassObject implements FlightConfigu
@Override
public void clearConfigListeners() {
super.clearConfigListeners();
// The DeploymentConfiguration also has listeners, so clear them as well
DeploymentConfiguration thisConfig = getDeploymentConfigurations().getDefault();
thisConfig.clearConfigListeners();
}

View File

@ -406,37 +406,37 @@ public class Rocket extends ComponentAssembly {
* and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
* changes.
*/
public void loadFrom(Rocket r) {
public void loadFrom(Rocket source) {
// Store list of components to invalidate after event has been fired
List<RocketComponent> toInvalidate = this.copyFrom(r);
List<RocketComponent> toInvalidate = this.copyFrom(source);
int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
if (this.massModID != r.massModID)
if (this.massModID != source.massModID)
type |= ComponentChangeEvent.MASS_CHANGE;
if (this.aeroModID != r.aeroModID)
if (this.aeroModID != source.aeroModID)
type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
// Loading a rocket is always a tree change since the component objects change
type |= ComponentChangeEvent.TREE_CHANGE;
this.modID = r.modID;
this.massModID = r.massModID;
this.aeroModID = r.aeroModID;
this.treeModID = r.treeModID;
this.functionalModID = r.functionalModID;
this.refType = r.refType;
this.customReferenceLength = r.customReferenceLength;
this.stageMap = r.stageMap;
this.modID = source.modID;
this.massModID = source.massModID;
this.aeroModID = source.aeroModID;
this.treeModID = source.treeModID;
this.functionalModID = source.functionalModID;
this.refType = source.refType;
this.customReferenceLength = source.customReferenceLength;
this.stageMap = source.stageMap;
// these flight configurations need to reference the _this_ Rocket:
this.configSet.reset();
this.configSet.setDefault(new FlightConfiguration(this));
for (FlightConfigurationId key : r.configSet.map.keySet()) {
for (FlightConfigurationId key : source.configSet.map.keySet()) {
this.configSet.set(key, new FlightConfiguration(this, key));
}
this.selectedConfiguration = this.configSet.get(r.getSelectedConfiguration().getId());
this.selectedConfiguration = this.configSet.get(source.getSelectedConfiguration().getId());
this.perfectFinish = r.perfectFinish;
this.perfectFinish = source.perfectFinish;
this.checkComponentStructure();
@ -453,25 +453,6 @@ public class Rocket extends ComponentAssembly {
/////// Implement the ComponentChangeListener lists
/**
* Creates a new EventListenerList for this component. This is necessary when cloning
* the structure.
*/
public void resetListeners() {
// System.out.println("RESETTING LISTENER LIST of Rocket "+this);
listenerList = new HashSet<EventListener>();
}
public void printListeners() {
System.out.println("" + this + " has " + listenerList.size() + " listeners:");
int i = 0;
for (EventListener l : listenerList) {
System.out.println(" " + (i) + ": " + l);
i++;
}
}
@Override
public void addComponentChangeListener(ComponentChangeListener l) {
checkState();
@ -497,10 +478,6 @@ public class Rocket extends ComponentAssembly {
try {
checkState();
{ // vvvv DEVEL vvvv
//System.err.println("fireEvent@rocket.");
} // ^^^^ DEVEL ^^^^
// Update modification ID's only for normal (not undo/redo) events
if (!cce.isUndoChange()) {
modID = UniqueID.next();

View File

@ -448,6 +448,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
}
// Make sure the config listeners aren't cloned
clone.configListeners = new LinkedList<>();
clone.bypassComponentChangeEvent = false;
return clone;
}
@ -603,7 +604,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
for (RocketComponent listener : configListeners) {
listener.setBypassChangeEvent(false);
listener.setMassOverridden(o);
listener.setBypassChangeEvent(false);
listener.setBypassChangeEvent(true);
}
if (massOverridden == o) {
@ -2277,7 +2278,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
this.bypassComponentChangeEvent = newValue;
}
public boolean getBypassComponentChangeEvent() {
/**
* Returns whether the current component if ignoring ComponentChangeEvents.
* @return true if the component is ignoring ComponentChangeEvents.
*/
public boolean isBypassComponentChangeEvent() {
return this.bypassComponentChangeEvent;
}
@ -2287,7 +2292,18 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
* @return true if listener was successfully added, false if not
*/
public boolean addConfigListener(RocketComponent listener) {
if (listener == null || configListeners.contains(listener) || listener == this) {
if (isBypassComponentChangeEvent()) {
// This is a precaution. If you are multi-comp editing and the current component is bypassing events,
// the editing will be REALLY weird, see GitHub issue #1956.
throw new IllegalStateException("Cannot add config listener while bypassing events");
}
if (listener == null) {
return false;
}
if (listener.getConfigListeners().size() > 0) {
throw new IllegalArgumentException("Listener already has config listeners");
}
if (configListeners.contains(listener) || listener == this) {
return false;
}
configListeners.add(listener);
@ -2555,6 +2571,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
this.displayOrder_side = src.displayOrder_side;
this.displayOrder_back = src.displayOrder_back;
this.configListeners = new LinkedList<>();
this.bypassComponentChangeEvent = false;
if (this instanceof InsideColorComponent && src instanceof InsideColorComponent) {
InsideColorComponentHandler icch = new InsideColorComponentHandler(this);
icch.copyFrom(((InsideColorComponent) src).getInsideColorComponentHandler());

View File

@ -10,7 +10,7 @@ import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.util.BoundingBox;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
/**
* Class for an axially symmetric rocket component generated by rotating
@ -621,7 +621,11 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
while (0 <= searchSiblingIndex) {
final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex);
if (searchSibling instanceof SymmetricComponent) {
return (SymmetricComponent) searchSibling;
SymmetricComponent candidate = (SymmetricComponent) searchSibling;
if (inline(candidate)) {
return candidate;
}
return null;
}
--searchSiblingIndex;
}
@ -658,9 +662,12 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
if(searchParent instanceof ComponentAssembly){
while (searchSiblingIndex < searchParent.getChildCount()) {
final RocketComponent searchSibling = searchParent.getChild(searchSiblingIndex);
if (searchSibling instanceof SymmetricComponent) {
return (SymmetricComponent) searchSibling;
SymmetricComponent candidate = (SymmetricComponent) searchSibling;
if (inline(candidate)) {
return candidate;
}
return null;
}
++searchSiblingIndex;
}
@ -671,6 +678,29 @@ public abstract class SymmetricComponent extends BodyComponent implements BoxBou
return null;
}
/***
* Determine whether a candidate symmetric component is in line with us
*
*/
private boolean inline(final SymmetricComponent candidate) {
// if we share a parent, we are in line
if (this.parent == candidate.parent)
return true;
// if both of our parents are either not ring instanceable, or
// have a radial offset of 0 from their centerline, we are in line.
if ((this.parent instanceof RingInstanceable) &&
(!MathUtil.equals(this.parent.getRadiusMethod().getRadius(this.parent.parent, this, this.parent.getRadiusOffset()), 0)))
return false;
if ((candidate.parent instanceof RingInstanceable) &&
(!MathUtil.equals(candidate.parent.getRadiusMethod().getRadius(candidate.parent.parent, candidate, candidate.parent.getRadiusOffset()), 0)))
return false;
return true;
}
/**
* Checks whether the component uses the previous symmetric component for its auto diameter.
*/

View File

@ -5,17 +5,13 @@ import static net.sf.openrocket.util.MathUtil.pow2;
import static net.sf.openrocket.util.MathUtil.pow3;
import java.util.Collection;
import java.util.EventObject;
import net.sf.openrocket.appearance.Appearance;
import net.sf.openrocket.appearance.Decal;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.StateChangeListener;
public class Transition extends SymmetricComponent implements InsideColorComponent {
@ -27,8 +23,8 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
private double shapeParameter;
private boolean clipped; // Not to be read - use isClipped(), which may be overridden
private double foreRadius, aftRadius;
private boolean autoForeRadius, autoAftRadius2; // Whether the start radius is automatic
protected double foreRadius, aftRadius; // Warning: avoid using these directly, use getForeRadius() and getAftRadius() instead (because the definition of the two can change for flipped nose cones)
protected boolean autoForeRadius, autoAftRadius; // Whether the start radius is automatic
private double foreShoulderRadius;
@ -53,7 +49,7 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
this.aftRadius = DEFAULT_RADIUS;
this.length = DEFAULT_RADIUS * 3;
this.autoForeRadius = true;
this.autoAftRadius2 = true;
this.autoAftRadius = true;
this.type = Shape.CONICAL;
this.shapeParameter = 0;
@ -81,19 +77,24 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
@Override
public double getForeRadius() {
if (isForeRadiusAutomatic()) {
// Get the automatic radius from the front
double r = -1;
SymmetricComponent c = this.getPreviousSymmetricComponent();
if (c != null) {
r = c.getFrontAutoRadius();
}
if (r < 0)
r = DEFAULT_RADIUS;
return r;
return getAutoForeRadius();
}
return foreRadius;
}
/**
* Returns the automatic radius from the front, taken from the previous component. Returns the default radius if there
* is no previous component.
*/
protected double getAutoForeRadius() {
SymmetricComponent c = this.getPreviousSymmetricComponent();
if (c != null) {
return c.getFrontAutoRadius();
} else {
return DEFAULT_RADIUS;
}
}
/**
* Return the fore radius that was manually entered, so not the value that the component received from automatic
* fore radius.
@ -136,13 +137,24 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
return autoForeRadius;
}
public void setForeRadiusAutomatic(boolean auto) {
/**
* Set the fore radius to automatic mode (takes its value from the previous symmetric component's radius).
*
* @param auto whether to set the fore radius to automatic mode
* @param sanityCheck whether to sanity check auto mode for whether there is a previous component of which you can take the radius
*/
public void setForeRadiusAutomatic(boolean auto, boolean sanityCheck) {
for (RocketComponent listener : configListeners) {
if (listener instanceof Transition) {
((Transition) listener).setForeRadiusAutomatic(auto);
}
}
// You can only set the auto fore radius if it is possible
if (sanityCheck) {
auto = auto && canUsePreviousCompAutomatic();
}
if (autoForeRadius == auto)
return;
@ -152,25 +164,34 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public void setForeRadiusAutomatic(boolean auto) {
setForeRadiusAutomatic(auto, false);
}
//////// Aft radius /////////
@Override
public double getAftRadius() {
if (isAftRadiusAutomatic()) {
// Return the auto radius from the rear
double r = -1;
SymmetricComponent c = this.getNextSymmetricComponent();
if (c != null) {
r = c.getRearAutoRadius();
}
if (r < 0)
r = DEFAULT_RADIUS;
return r;
return getAutoAftRadius();
}
return aftRadius;
}
/**
* Returns the automatic radius from the rear, taken from the next component. Returns the default radius if there
* is no next component.
*/
protected double getAutoAftRadius() {
SymmetricComponent c = this.getNextSymmetricComponent();
if (c != null) {
return c.getRearAutoRadius();
} else {
return DEFAULT_RADIUS;
}
}
/**
* Return the aft radius that was manually entered, so not the value that the component received from automatic
* zft radius.
@ -191,10 +212,10 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
}
}
if ((this.aftRadius == radius) && (autoAftRadius2 == false))
if ((this.aftRadius == radius) && (autoAftRadius == false))
return;
this.autoAftRadius2 = false;
this.autoAftRadius = false;
this.aftRadius = Math.max(radius, 0);
if (doClamping && this.thickness > this.foreRadius && this.thickness > this.aftRadius)
@ -210,25 +231,40 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
@Override
public boolean isAftRadiusAutomatic() {
return autoAftRadius2;
return autoAftRadius;
}
public void setAftRadiusAutomatic(boolean auto) {
/**
* Set the aft radius to automatic mode (takes its value from the next symmetric component's radius).
*
* @param auto whether to set the aft radius to automatic mode
* @param sanityCheck whether to sanity check auto mode for whether there is a next component of which you can take the radius
*/
public void setAftRadiusAutomatic(boolean auto, boolean sanityCheck) {
for (RocketComponent listener : configListeners) {
if (listener instanceof Transition) {
((Transition) listener).setAftRadiusAutomatic(auto);
}
}
if (autoAftRadius2 == auto)
// You can only set the auto aft radius if it is possible
if (sanityCheck) {
auto = auto && canUseNextCompAutomatic();
}
if (autoAftRadius == auto)
return;
autoAftRadius2 = auto;
autoAftRadius = auto;
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public void setAftRadiusAutomatic(boolean auto) {
setAftRadiusAutomatic(auto, false);
}
//// Radius automatics
@ -257,6 +293,32 @@ public class Transition extends SymmetricComponent implements InsideColorCompone
return isAftRadiusAutomatic();
}
/**
* Checks whether this component can use the automatic radius of the previous symmetric component.
* @return false if there is no previous symmetric component, or if the previous component already has this component
* as its auto dimension reference
*/
public boolean canUsePreviousCompAutomatic() {
SymmetricComponent referenceComp = getPreviousSymmetricComponent();
if (referenceComp == null) {
return false;
}
return !referenceComp.usesNextCompAutomatic();
}
/**
* Checks whether this component can use the automatic radius of the next symmetric component.
* @return false if there is no next symmetric component, or if the next component already has this component
* as its auto dimension reference
*/
public boolean canUseNextCompAutomatic() {
SymmetricComponent referenceComp = getNextSymmetricComponent();
if (referenceComp == null) {
return false;
}
return !referenceComp.usesPreviousCompAutomatic();
}
//////// Type & shape /////////

View File

@ -7,7 +7,9 @@ import java.util.Deque;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.IgnitionEvent;
import net.sf.openrocket.motor.MotorConfiguration;
@ -58,12 +60,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// this is just a list of simulation branches to
Deque<SimulationStatus> toSimulate = new ArrayDeque<SimulationStatus>();
FlightData flightData;
@Override
public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
// Set up flight data
FlightData flightData = new FlightData();
flightData = new FlightData();
// Set up rocket configuration
this.fcid = simulationConditions.getFlightConfigurationID();
@ -82,6 +86,13 @@ public class BasicEventSimulationEngine implements SimulationEngine {
throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noMotorsDefined"));
}
// Can't calculate stability
if (currentStatus.getSimulationConditions().getAerodynamicCalculator()
.getCP(currentStatus.getConfiguration(),
new FlightConditions(currentStatus.getConfiguration()),
new WarningSet()).weight < MathUtil.EPSILON)
throw new SimulationException(trans.get("BasicEventSimulationEngine.error.cantCalculateStability"));
// Problems that let us simulate, but result is likely bad
// No recovery device
@ -268,6 +279,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Add FlightEvent for Abort.
currentStatus.getFlightData().addEvent(new FlightEvent(FlightEvent.Type.EXCEPTION, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), e.getLocalizedMessage()));
flightData.addBranch(currentStatus.getFlightData());
flightData.getWarningSet().addAll(currentStatus.getWarnings());
e.setFlightData(flightData);
throw e;
}
@ -380,7 +396,13 @@ public class BasicEventSimulationEngine implements SimulationEngine {
case IGNITION: {
MotorClusterState motorState = (MotorClusterState) event.getData();
// If there are multiple ignition events (as is the case if the preceding stage has several burnout events, for instance)
// We get multiple ignition events for the upper stage motor. Ignore are all after the first.
if (motorState.getIgnitionTime() < currentStatus.getSimulationTime()) {
log.info("Ignoring motor " +motorState.toDescription()+" ignition event @"+currentStatus.getSimulationTime());
continue;
}
log.info(" Igniting motor: "+motorState.toDescription()+" @"+currentStatus.getSimulationTime());
motorState.ignite( event.getTime());

View File

@ -1,6 +1,7 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.rocketcomponent.InstanceMap;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;
@ -35,8 +36,10 @@ public class BasicLandingStepper extends AbstractSimulationStepper {
// Get total CD
double mach = airSpeed.length() / atmosphere.getMachSpeed();
final InstanceMap imap = status.getConfiguration().getActiveInstances();
for (RecoveryDevice c : status.getDeployedRecoveryDevices()) {
totalCD += c.getCD(mach) * c.getArea() / refArea;
totalCD += imap.count(c) * c.getCD(mach) * c.getArea() / refArea;
}
// Compute drag force

View File

@ -69,6 +69,27 @@ public class BasicTumbleStepper extends AbstractSimulationStepper {
double timeStep = MathUtil.min(0.5 / linearAcceleration.length(), RECOVERY_TIME_STEP);
// Perform Euler integration
Coordinate newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
// If I've hit the ground, recalculate time step and position
if (newPosition.z < 0) {
final double a = linearAcceleration.z;
final double v = status.getRocketVelocity().z;
final double z0 = status.getRocketPosition().z;
// The new timestep is the solution of
// 1/2 at^2 + vt + z0 = 0
timeStep = (-v - Math.sqrt(v*v - 2*a*z0))/a;
newPosition = status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2));
// avoid rounding error in new altitude
newPosition = newPosition.setZ(0);
}
status.setRocketPosition(status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2)));
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));

View File

@ -1,7 +1,11 @@
package net.sf.openrocket.simulation.exception;
import net.sf.openrocket.simulation.FlightData;
public class SimulationException extends Exception {
private FlightData flightData = null;
public SimulationException() {
}
@ -18,4 +22,11 @@ public class SimulationException extends Exception {
super(message, cause);
}
public void setFlightData(FlightData f) {
flightData = f;
}
public FlightData getFlightData() {
return flightData;
}
}

View File

@ -76,6 +76,7 @@ public abstract class Preferences implements ChangeSource {
public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
private static final String AUTO_OPEN_LAST_DESIGN = "AUTO_OPEN_LAST_DESIGN";
private static final String OPEN_LEFTMOST_DESIGN_TAB = "OPEN_LEFTMOST_DESIGN_TAB";
public static final String MARKER_STYLE_ICON = "MARKER_STYLE_ICON";
private static final String SHOW_MARKERS = "SHOW_MARKERS";
private static final String SHOW_ROCKSIM_FORMAT_WARNING = "SHOW_ROCKSIM_FORMAT_WARNING";

View File

@ -185,8 +185,8 @@ public class SerializeThrustcurveMotors {
System.exit(1);
} else {
while (iterator.hasNext()) {
Pair<String, InputStream> f = iterator.next();
String fileName = f.getU();
Pair<File, InputStream> f = iterator.next();
String fileName = f.getU().getName();
InputStream is = f.getV();
List<ThrustCurveMotor.Builder> motors = loader.load(is, fileName);

View File

@ -90,8 +90,11 @@ public abstract class Unit {
* @return A string representation of the number in these units.
*/
public String toString(double value) {
double val = toUnit(value);
if (Double.isNaN(value))
return "N/A";
double val = toUnit(value);
if (Math.abs(val) > 1E6) {
return expFormat.format(val);
}

View File

@ -82,7 +82,7 @@ public class LinearInterpolator implements Cloneable {
if ( y1 != null ) {
// Wow, x was a key in the map. Such luck.
return y1.doubleValue();
return y1;
}
// we now know that x is not in the map, so we need to find the lower and higher keys.
@ -96,16 +96,16 @@ public class LinearInterpolator implements Cloneable {
Double firstKey = sortMap.firstKey();
// x is smaller than the first entry in the map.
if ( x < firstKey.doubleValue() ) {
if ( x < firstKey) {
y1 = sortMap.get(firstKey);
return y1.doubleValue();
return y1;
}
// floor key is the largest key smaller than x - since we have at least one key,
// and x>=firstKey, we know that floorKey != null.
Double floorKey = sortMap.subMap(firstKey, x).lastKey();
x1 = floorKey.doubleValue();
x1 = floorKey;
y1 = sortMap.get(floorKey);
// Now we need to find the key that is greater or equal to x
@ -113,16 +113,16 @@ public class LinearInterpolator implements Cloneable {
// Check if x is bigger than all the entries.
if ( tailMap.isEmpty() ) {
return y1.doubleValue();
return y1;
}
Double ceilKey = tailMap.firstKey();
// Check if x is bigger than all the entries.
if ( ceilKey == null ) {
return y1.doubleValue();
return y1;
}
x2 = ceilKey.doubleValue();
x2 = ceilKey;
y2 = sortMap.get(ceilKey);
return (x - x1)/(x2-x1) * (y2-y1) + y1;

View File

@ -290,7 +290,7 @@ public class BarrowmanCalculatorTest {
FlightConfiguration configuration = rocket.getSelectedConfiguration();
WarningSet warnings = new WarningSet();
calc.testIsContinuous(configuration, rocket, warnings);
calc.checkGeometry(configuration, rocket, warnings);
assertTrue("Estes Alpha III should be continuous: ", warnings.isEmpty());
}
@ -301,7 +301,7 @@ public class BarrowmanCalculatorTest {
FlightConfiguration configuration = rocket.getSelectedConfiguration();
WarningSet warnings = new WarningSet();
calc.testIsContinuous(configuration, rocket, warnings);
calc.checkGeometry(configuration, rocket, warnings);
assertTrue("F9H should be continuous: ", warnings.isEmpty());
}
@ -319,7 +319,7 @@ public class BarrowmanCalculatorTest {
body.setOuterRadius( 0.012 );
body.setName( body.getName()+" << discontinuous");
calc.testIsContinuous(configuration, rocket, warnings);
calc.checkGeometry(configuration, rocket, warnings);
assertFalse(" Estes Alpha III has an undetected discontinuity:", warnings.isEmpty());
}
@ -340,7 +340,7 @@ public class BarrowmanCalculatorTest {
body.setOuterRadius( 0.012 );
body.setName( body.getName()+" << discontinuous");
calc.testIsContinuous(configuration, rocket, warnings);
calc.checkGeometry(configuration, rocket, warnings);
assertFalse(" Missed discontinuity in Falcon 9 Heavy:" , warnings.isEmpty());
}

View File

@ -0,0 +1,101 @@
package net.sf.openrocket.aerodynamics;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import net.sf.openrocket.ServicesForTesting;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.aerodynamics.barrowman.RailButtonCalc;
import net.sf.openrocket.plugin.PluginModule;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.RailButton;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.TestRockets;
import net.sf.openrocket.util.Transformation;
public class RailButtonCalcTest {
protected final double EPSILON = 0.0001;
private static Injector injector;
@BeforeClass
public static void setup() {
Module applicationModule = new ServicesForTesting();
Module pluginModule = new PluginModule();
injector = Guice.createInjector( applicationModule, pluginModule);
Application.setInjector(injector);
}
@Test
public void testRailButtons() {
Rocket rocket = TestRockets.makeEstesAlphaIII();
FlightConfiguration config = rocket.getSelectedConfiguration();
// Get the body tube...
BodyTube tube = (BodyTube)rocket.getChild(0).getChild(1);
// Replace the launch lug with a (single) railbutton
LaunchLug lug = (LaunchLug)tube.getChild(1);
rocket.removeChild(lug);
RailButton button = new RailButton();
tube.addChild(button);
// Button parameters from Binder Design standard 1010
button.setOuterDiameter(0.011);
button.setInnerDiameter(0.006);
button.setBaseHeight(0.002);
button.setFlangeHeight(0.002);
button.setTotalHeight(0.008);
button.setAxialMethod(AxialMethod.ABSOLUTE);
button.setAxialOffset(1.0);
// Set up flight conditions
FlightConditions conditions = new FlightConditions(config);
conditions.setMach(1.0);
BarrowmanCalculator barrowmanObj = new BarrowmanCalculator();
RailButtonCalc calcObj = new RailButtonCalc(button);
// Calculate effective CD for rail button
// Boundary layer height
double rex = calcObj.calculateReynoldsNumber(1.0, conditions); // Reynolds number of button location
double del = 0.37 * 1.0 / Math.pow(rex, 0.2); // Boundary layer height
// Interpolate velocity at midpoint of railbutton
double mach = MathUtil.map(0.008/2.0, 0, del, 0, 1.0);
// Interpolate to get CD
double cd = MathUtil.map(mach, 0.2, 0.3, 1.22, 1.25);
// Reference area of rail button
final double outerArea = button.getTotalHeight() * button.getOuterDiameter();
final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight();
final double refArea = outerArea - notchArea;
// Get "effective" CD
double calccd = cd * MathUtil.pow2(mach) * barrowmanObj.calculateStagnationCD(conditions.getMach()) * refArea / conditions.getRefArea() ;
// Now compare with value from RailButtonCalc
WarningSet warnings = new WarningSet();
AerodynamicForces assemblyForces = new AerodynamicForces().zero();
AerodynamicForces componentForces = new AerodynamicForces();
double testcd = calcObj.calculatePressureCD(conditions, barrowmanObj.calculateStagnationCD(conditions.getMach()), 0, warnings);
assertEquals("Calculated rail button CD incorrect", calccd, testcd, EPSILON);
}
}

View File

@ -23,7 +23,7 @@ public class ThrustCurveMotorSetTest {
private static final ThrustCurveMotor motor1 = new ThrustCurveMotor.Builder()
.setManufacturer(Manufacturer.getManufacturer("A"))
.setCommonName("F12")
.setDesignation("F12X")
.setDesignation("F12")
.setDescription("Desc")
.setMotorType(Motor.Type.UNKNOWN)
.setStandardDelays(new double[] {})
@ -38,7 +38,7 @@ public class ThrustCurveMotorSetTest {
private static final ThrustCurveMotor motor2 = new ThrustCurveMotor.Builder()
.setManufacturer(Manufacturer.getManufacturer("A"))
.setCommonName("F12")
.setDesignation("F12H")
.setDesignation("F12")
.setDescription("Desc")
.setMotorType(Motor.Type.SINGLE)
.setStandardDelays(new double[] { 5 })
@ -51,20 +51,6 @@ public class ThrustCurveMotorSetTest {
.build();
private static final ThrustCurveMotor motor3 = new ThrustCurveMotor.Builder()
.setManufacturer(Manufacturer.getManufacturer("A"))
.setCode("F12")
.setDescription("Desc")
.setMotorType(Motor.Type.UNKNOWN)
.setStandardDelays(new double[] { 0, Motor.PLUGGED_DELAY })
.setDiameter(0.024)
.setLength(0.07)
.setTimePoints(new double[] { 0, 1, 2 })
.setThrustPoints(new double[] { 0, 2, 0 })
.setCGPoints(new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL })
.setDigest("digestC")
.build();
private static final ThrustCurveMotor motor4 = new ThrustCurveMotor.Builder()
.setManufacturer(Manufacturer.getManufacturer("A"))
.setDesignation("F12")
.setDescription("Desc")
@ -113,39 +99,20 @@ public class ThrustCurveMotorSetTest {
// Add motor2
assertTrue(set.matches(motor2));
set.addMotor(motor2);
assertEquals(motor1.getManufacturer(), set.getManufacturer());
assertEquals(motor3.getCommonName(), set.getCommonName());
assertEquals(motor2.getManufacturer(), set.getManufacturer());
assertEquals(motor2.getCommonName(), set.getCommonName());
assertEquals(Motor.Type.SINGLE, set.getType());
assertEquals(motor1.getDiameter(), set.getDiameter(), 0.00001);
assertEquals(motor1.getLength(), set.getLength(), 0.00001);
assertEquals(motor2.getDiameter(), set.getDiameter(), 0.00001);
assertEquals(motor2.getLength(), set.getLength(), 0.00001);
assertEquals(2, set.getMotors().size());
assertEquals(motor2, set.getMotors().get(0));
assertEquals(motor1, set.getMotors().get(1));
assertEquals(motor1, set.getMotors().get(0));
assertEquals(motor2, set.getMotors().get(1));
assertEquals(Arrays.asList(5.0), set.getDelays());
// Add motor3
assertTrue(set.matches(motor3));
set.addMotor(motor3);
assertEquals(motor1.getManufacturer(), set.getManufacturer());
assertEquals(motor3.getCommonName(), set.getCommonName());
assertEquals(Motor.Type.SINGLE, set.getType());
assertEquals(motor1.getDiameter(), set.getDiameter(), 0.00001);
assertEquals(motor1.getLength(), set.getLength(), 0.00001);
assertEquals(3, set.getMotors().size());
System.out.println("motor set");
System.out.println(set.getMotors());
System.out.println(motor3);
System.out.println(motor2);
System.out.println(motor1);
assertEquals(motor3, set.getMotors().get(0));
assertEquals(motor2, set.getMotors().get(1));
assertEquals(motor1, set.getMotors().get(2));
assertEquals(Arrays.asList(0.0, 5.0, Motor.PLUGGED_DELAY), set.getDelays());
// Test that adding motor4 fails
assertFalse(set.matches(motor4));
// Test that adding motor3 fails
assertFalse(set.matches(motor3));
try {
set.addMotor(motor4);
set.addMotor(motor3);
fail("Did not throw exception");
} catch (IllegalArgumentException e) {
}

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.file.iterator;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import net.sf.openrocket.util.Pair;
@ -13,14 +14,14 @@ public class TestFileIterator {
@Test
public void testFileIterator() {
final Pair<String, InputStream> one = new Pair<String, InputStream>("one", new ByteArrayInputStream(new byte[] { 1 }));
final Pair<String, InputStream> two = new Pair<String, InputStream>("two", new ByteArrayInputStream(new byte[] { 2 }));
final Pair<File, InputStream> one = new Pair<>(new File("one"), new ByteArrayInputStream(new byte[] { 1 }));
final Pair<File, InputStream> two = new Pair<>(new File("two"), new ByteArrayInputStream(new byte[] { 2 }));
FileIterator iterator = new FileIterator() {
private int count = 0;
@Override
protected Pair<String, InputStream> findNext() {
protected Pair<File, InputStream> findNext() {
count++;
switch (count) {
case 1:

View File

@ -0,0 +1,398 @@
package net.sf.openrocket.rocketcomponent;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
import net.sf.openrocket.util.MathUtil;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NoseConeTest extends BaseTestCase {
private final double EPSILON = MathUtil.EPSILON * 1000;
@Test
public void testNormalNoseCone() {
NoseCone noseCone = new NoseCone();
// First set the parameters using the normal transition setters (i.e. using AftRadius and AftShoulder instead of Base and Shoulder)
noseCone.setType(Transition.Shape.OGIVE);
noseCone.setLength(0.06);
noseCone.setAftRadius(0.1);
noseCone.setAftShoulderLength(0.01);
noseCone.setAftShoulderRadius(0.05);
noseCone.setAftShoulderCapped(false);
noseCone.setAftShoulderThickness(0.001);
assertEquals(Transition.Shape.OGIVE, noseCone.getType());
assertEquals(0.06, noseCone.getLength(), EPSILON);
assertEquals(0.1, noseCone.getAftRadius(), EPSILON);
assertEquals(0.1, noseCone.getBaseRadius(), EPSILON);
assertEquals(0.1, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(0.01, noseCone.getAftShoulderLength(), EPSILON);
assertEquals(0.01, noseCone.getShoulderLength(), EPSILON);
assertEquals(0.05, noseCone.getAftShoulderRadius(), EPSILON);
assertEquals(0.05, noseCone.getShoulderRadius(), EPSILON);
assertFalse(noseCone.isAftShoulderCapped());
assertFalse(noseCone.isShoulderCapped());
assertEquals(0.001, noseCone.getAftShoulderThickness(), EPSILON);
assertEquals(0.001, noseCone.getShoulderThickness(), EPSILON);
assertFalse(noseCone.isAftRadiusAutomatic());
assertEquals(0, noseCone.getForeRadius(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeShoulderLength(), EPSILON);
assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON);
assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON);
assertFalse(noseCone.isForeShoulderCapped());
assertFalse(noseCone.isForeRadiusAutomatic());
// Test setting the specific nose cone setters
noseCone.setBaseRadius(0.2);
noseCone.setShoulderLength(0.03);
noseCone.setShoulderRadius(0.04);
noseCone.setShoulderCapped(true);
noseCone.setShoulderThickness(0.005);
assertEquals(0.2, noseCone.getAftRadius(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadius(), EPSILON);
assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON);
assertEquals(0.03, noseCone.getShoulderLength(), EPSILON);
assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON);
assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON);
assertTrue(noseCone.isAftShoulderCapped());
assertTrue(noseCone.isShoulderCapped());
assertEquals(0.005, noseCone.getAftShoulderThickness(), EPSILON);
assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON);
assertFalse(noseCone.isAftRadiusAutomatic());
assertEquals(0, noseCone.getForeRadius(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeShoulderLength(), EPSILON);
assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON);
assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON);
assertFalse(noseCone.isForeShoulderCapped());
assertFalse(noseCone.isForeRadiusAutomatic());
}
@Test
public void testFlippedNoseCone() {
NoseCone noseCone = new NoseCone();
// First set the parameters using the normal transition setters (i.e. using AftRadius and AftShoulder instead of Base and Shoulder)
noseCone.setType(Transition.Shape.OGIVE);
noseCone.setLength(0.06);
noseCone.setAftRadius(0.1);
noseCone.setAftShoulderLength(0.01);
noseCone.setAftShoulderRadius(0.05);
noseCone.setAftShoulderCapped(false);
noseCone.setAftShoulderThickness(0.001);
noseCone.setFlipped(true);
assertEquals(Transition.Shape.OGIVE, noseCone.getType());
assertEquals(0.06, noseCone.getLength(), EPSILON);
assertEquals(0.1, noseCone.getForeRadius(), EPSILON);
assertEquals(0.1, noseCone.getBaseRadius(), EPSILON);
assertEquals(0.1, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(0.01, noseCone.getForeShoulderLength(), EPSILON);
assertEquals(0.01, noseCone.getShoulderLength(), EPSILON);
assertEquals(0.05, noseCone.getForeShoulderRadius(), EPSILON);
assertEquals(0.05, noseCone.getShoulderRadius(), EPSILON);
assertFalse(noseCone.isForeShoulderCapped());
assertFalse(noseCone.isShoulderCapped());
assertEquals(0.001, noseCone.getForeShoulderThickness(), EPSILON);
assertEquals(0.001, noseCone.getShoulderThickness(), EPSILON);
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(0, noseCone.getAftRadius(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftShoulderLength(), EPSILON);
assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON);
assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON);
assertFalse(noseCone.isAftShoulderCapped());
assertFalse(noseCone.isAftRadiusAutomatic());
// Test setting the specific nose cone setters
noseCone.setBaseRadius(0.2);
noseCone.setShoulderLength(0.03);
noseCone.setShoulderRadius(0.04);
noseCone.setShoulderCapped(true);
noseCone.setShoulderThickness(0.005);
assertEquals(0.2, noseCone.getForeRadius(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadius(), EPSILON);
assertEquals(0.2, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(0.03, noseCone.getForeShoulderLength(), EPSILON);
assertEquals(0.03, noseCone.getShoulderLength(), EPSILON);
assertEquals(0.04, noseCone.getForeShoulderRadius(), EPSILON);
assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON);
assertTrue(noseCone.isForeShoulderCapped());
assertTrue(noseCone.isShoulderCapped());
assertEquals(0.005, noseCone.getForeShoulderThickness(), EPSILON);
assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON);
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(0, noseCone.getAftRadius(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftShoulderLength(), EPSILON);
assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON);
assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON);
assertFalse(noseCone.isAftShoulderCapped());
assertFalse(noseCone.isAftRadiusAutomatic());
// Flip back to normal
noseCone.setFlipped(false);
assertEquals(0.2, noseCone.getAftRadius(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadius(), EPSILON);
assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON);
assertEquals(0.03, noseCone.getShoulderLength(), EPSILON);
assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON);
assertEquals(0.04, noseCone.getShoulderRadius(), EPSILON);
assertTrue(noseCone.isAftShoulderCapped());
assertTrue(noseCone.isShoulderCapped());
assertEquals(0.005, noseCone.getAftShoulderThickness(), EPSILON);
assertEquals(0.005, noseCone.getShoulderThickness(), EPSILON);
assertFalse(noseCone.isAftRadiusAutomatic());
assertEquals(0, noseCone.getForeRadius(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeShoulderLength(), EPSILON);
assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON);
assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON);
assertFalse(noseCone.isForeShoulderCapped());
assertFalse(noseCone.isForeRadiusAutomatic());
}
@Test
public void testNormalNoseConeRadiusAutomatic() {
Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket();
AxialStage stage = rocket.getStage(0);
NoseCone noseCone = new NoseCone(Transition.Shape.CONICAL, 0.06, 0.01);
BodyTube tube1 = new BodyTube(0.06, 0.02);
tube1.setOuterRadiusAutomatic(false);
BodyTube tube2 = new BodyTube(0.06, 0.03);
tube2.setOuterRadiusAutomatic(false);
// Test no previous or next component
stage.addChild(noseCone);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(stage, noseCone.getPreviousComponent());
assertNull(noseCone.getPreviousSymmetricComponent());
assertNull(noseCone.getNextComponent());
assertNull(noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setAftRadiusAutomatic(true, true);
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
noseCone.setForeRadiusAutomatic(true, true);
assertFalse(noseCone.isForeRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
// Test with next component
stage.addChild(tube1);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(stage, noseCone.getPreviousComponent());
assertNull(noseCone.getPreviousSymmetricComponent());
assertSame(tube1, noseCone.getNextComponent());
assertSame(tube1, noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setAftRadiusAutomatic(true, true);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertTrue(noseCone.usesNextCompAutomatic());
assertSame(stage, noseCone.getPreviousComponent());
assertNull(noseCone.getPreviousSymmetricComponent());
assertSame(tube1, noseCone.getNextComponent());
assertSame(tube1, noseCone.getNextSymmetricComponent());
assertTrue(noseCone.isAftRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON);
assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setAftRadiusAutomatic(false, true);
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setBaseRadiusAutomatic(true);
assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setForeRadiusAutomatic(true, true);
assertFalse(noseCone.isForeRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
// Test with previous component
stage.addChild(tube2, 0);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertTrue(noseCone.usesNextCompAutomatic());
assertSame(tube2, noseCone.getPreviousComponent());
assertSame(tube2, noseCone.getPreviousSymmetricComponent());
assertSame(tube1, noseCone.getNextComponent());
assertSame(tube1, noseCone.getNextSymmetricComponent());
assertTrue(noseCone.isAftRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON);
assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON);
// Do a flip
noseCone.setFlipped(true);
assertTrue(noseCone.isForeRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isAftRadiusAutomatic());
}
@Test
public void testFlippedNoseConeRadiusAutomatic() {
Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket();
AxialStage stage = rocket.getStage(0);
NoseCone noseCone = new NoseCone(Transition.Shape.CONICAL, 0.06, 0.01);
noseCone.setFlipped(true);
BodyTube tube1 = new BodyTube(0.06, 0.02);
tube1.setOuterRadiusAutomatic(false);
BodyTube tube2 = new BodyTube(0.06, 0.03);
tube2.setOuterRadiusAutomatic(false);
// Test no previous or next component
stage.addChild(noseCone);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(stage, noseCone.getPreviousComponent());
assertNull(noseCone.getPreviousSymmetricComponent());
assertNull(noseCone.getNextComponent());
assertNull(noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setAftRadiusAutomatic(true, true);
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
noseCone.setForeRadiusAutomatic(true, true);
assertFalse(noseCone.isForeRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
// Test with previous component
stage.addChild(tube1, 0);
assertFalse(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(tube1, noseCone.getPreviousComponent());
assertSame(tube1, noseCone.getPreviousSymmetricComponent());
assertNull(noseCone.getNextComponent());
assertNull(noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertFalse(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isForeRadiusAutomatic());
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
noseCone.setBaseRadiusAutomatic(true);
assertTrue(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(tube1, noseCone.getPreviousComponent());
assertSame(tube1, noseCone.getPreviousSymmetricComponent());
assertNull(noseCone.getNextComponent());
assertNull(noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertTrue(noseCone.isForeRadiusAutomatic());
assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON);
assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
noseCone.setForeRadiusAutomatic(false, true);
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON);
noseCone.setBaseRadiusAutomatic(true);
assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON);
assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertTrue(noseCone.isForeRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertFalse(noseCone.isAftRadiusAutomatic());
// Test with next component
stage.addChild(tube2);
assertTrue(noseCone.usesPreviousCompAutomatic());
assertFalse(noseCone.usesNextCompAutomatic());
assertSame(tube1, noseCone.getPreviousComponent());
assertSame(tube1, noseCone.getPreviousSymmetricComponent());
assertSame(tube2, noseCone.getNextComponent());
assertSame(tube2, noseCone.getNextSymmetricComponent());
assertFalse(noseCone.isAftRadiusAutomatic());
assertTrue(noseCone.isBaseRadiusAutomatic());
assertTrue(noseCone.isForeRadiusAutomatic());
assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON);
assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON);
assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON);
assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON);
assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON);
assertEquals(0, noseCone.getAftRadius(), EPSILON);
}
}

View File

@ -24,6 +24,8 @@ import static org.junit.Assert.assertSame;
* Tests to verify that simulations contain all the expected flight events.
*/
public class FlightEventsTest extends BaseTestCase {
private static final double EPSILON = 0.005;
/**
* Tests for a single stage design.
*/
@ -66,7 +68,7 @@ public class FlightEventsTest extends BaseTestCase {
// Test that the event times are correct
for (int i = 0; i < expectedEventTimes.length; i++) {
assertEquals(" Flight type " + expectedEventTypes[i] + " has wrong time",
expectedEventTimes[i], eventList.get(i).getTime(), 0.001);
expectedEventTimes[i], eventList.get(i).getTime(), EPSILON);
}
@ -142,7 +144,7 @@ public class FlightEventsTest extends BaseTestCase {
// Test that the event times are correct
for (int i = 0; i < expectedEventTimes.length; i++) {
assertEquals(" Flight type " + expectedEventTypes[i] + " has wrong time",
expectedEventTimes[i], eventList.get(i).getTime(), 0.001);
expectedEventTimes[i], eventList.get(i).getTime(), EPSILON);
}
// Test that the event sources are correct

View File

@ -58,6 +58,7 @@ The following file format versions exist:
Added PhotoStudio settings saving (<photostudio>)
Added override CD parameter (<overridecd>)
Added stage activeness remembrance (<stage> under <motorconfiguration>)
Added <isflipped> parameter for Nose Cones
Separated <overridesubcomponents> into individual parameters for mass, CG, and CD.
Rename <fincount> to <instancecount> (<fincount> remains for backward compatibility)
Rename <position> to <axialoffset> (<position> remains for backward compatibility)

View File

@ -19,7 +19,7 @@
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.inject.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.json-1.1.3.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.json-api-1.1.3.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.activation-api.2.3.1.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/lib/javax.activation-api-2.3.1.jar"/>
<classpathentry kind="lib" path="/OpenRocket Core/resources"/>
<classpathentry kind="lib" path="resources"/>
<classpathentry kind="lib" path="lib/miglayout-4.0-swing.jar" sourcepath="reference/miglayout-4.0-sources.jar"/>

Some files were not shown because too many files have changed in this diff Show More