Module MAPLEAF.Rocket.stage

Expand source code
from typing import Union

from MAPLEAF.Rocket.CompositeObject import CompositeObject
from MAPLEAF.Motion import Vector
from MAPLEAF.Rocket import (BodyComponent, PlanarInterface,
                            rocketComponentFactory, stringNameToClassMap)

__all__ = [ "Stage" ]

class Stage(CompositeObject, BodyComponent):
    ''' Represents a single rocket stage '''

    def __init__(self, stageDictReader, rocket):
        #TODO: This should take a boolean parameter 'dropped' which specified whether the stage being initialized is on the pad (full engine) or being dropped (empty engine)
        self.rocket = rocket
        self.stageDictReader = stageDictReader

        self.name = stageDictReader.getDictName()
        self.stageNumber = stageDictReader.getInt("stageNumber")
        self.position = stageDictReader.getVector("position")

        self.separationConditionType = stageDictReader.getString("separationTriggerType")
        self.separationDelay = stageDictReader.getFloat("separationDelay")
        self.separationConditionValue = stageDictReader.getFloat("separationTriggerValue")

        #### Placeholder fields, to be filled by subcomponents ####
        self.motor = None # Filled in by Motor.__init__
        self.engineShutOffTime = None # Filled in by Motor.__init__ - used for base drag calculations

        self.components = []
        self.initializeSubComponents()

        # Check if any stage-level inertia properties are being overriden
        CGOverride = stageDictReader.tryGetVector("constCG", defaultValue=None)
        if CGOverride != None:
            CGOverride += self.position
        massOverride = stageDictReader.tryGetFloat("constMass", defaultValue=None)
        MOIOverride = stageDictReader.tryGetVector("constMOI", defaultValue=None)

        # Initialize functionality inherited from CompositeObject - pass in any overrides
        CompositeObject.__init__(self, components=self.components, CGOverride=CGOverride, MOIOverride=MOIOverride, massOverride=massOverride)
        
    def initializeSubComponents(self):
        # Assume each sub dictionary represents a component
        subDicts = self.stageDictReader.getImmediateSubDicts()

        def returnZeroIfBodyComponent(componentDictPath):
            className = self.stageDictReader.getString(componentDictPath + ".class")
            componentClass = stringNameToClassMap[className]
            
            if issubclass(componentClass, BodyComponent):
                return 0, componentDictPath
            else: 
                return 1, componentDictPath

        # Make sure BodyComponents are initializede before others
            # This allows non-body components (which are attached to body components) like the fins
                # to get info about the body radius where they're mounted
        subDicts.sort(key=returnZeroIfBodyComponent)

        # Initialize each component, add to componets
        for componentDict in subDicts:
            newComponent = rocketComponentFactory(componentDict, self.rocket, self)
            self.components.append(newComponent)

            try:
                if newComponent.name not in self.__dict__: # Avoid clobbering existing info
                    setattr(self, newComponent.name, newComponent) # Make component available as stage.componentName
            except AttributeError:
                pass # Expect to arrive here if the component doesn't define a "name" attribute   

    def getComponentsOfType(self, type):
        matchingComponents = []
        for comp in self.components:
            if isinstance(comp, type):
                matchingComponents.append(comp)

        return matchingComponents

    #### Geometry / Introspection ####
    def _getFirstComponentOfType(self, componentList, desiredType):
        for comp in componentList:
            if isinstance(comp, desiredType):
                return comp
        return None

    def getTopInterfaceLocation(self) -> Union[Vector, None]:
        # Expects components to be sorted by z-location, top to bottom
        comp = self._getFirstComponentOfType(self.components, BodyComponent)
        try:
            topZ = comp.getTopInterfaceLocation().Z
            return Vector(self.position.X, self.position.Y, topZ)   
        except AttributeError:
            return None # Occurs when comp.getTopInterfaceLocation() == None

    def getBottomInterfaceLocation(self) -> Union[Vector, None]:
        # Expects components to be sorted by z-location, top to bottom
        comp = self._getFirstComponentOfType(reversed(self.components), BodyComponent)
        try:
            bottomZ = comp.getBottomInterfaceLocation().Z
            return Vector(self.position.X, self.position.Y, bottomZ)   
        except AttributeError:
            return None # Occurs when comp.getBottomInterfaceLocation() == None

    def getLength(self):
        try:
            topBodyComponent = self._getFirstComponentOfType(self.components, BodyComponent)
            topLocationZ = topBodyComponent.position.Z

            bottomBodyComponent = self._getFirstComponentOfType(reversed(self.components), BodyComponent)
            bottomLocationZ = bottomBodyComponent.position.Z - bottomBodyComponent.length
            return (topLocationZ - bottomLocationZ)
        except AttributeError:
            return None # No body components in current Stage

    def getMaxDiameter(self):
        maxDiameter = 0
        for comp in self.components:
            try:
                maxDiameter = max(comp.getMaxDiameter(), maxDiameter)
            except AttributeError:
                pass # Component is not a BodyComponent
        
        return maxDiameter

    def getRadius(self, distanceFromTop):
        topOfStageZ = self.position.Z
        desiredZ = topOfStageZ - distanceFromTop

        for comp in self.components:
            try:
                # Check if desired location is part of this component, if so, return the component's radius
                compTopZ = comp.position.Z
                compBottomZ = compTopZ - comp.length
                if desiredZ <= compTopZ and desiredZ >= compBottomZ:
                    distanceFromTopOfComponent = compTopZ - desiredZ
                    return comp.getRadius(distanceFromTopOfComponent)

            except AttributeError:
                pass # Not a body component
        
        raise ValueError("Length {} from the top of stage '{}' does not fall between the top and bottom of any of its components".format(distanceFromTop, self.name))

    def plotShape(self):
        '''
            Calls .plotShape() on all subcomponents
            Returns CGsubZ, CGsubY (two lists of scalars for a 2D plot)
        '''
        CGsubZ = []
        CGsubY = []
        for component in self.components:               
            try:
                component.plotShape()
                initialComponentInertia = component.getInertia(0, self.rocket.rigidBody.state)
                CGsubZ.append(initialComponentInertia.CG.Z)
                CGsubY.append(initialComponentInertia.CG.Y)
            
            except AttributeError:
                pass # Plotting function not implemented
        
        return CGsubZ, CGsubY

Classes

class Stage (stageDictReader, rocket)

Represents a single rocket stage

Expand source code
class Stage(CompositeObject, BodyComponent):
    ''' Represents a single rocket stage '''

    def __init__(self, stageDictReader, rocket):
        #TODO: This should take a boolean parameter 'dropped' which specified whether the stage being initialized is on the pad (full engine) or being dropped (empty engine)
        self.rocket = rocket
        self.stageDictReader = stageDictReader

        self.name = stageDictReader.getDictName()
        self.stageNumber = stageDictReader.getInt("stageNumber")
        self.position = stageDictReader.getVector("position")

        self.separationConditionType = stageDictReader.getString("separationTriggerType")
        self.separationDelay = stageDictReader.getFloat("separationDelay")
        self.separationConditionValue = stageDictReader.getFloat("separationTriggerValue")

        #### Placeholder fields, to be filled by subcomponents ####
        self.motor = None # Filled in by Motor.__init__
        self.engineShutOffTime = None # Filled in by Motor.__init__ - used for base drag calculations

        self.components = []
        self.initializeSubComponents()

        # Check if any stage-level inertia properties are being overriden
        CGOverride = stageDictReader.tryGetVector("constCG", defaultValue=None)
        if CGOverride != None:
            CGOverride += self.position
        massOverride = stageDictReader.tryGetFloat("constMass", defaultValue=None)
        MOIOverride = stageDictReader.tryGetVector("constMOI", defaultValue=None)

        # Initialize functionality inherited from CompositeObject - pass in any overrides
        CompositeObject.__init__(self, components=self.components, CGOverride=CGOverride, MOIOverride=MOIOverride, massOverride=massOverride)
        
    def initializeSubComponents(self):
        # Assume each sub dictionary represents a component
        subDicts = self.stageDictReader.getImmediateSubDicts()

        def returnZeroIfBodyComponent(componentDictPath):
            className = self.stageDictReader.getString(componentDictPath + ".class")
            componentClass = stringNameToClassMap[className]
            
            if issubclass(componentClass, BodyComponent):
                return 0, componentDictPath
            else: 
                return 1, componentDictPath

        # Make sure BodyComponents are initializede before others
            # This allows non-body components (which are attached to body components) like the fins
                # to get info about the body radius where they're mounted
        subDicts.sort(key=returnZeroIfBodyComponent)

        # Initialize each component, add to componets
        for componentDict in subDicts:
            newComponent = rocketComponentFactory(componentDict, self.rocket, self)
            self.components.append(newComponent)

            try:
                if newComponent.name not in self.__dict__: # Avoid clobbering existing info
                    setattr(self, newComponent.name, newComponent) # Make component available as stage.componentName
            except AttributeError:
                pass # Expect to arrive here if the component doesn't define a "name" attribute   

    def getComponentsOfType(self, type):
        matchingComponents = []
        for comp in self.components:
            if isinstance(comp, type):
                matchingComponents.append(comp)

        return matchingComponents

    #### Geometry / Introspection ####
    def _getFirstComponentOfType(self, componentList, desiredType):
        for comp in componentList:
            if isinstance(comp, desiredType):
                return comp
        return None

    def getTopInterfaceLocation(self) -> Union[Vector, None]:
        # Expects components to be sorted by z-location, top to bottom
        comp = self._getFirstComponentOfType(self.components, BodyComponent)
        try:
            topZ = comp.getTopInterfaceLocation().Z
            return Vector(self.position.X, self.position.Y, topZ)   
        except AttributeError:
            return None # Occurs when comp.getTopInterfaceLocation() == None

    def getBottomInterfaceLocation(self) -> Union[Vector, None]:
        # Expects components to be sorted by z-location, top to bottom
        comp = self._getFirstComponentOfType(reversed(self.components), BodyComponent)
        try:
            bottomZ = comp.getBottomInterfaceLocation().Z
            return Vector(self.position.X, self.position.Y, bottomZ)   
        except AttributeError:
            return None # Occurs when comp.getBottomInterfaceLocation() == None

    def getLength(self):
        try:
            topBodyComponent = self._getFirstComponentOfType(self.components, BodyComponent)
            topLocationZ = topBodyComponent.position.Z

            bottomBodyComponent = self._getFirstComponentOfType(reversed(self.components), BodyComponent)
            bottomLocationZ = bottomBodyComponent.position.Z - bottomBodyComponent.length
            return (topLocationZ - bottomLocationZ)
        except AttributeError:
            return None # No body components in current Stage

    def getMaxDiameter(self):
        maxDiameter = 0
        for comp in self.components:
            try:
                maxDiameter = max(comp.getMaxDiameter(), maxDiameter)
            except AttributeError:
                pass # Component is not a BodyComponent
        
        return maxDiameter

    def getRadius(self, distanceFromTop):
        topOfStageZ = self.position.Z
        desiredZ = topOfStageZ - distanceFromTop

        for comp in self.components:
            try:
                # Check if desired location is part of this component, if so, return the component's radius
                compTopZ = comp.position.Z
                compBottomZ = compTopZ - comp.length
                if desiredZ <= compTopZ and desiredZ >= compBottomZ:
                    distanceFromTopOfComponent = compTopZ - desiredZ
                    return comp.getRadius(distanceFromTopOfComponent)

            except AttributeError:
                pass # Not a body component
        
        raise ValueError("Length {} from the top of stage '{}' does not fall between the top and bottom of any of its components".format(distanceFromTop, self.name))

    def plotShape(self):
        '''
            Calls .plotShape() on all subcomponents
            Returns CGsubZ, CGsubY (two lists of scalars for a 2D plot)
        '''
        CGsubZ = []
        CGsubY = []
        for component in self.components:               
            try:
                component.plotShape()
                initialComponentInertia = component.getInertia(0, self.rocket.rigidBody.state)
                CGsubZ.append(initialComponentInertia.CG.Z)
                CGsubY.append(initialComponentInertia.CG.Y)
            
            except AttributeError:
                pass # Plotting function not implemented
        
        return CGsubZ, CGsubY

Ancestors

Methods

def getComponentsOfType(self, type)
Expand source code
def getComponentsOfType(self, type):
    matchingComponents = []
    for comp in self.components:
        if isinstance(comp, type):
            matchingComponents.append(comp)

    return matchingComponents
def getLength(self)
Expand source code
def getLength(self):
    try:
        topBodyComponent = self._getFirstComponentOfType(self.components, BodyComponent)
        topLocationZ = topBodyComponent.position.Z

        bottomBodyComponent = self._getFirstComponentOfType(reversed(self.components), BodyComponent)
        bottomLocationZ = bottomBodyComponent.position.Z - bottomBodyComponent.length
        return (topLocationZ - bottomLocationZ)
    except AttributeError:
        return None # No body components in current Stage
def initializeSubComponents(self)
Expand source code
def initializeSubComponents(self):
    # Assume each sub dictionary represents a component
    subDicts = self.stageDictReader.getImmediateSubDicts()

    def returnZeroIfBodyComponent(componentDictPath):
        className = self.stageDictReader.getString(componentDictPath + ".class")
        componentClass = stringNameToClassMap[className]
        
        if issubclass(componentClass, BodyComponent):
            return 0, componentDictPath
        else: 
            return 1, componentDictPath

    # Make sure BodyComponents are initializede before others
        # This allows non-body components (which are attached to body components) like the fins
            # to get info about the body radius where they're mounted
    subDicts.sort(key=returnZeroIfBodyComponent)

    # Initialize each component, add to componets
    for componentDict in subDicts:
        newComponent = rocketComponentFactory(componentDict, self.rocket, self)
        self.components.append(newComponent)

        try:
            if newComponent.name not in self.__dict__: # Avoid clobbering existing info
                setattr(self, newComponent.name, newComponent) # Make component available as stage.componentName
        except AttributeError:
            pass # Expect to arrive here if the component doesn't define a "name" attribute   
def plotShape(self)

Calls .plotShape() on all subcomponents Returns CGsubZ, CGsubY (two lists of scalars for a 2D plot)

Expand source code
def plotShape(self):
    '''
        Calls .plotShape() on all subcomponents
        Returns CGsubZ, CGsubY (two lists of scalars for a 2D plot)
    '''
    CGsubZ = []
    CGsubY = []
    for component in self.components:               
        try:
            component.plotShape()
            initialComponentInertia = component.getInertia(0, self.rocket.rigidBody.state)
            CGsubZ.append(initialComponentInertia.CG.Z)
            CGsubY.append(initialComponentInertia.CG.Y)
        
        except AttributeError:
            pass # Plotting function not implemented
    
    return CGsubZ, CGsubY

Inherited members