Boundary Recipes
AbstractBoundary
and boundaryHeatTransferRate
are your places to go for more information.
A Fixed Heat Flux Boundary
struct FixedHeatFluxBoundary <: AbstractBoundary
# Here goes the parameters that we will have access to in this boundaries
# method for the boundaryHeatTransferRate. We wont actually have any for
# this boundary
# heat flux we want to use as the fixed value.
# Our new boundary has to have a constructor that meets the signature
# requirements detailed in the AbstractBoundary documentation. For this
# boundary we don't need any of the inputs so we'll just discard them.
FixedHeatFluxBoundary(_, _, _, _) = new()
end
This boundary needs to implement a method for the boundaryHeatTransferRate
function. This is what will be called on each node of the simulation that is on the boundary. As with the constructor, our simple boundary can ignore all the fields.
function Types.boundaryHeatTransferRate(_, _, _::FixedHeatFluxBoundary)
# Return a floating point representing the heat flux density
return 10.0
end
A Variable Heat Flux Boundary
Let's raise the stakes a little (but not much), we'll make a new boundary that has a heat flux density that changes depending only on the current time of the simulation. We'll have to make a new AbstractProblemParams
(the one used here is from A Variable Heat Flux Parameter Set )
struct FixedHeatFluxBoundary <: Types.AbstractBoundary
# All we need for this one is the heat flux we want to.
heatFluxDensity::Float64
# Our new boundary has to have a constructor that meets the signature
# requirements detailed in the AbstractBoundary documentation. For this
# boundary we only actually need two of the inputs, so we'll just discard
# the rest of them.
function RecoatCoolBoundary(_, cts, prob::Problem, _)
# Normally a boundary's constructor would use the params field that we
# gave to the problem quite a bit, so it's handy to make it easier to
# access. In this case it will only be used once, but I'll keep this
# here out of good habit anyway.
param = prob.params
# The current time step (cts) is given in, so lets grab its time to use
# in our heat flux function.
time = cts.t
heatflux = param.heatflux(time)
# Another useful field in prob is the problems geometry. Here we'll use
# this to find out the area of a node, to convert our heat flux into a
# heat flux density.
geom = prob.geometry
# Finding the area of the face of a node parallel to the y-axis
area = geom.X * geom.Z
heatfluxdensity = heatflux / area
return new(heatfluxdensity)
end
end
function Types.boundaryHeatTransferRate(_, _, p::FixedHeatFluxBoundary)
# Return a floating point representing the heat flux density
return p.heatfluxdensity
end
A Cooling Recoat Boundary
This boundary behaves very similarly to the built-in boundary RecoatBoundary
, but it uses the temperatures for the air and internal surfaces. It also conditionally calls coolingStart
to make sure that the start time of the cool down stage is set. And the lamp is turned off (even if there is a recoat lamp power set).
struct RecoatCoolBoundary <: Types.AbstractBoundary
# All of the parameters we need to pass into the boundaryHeatTransferRate function
overheadTemp::Float64
surfaceTemp::Float64
eₗ::Array{Float64,3}
ε::Float64
airTemp::Float64
h::Float64
shadow::Vector{Bool}
Po::Float64
function RecoatCoolBoundary(pts, cts, prob::Problem, ls::Types.LoadStep)
param = prob.params
# Use an overhead power of 0 w
param.overheadTemp = overheadTemp = param.overheadHeatupFunc(0.0, param.overheadTemp, cts)
# If the coolStart hasn't been set, set it
if isnan(prob.params.coolStart)
coolingStart(pts.t, cts.t, prob.params)
end
# Find how far through the cooling parameters the current time step is
tAir = (cts.t - param.coolStart) + param.airCoolStart
tSurface = (cts.t - param.surfaceCoolStart)
# Use the Cool parameters to find the air and surface temperatures
airTemp = param.airCool(tAir)
surfaceTemp = param.surfaceCool(tSurface)
# Get the other parameters to pass through
eₗ = prob.eᵗ
ε = prob.matProp.ε
h = param.convectionCoef
Po = param.percentOverhead
# Calculate the position of the carriage and therefor its shadow
pos = ceil(Int, (param.carriageWidth + prob.geometry.Y_BUILD) * cts.tₚ)
shadowPos = (pos - param.carriageWidth, pos)
shadow = movingObjOverlap(prob.geometry, true, shadowPos)
# Calculate the distance across the bed the recoater has traveled, and
# use it to set the new nodes
recoatDist = pos - param.recoatOffset
if prob.geometry.Y_OFFSET < recoatDist <= prob.geometry.Y_OFFSET + prob.geometry.Y
recoatDist = recoatDist - prob.geometry.Y_OFFSET
recoating!(pts, cts, prob, ls, recoatDist, surfaceTemp)
end
# Any new powder put down is set to have the machines ambient air
# temperature as it's initial temperature
z₂ᵣ = map(first, ls.ind.z₂)
for i in z₂ᵣ
if pts.T[i] == prob.init.T[i]
pts.T[i] = airTemp
end
end
return new(overheadTemp, surfaceTemp, eₗ, ε, airTemp, h, shadow, Po)
end
end
The new method for the boundaryHeatTransferRate
is the same as the method use for the RecoatBoundary
but without the lamp logic.
function Types.boundaryHeatTransferRate(T, i, p::RecoatCoolBoundary)
shadow = p.shadow[i[2]]
eₗ = p.eₗ[i]
return (
convectionFlow(T, p.airTemp, p.h) +
radiationFlow(T, p.surfaceTemp, p.ε) * (shadow || (1 - p.Po)) +
radiationFlow(T, p.overheadTemp, p.ε) * !shadow * p.Po
)
end
Have a look at A Cooling Recoat Load and Load Set to see how to use this in a load.
A Cooling Recoat Return Boundary
To make use of the above boundary, it would be handy to have a load that represents the recoat carriage returning to its initial position (going in the other direction to the above load).
struct RecoatCoolReturnBoundary <: AbstractBoundary
overheadTemp::Float64
surfaceTemp::Float64
eₗ::Array{Float64,3}
ε::Float64
airTemp::Float64
h::Float64
shadow::Vector{Bool}
Po::Float64
function RecoatCoolReturnBoundary(pts, cts, prob::Problem, ls::Types.LoadStep)
param = prob.params
param.overheadTemp = overheadTemp = param.overheadHeatupFunc(0.0, param.overheadTemp, cts)
# We'll still call this, in case we want to use this boundary before the previous one
if isnan(param.coolStart)
coolingStart(pts.t, cts.t, param)
end
tAir = (cts.t - param.coolStart) + param.airCoolStart
tSurface = (cts.t - param.coolStart) + param.surfaceCoolStart
airTemp = param.airCool(tAir)
surfaceTemp = param.surfaceCool(tSurface)
eₗ = prob.eᵗ
ε = prob.matProp.ε
h = param.convectionCoef
Po = param.percentOverhead
# Calculate the position of the carriage on its return stroke
pos = ceil(Int, (param.carriageWidth + prob.geometry.Y_BUILD) * (1 - cts.tₚ))
shadowPos = (pos - param.carriageWidth, pos)
shadow = movingObjOverlap(prob.geometry, true, shadowPos)
# No need to run any of the recoat logic for this one
return new(overheadTemp, surfaceTemp, eₗ, ε, airTemp, h, shadow, Po)
end
end
function Types.boundaryHeatTransferRate(T, i, p::RecoatCoolReturnBoundary)
shadow = p.shadow[i[2]]
eₗ = p.eₗ[i]
return (
convectionFlow(T, p.airTemp, p.h) +
radiationFlow(T, p.surfaceTemp, p.ε) * (shadow || (1 - p.Po)) +
radiationFlow(T, p.overheadTemp, p.ε) * !shadow * p.Po
)
end