Commit 31891fa3 authored by D1plo1d's avatar D1plo1d

Merging experimental branch with Pronserve

parents 7019d781 b4f620a0
# This file is part of the Printrun suite.
#
# Copyright 2013 Francesco Santini francesco.santini@gmail.com
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
#
# This code is imported from RepetierHost - Original copyright and license:
# Copyright 2011 repetier repetierdev@gmail.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
class GCodeAnalyzer():
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.e = 0
self.emax = 0
self.f = 1000
self.lastX = 0
self.lastY = 0
self.lastZ = 0
self.lastE = 0
self.xOffset = 0
self.yOffset = 0
self.zOffset = 0
self.eOffset = 0
self.lastZPrint = 0
self.layerZ = 0
self.relative = False
self.eRelative = False
self.homeX = 0
self.homeY = 0
self.homeZ = 0
self.maxX = 150
self.maxY = 150
self.maxZ = 150
self.minX = 0
self.minY = 0
self.minZ = 0
self.hasHomeX = False
self.hasHomeY = False
self.hasHomeZ = False
# find a code in a gstring line
def findCode(self, gcode, codeStr):
pattern = re.compile(codeStr + "\\s*(-?[\d.]*)",re.I)
m=re.search(pattern, gcode)
if m == None:
return None
else:
return m.group(1)
def Analyze(self, gcode):
gcode = gcode[:gcode.find(";")].lstrip() # remove comments
if gcode.startswith("@"): return # code is a host command
code_g = self.findCode(gcode, "G")
code_m = self.findCode(gcode, "M")
# we have a g_code
if code_g != None:
code_g = int(code_g)
#get movement codes
if code_g == 0 or code_g == 1 or code_g == 2 or code_g == 3:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
eChanged = False;
code_f = self.findCode(gcode, "F")
if code_f != None:
self.f=float(code_f)
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
if self.relative:
if code_x != None: self.x += float(code_x)
if code_y != None: self.y += float(code_y)
if code_z != None: self.z += float(code_z)
if code_e != None:
e = float(code_e)
if e != 0:
eChanged = True
self.e += e
else:
#absolute coordinates
if code_x != None: self.x = self.xOffset + float(code_x)
if code_y != None: self.y = self.yOffset + float(code_y)
if code_z != None: self.z = self.zOffset + float(code_z)
if code_e != None:
e = float(code_e)
if self.eRelative:
if e != 0:
eChanged = True
self.e += e
else:
# e is absolute. Is it changed?
if self.e != self.eOffset + e:
eChanged = True
self.e = self.eOffset + e
#limit checking
if self.x < self.minX: self.x = self.minX
if self.y < self.minY: self.y = self.minY
if self.z < self.minZ: self.z = self.minZ
if self.x > self.maxX: self.x = self.maxX
if self.y > self.maxY: self.y = self.maxY
if self.z > self.maxZ: self.z = self.maxZ
#Repetier has a bunch of limit-checking code here and time calculations: we are leaving them for now
elif code_g == 28 or code_g == 161:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
homeAll = False
if code_x == None and code_y == None and code_z == None: homeAll = True
if code_x != None or homeAll:
self.hasHomeX = True
self.xOffset = 0
self.x = self.homeX
if code_y != None or homeAll:
self.hasHomeY = True
self.yOffset = 0
self.y = self.homeY
if code_z != None or homeAll:
self.hasHomeZ = True
self.zOffset = 0
self.z = self.homeZ
if code_e != None:
self.eOffset = 0
self.e = 0
elif code_g == 162:
self.lastX = self.x
self.lastY = self.y
self.lastZ = self.z
self.lastE = self.e
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
homeAll = False
if code_x == None and code_y == None and code_z == None: homeAll = True
if code_x != None or homeAll:
self.hasHomeX = True
self.xOffset = 0
self.x = self.maxX
if code_y != None or homeAll:
self.hasHomeY = True
self.yOffset = 0
self.y = self.maxY
if code_z != None or homeAll:
self.hasHomeZ = True
self.zOffset = 0
self.z = self.maxZ
elif code_g == 90: self.relative = False
elif code_g == 91: self.relative = True
elif code_g == 92:
code_x = self.findCode(gcode, "X")
code_y = self.findCode(gcode, "Y")
code_z = self.findCode(gcode, "Z")
code_e = self.findCode(gcode, "E")
if code_x != None:
self.xOffset = self.x - float(code_x)
self.x = self.xOffset
if code_y != None:
self.yOffset = self.y - float(code_y)
self.y = self.yOffset
if code_z != None:
self.zOffset = self.z - float(code_z)
self.z = self.zOffset
if code_e != None:
self.xOffset = self.e - float(code_e)
self.e = self.eOffset
#End code_g != None
if code_m != None:
code_m = int(code_m)
if code_m == 82: self.eRelative = False
elif code_m == 83: self.eRelative = True
def print_status(self):
attrs = vars(self)
print '\n'.join("%s: %s" % item for item in attrs.items())
\ No newline at end of file
P-face.png

8.85 KB

......@@ -16,6 +16,17 @@
import sys
import re
import math
def deltalen(a,b):
d = object()
d.x = b.x - a.x
d.y = b.y - a.y
d.z = b.z - a.z
return math.sqrt((d.x*d.x)+(d.y*d.y)+(d.z*d.z))
class Line(object):
def __init__(self,l):
......@@ -29,6 +40,7 @@ class Line(object):
self.raw = l.upper().lstrip()
self.imperial = False
self.relative = False
self.relative_e = False
if ";" in self.raw:
self.raw = self.raw.split(";")[0]
......@@ -77,53 +89,55 @@ class Line(object):
return ""
def _get_float(self,which):
return float(self.regex.findall(self.raw.split(which)[1])[0])
try:
return float(self.regex.findall(self.raw.split(which)[1])[0])
except:
return None
def _parse_coordinates(self):
if "X" in self.raw:
self._x = self._get_float("X")
if "Y" in self.raw:
self._y = self._get_float("Y")
if "Z" in self.raw:
self._z = self._get_float("Z")
if "E" in self.raw:
self.e = self._get_float("E")
if "F" in self.raw:
self.f = self._get_float("F")
try:
if "X" in self.raw:
self._x = self._get_float("X")
except:
pass
try:
if "Y" in self.raw:
self._y = self._get_float("Y")
except:
pass
try:
if "Z" in self.raw:
self._z = self._get_float("Z")
except:
pass
try:
if "E" in self.raw:
self.e = self._get_float("E")
except:
pass
try:
if "F" in self.raw:
self.f = self._get_float("F")
except:
pass
def is_move(self):
return "G1" in self.raw or "G0" in self.raw
return self.command() and ("G1" in self.raw or "G0" in self.raw)
def __str__(self):
return self.raw
class Layer(object):
def __init__(self,lines):
self.lines = lines
class GCode(object):
def __init__(self,data):
self.lines = [Line(i) for i in data]
self._preprocess()
def _preprocess(self):
#checks for G20, G21, G90 and G91, sets imperial and relative flags
imperial = False
relative = False
for line in self.lines:
if line.command() == "G20":
imperial = True
elif line.command() == "G21":
imperial = False
elif line.command() == "G90":
relative = False
elif line.command() == "G91":
relative = True
elif line.is_move():
line.imperial = imperial
line.relative = relative
def measure(self):
xmin = 999999999
ymin = 999999999
......@@ -132,7 +146,8 @@ class GCode(object):
ymax = -999999999
zmax = -999999999
relative = False
relative_e = False
current_x = 0
current_y = 0
current_z = 0
......@@ -147,13 +162,13 @@ class GCode(object):
x = line.x
y = line.y
z = line.z
if line.relative:
x = current_x + (x or 0)
y = current_y + (y or 0)
z = current_z + (z or 0)
if x and line.e:
if x < xmin:
xmin = x
......@@ -169,22 +184,137 @@ class GCode(object):
zmin = z
if z > zmax:
zmax = z
current_x = x or current_x
current_y = y or current_y
current_z = z or current_z
return ( (xmin,xmax),(ymin,ymax),(zmin,zmax) )
class GCode(object):
def __init__(self,data):
self.lines = [Line(i) for i in data]
self._preprocess()
self._create_layers()
def _preprocess(self):
#checks for G20, G21, G90 and G91, sets imperial and relative flags
imperial = False
relative = False
relative_e = False
for line in self.lines:
if line.command() == "G20":
imperial = True
elif line.command() == "G21":
imperial = False
elif line.command() == "G90":
relative = False
relative_e = False
elif line.command() == "G91":
relative = True
relative_e = True
elif line.command() == "M82":
relative_e = False
elif line.command() == "M83":
relative_e = True
elif line.is_move():
line.imperial = imperial
line.relative = relative
line.relative_e = relative_e
def _create_layers(self):
self.layers = []
prev_z = None
cur_z = 0
cur_lines = []
layer_index = []
temp_layers = {}
for line in self.lines:
if line.command() == "G92" and line.z != None:
cur_z = line.z
elif line.is_move():
if line.z != None:
if line.relative:
cur_z += line.z
else:
cur_z = line.z
if cur_z != prev_z:
old_lines = temp_layers.pop(prev_z,[])
old_lines += cur_lines
temp_layers[prev_z] = old_lines
if not prev_z in layer_index:
layer_index.append(prev_z)
cur_lines = []
cur_lines.append(line)
prev_z = cur_z
old_lines = temp_layers.pop(prev_z,[])
old_lines += cur_lines
temp_layers[prev_z] = old_lines
if not prev_z in layer_index:
layer_index.append(prev_z)
layer_index.sort()
for idx in layer_index:
cur_lines = temp_layers[idx]
has_movement = False
for l in cur_lines:
if l.is_move() and l.e != None:
has_movement = True
break
if has_movement:
self.layers.append(Layer(cur_lines))
def num_layers(self):
return len(self.layers)
def measure(self):
xmin = 999999999
ymin = 999999999
zmin = 0
xmax = -999999999
ymax = -999999999
zmax = -999999999
for l in self.layers:
xd,yd,zd = l.measure()
if xd[0] < xmin:
xmin = xd[0]
if xd[1] > xmax:
xmax = xd[1]
if yd[0] < ymin:
ymin = yd[0]
if yd[1] > ymax:
ymax = yd[1]
if zd[0] < zmin:
zmin = zd[0]
if zd[1] > zmax:
zmax = zd[1]
self.xmin = xmin
self.ymin = ymin
self.zmin = zmin
self.xmax = xmax
self.ymin = ymin
self.ymax = ymax
self.zmin = zmin
self.zmax = zmax
self.width = xmax-xmin
self.depth = ymax-ymin
self.height = zmax-zmin
self.width = xmax - xmin
self.depth = ymax - ymin
self.height = zmax - zmin
def filament_length(self):
total_e = 0
......@@ -196,7 +326,7 @@ class GCode(object):
total_e += cur_e
cur_e = line.e
elif line.is_move() and line.e:
if line.relative:
if line.relative_e:
cur_e += line.e
else:
cur_e = line.e
......@@ -210,6 +340,8 @@ def main():
print "usage: %s filename.gcode" % sys.argv[0]
return
# d = [i.replace("\n","") for i in open(sys.argv[1])]
# gcode = GCode(d)
gcode = GCode(list(open(sys.argv[1])))
gcode.measure()
......@@ -219,6 +351,8 @@ def main():
print "\tY: %0.02f - %0.02f (%0.02f)" % (gcode.ymin,gcode.ymax,gcode.depth)
print "\tZ: %0.02f - %0.02f (%0.02f)" % (gcode.zmin,gcode.zmax,gcode.height)
print "Filament used: %0.02fmm" % gcode.filament_length()
print "Number of layers: %d" % gcode.num_layers()
if __name__ == '__main__':
main()
plater.png

2.04 KB

......@@ -20,6 +20,7 @@ from threading import Thread
from select import error as SelectError
import time, getopt, sys
import platform, os
from GCodeAnalyzer import GCodeAnalyzer
def control_ttyhup(port, disable_hup):
"""Controls the HUPCL"""
......@@ -69,7 +70,11 @@ class printcore():
self.print_thread = None
if port is not None and baud is not None:
self.connect(port, baud)
self.analyzer = GCodeAnalyzer()
self.xy_feedrate = None
self.z_feedrate = None
self.pronterface = None
def disconnect(self):
"""Disconnects from printer and pauses the print
"""
......@@ -219,17 +224,69 @@ class printcore():
self.print_thread.start()
return True
# run a simple script if it exists, no multithreading
def runSmallScript(self, filename):
if filename == None: return
f = None
try:
f = open(filename)
except:
pass
if f != None:
for i in f:
l = i.replace("\n", "")
l = l[:l.find(";")] #remove comment
self.send_now(l)
f.close()
def pause(self):
"""Pauses the print, saving the current position.
"""
if not self.printing: return False
self.paused = True
self.printing = False
self.print_thread.join()
# try joining the print thread: enclose it in try/except because we might be calling it from the thread itself
try:
self.print_thread.join()
except:
pass
self.print_thread = None
# saves the status
self.pauseX = self.analyzer.x-self.analyzer.xOffset;
self.pauseY = self.analyzer.y-self.analyzer.yOffset;
self.pauseZ = self.analyzer.z-self.analyzer.zOffset;
self.pauseE = self.analyzer.e-self.analyzer.eOffset;
self.pauseF = self.analyzer.f;
self.pauseRelative = self.analyzer.relative;
def resume(self):
"""Resumes a paused print.
"""
if not self.paused: return False
if self.paused:
#restores the status
self.send_now("G90") # go to absolute coordinates
xyFeedString = ""
zFeedString = ""
if self.xy_feedrate != None: xyFeedString = " F" + str(self.xy_feedrate)
if self.z_feedrate != None: zFeedString = " F" + str(self.z_feedrate)
self.send_now("G1 X" + str(self.pauseX) + " Y" + str(self.pauseY) + xyFeedString)
self.send_now("G1 Z" + str(self.pauseZ) + zFeedString)
self.send_now("G92 E" + str(self.pauseE))
if self.pauseRelative: self.send_now("G91") # go back to relative if needed
#reset old feed rate
self.send_now("G1 F" + str(self.pauseF))
self.paused = False
self.printing = True
self.print_thread = Thread(target = self._print)
......@@ -287,11 +344,24 @@ class printcore():
self.sentlines = {}
self.log = []
self.sent = []
try:
self.print_thread.join()
except: pass
self.print_thread = None
if self.endcb:
#callback for printing done
try: self.endcb()
except: pass
#now only "pause" is implemented as host command
def processHostCommand(self, command):
command = command.lstrip()
if command.startswith(";@pause"):
if self.pronterface != None:
self.pronterface.pause(None)
else:
self.pause()
def _sendnext(self):
if not self.printer:
return
......@@ -312,6 +382,13 @@ class printcore():
return
if self.printing and self.queueindex < len(self.mainqueue):
tline = self.mainqueue[self.queueindex]
#check for host command
if tline.lstrip().startswith(";@"):
#it is a host command: pop it from the list
self.mainqueue.pop(self.queueindex)
self.processHostCommand(tline)
return
tline = tline.split(";")[0]
if len(tline) > 0:
self._send(tline, self.lineno, True)
......@@ -335,6 +412,7 @@ class printcore():
self.sentlines[lineno] = command
if self.printer:
self.sent.append(command)
self.analyzer.Analyze(command) # run the command through the analyzer
if self.loud:
print "SENT: ", command
if self.sendcb:
......
......@@ -223,7 +223,7 @@ class MainToolbar(wx.BoxSizer):
root.serialport = wx.ComboBox(root.panel, -1,
choices = root.scanserial(),
style = wx.CB_DROPDOWN, size = (100, 25))
style = wx.CB_DROPDOWN, size = (150, 25))
root.serialport.SetToolTip(wx.ToolTip("Select Port Printer is connected to"))
root.rescanports()
self.Add(root.serialport)
......
This diff is collapsed.
......@@ -85,7 +85,7 @@ class Tee(object):
class PronterWindow(MainWindow, pronsole.pronsole):
def __init__(self, filename = None, size = winsize):
pronsole.pronsole.__init__(self)
self.settings.build_dimensions = '200x200x100+0+0+0' #default build dimensions are 200x200x100 with 0, 0, 0 in the corner of the bed
self.settings.build_dimensions = '200x200x100+0+0+0+0+0+0' #default build dimensions are 200x200x100 with 0, 0, 0 in the corner of the bed
self.settings.last_bed_temperature = 0.0
self.settings.last_file_path = ""
self.settings.last_temperature = 0.0
......@@ -93,7 +93,11 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.settings.preview_grid_step1 = 10.
self.settings.preview_grid_step2 = 50.
self.settings.bgcolor = "#FFFFFF"
self.helpdict["build_dimensions"] = _("Dimensions of Build Platform\n & optional offset of origin\n\nExamples:\n XXXxYYY\n XXX,YYY,ZZZ\n XXXxYYYxZZZ+OffX+OffY+OffZ")
self.pauseScript = "pause.gcode"
self.endScript = "end.gcode"
self.helpdict["build_dimensions"] = _("Dimensions of Build Platform\n & optional offset of origin\n & optional switch position\n\nExamples:\n XXXxYYY\n XXX,YYY,ZZZ\n XXXxYYYxZZZ+OffX+OffY+OffZ\nXXXxYYYxZZZ+OffX+OffY+OffZ+HomeX+HomeY+HomeZ")
self.helpdict["last_bed_temperature"] = _("Last Set Temperature for the Heated Print Bed")
self.helpdict["last_file_path"] = _("Folder of last opened file")
self.helpdict["last_temperature"] = _("Last Temperature of the Hot End")
......@@ -128,6 +132,30 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.btndict = {}
self.parse_cmdline(sys.argv[1:])
self.build_dimensions_list = self.get_build_dimensions(self.settings.build_dimensions)
#initialize the code analyzer with the correct sizes. There must be a more general way to do so
# minimum = offset
self.p.analyzer.minX = self.build_dimensions_list[3]
self.p.analyzer.minY = self.build_dimensions_list[4]
self.p.analyzer.minZ = self.build_dimensions_list[5]
#max = offset + bedsize
self.p.analyzer.maxX = self.build_dimensions_list[3] + self.build_dimensions_list[0]
self.p.analyzer.maxY = self.build_dimensions_list[4] + self.build_dimensions_list[1]
self.p.analyzer.maxZ = self.build_dimensions_list[5] + self.build_dimensions_list[2]
self.p.analyzer.homeX = self.build_dimensions_list[6]
self.p.analyzer.homeY = self.build_dimensions_list[7]
self.p.analyzer.homeZ = self.build_dimensions_list[8]
#set feedrates in printcore for pause/resume
self.p.xy_feedrate = self.settings.xy_feedrate
self.p.z_feedrate = self.settings.z_feedrate
#make printcore aware of me
self.p.pronterface = self
self.panel.SetBackgroundColour(self.settings.bgcolor)
customdict = {}
try:
......@@ -183,6 +211,8 @@ class PronterWindow(MainWindow, pronsole.pronsole):
wx.CallAfter(self.pausebtn.Disable)
wx.CallAfter(self.printbtn.SetLabel, _("Print"))
self.p.runSmallScript(self.endScript)
param = self.settings.final_command
if not param:
return
......@@ -268,16 +298,16 @@ class PronterWindow(MainWindow, pronsole.pronsole):
if f>0:
wx.CallAfter(self.btemp.SetValue, str(f))
self.set("last_bed_temperature", str(f))
wx.CallAfter(self.setboff.SetBackgroundColour, "")
wx.CallAfter(self.setboff.SetForegroundColour, "")
wx.CallAfter(self.setboff.SetBackgroundColour, None)
wx.CallAfter(self.setboff.SetForegroundColour, None)
wx.CallAfter(self.setbbtn.SetBackgroundColour, "#FFAA66")
wx.CallAfter(self.setbbtn.SetForegroundColour, "#660000")
wx.CallAfter(self.btemp.SetBackgroundColour, "#FFDABB")
else:
wx.CallAfter(self.setboff.SetBackgroundColour, "#0044CC")
wx.CallAfter(self.setboff.SetForegroundColour, "white")
wx.CallAfter(self.setbbtn.SetBackgroundColour, "")
wx.CallAfter(self.setbbtn.SetForegroundColour, "")
wx.CallAfter(self.setbbtn.SetBackgroundColour, None)
wx.CallAfter(self.setbbtn.SetForegroundColour, None)
wx.CallAfter(self.btemp.SetBackgroundColour, "white")
wx.CallAfter(self.btemp.Refresh)
......@@ -287,16 +317,16 @@ class PronterWindow(MainWindow, pronsole.pronsole):
if f > 0:
wx.CallAfter(self.htemp.SetValue, str(f))
self.set("last_temperature", str(f))
wx.CallAfter(self.settoff.SetBackgroundColour, "")
wx.CallAfter(self.settoff.SetForegroundColour, "")
wx.CallAfter(self.settoff.SetBackgroundColour, None)
wx.CallAfter(self.settoff.SetForegroundColour, None)
wx.CallAfter(self.settbtn.SetBackgroundColour, "#FFAA66")
wx.CallAfter(self.settbtn.SetForegroundColour, "#660000")
wx.CallAfter(self.htemp.SetBackgroundColour, "#FFDABB")
else:
wx.CallAfter(self.settoff.SetBackgroundColour, "#0044CC")
wx.CallAfter(self.settoff.SetForegroundColour, "white")
wx.CallAfter(self.settbtn.SetBackgroundColour, "")
wx.CallAfter(self.settbtn.SetForegroundColour, "")
wx.CallAfter(self.settbtn.SetBackgroundColour, None)
wx.CallAfter(self.settbtn.SetForegroundColour, None)
wx.CallAfter(self.htemp.SetBackgroundColour, "white")
wx.CallAfter(self.htemp.Refresh)
......@@ -1336,7 +1366,9 @@ class PronterWindow(MainWindow, pronsole.pronsole):
#print "Not printing, cannot pause."
return
self.p.pause()
self.p.runSmallScript(self.pauseScript)
self.paused = True
#self.p.runSmallScript(self.pauseScript)
self.extra_print_time += int(time.time() - self.starttime)
wx.CallAfter(self.pausebtn.SetLabel, _("Resume"))
else:
......@@ -1475,9 +1507,12 @@ class PronterWindow(MainWindow, pronsole.pronsole):
"[^\d+-]*(\d+)?" + # Z build size
"[^\d+-]*([+-]\d+)?" + # X corner coordinate
"[^\d+-]*([+-]\d+)?" + # Y corner coordinate
"[^\d+-]*([+-]\d+)?" # Z corner coordinate
"[^\d+-]*([+-]\d+)?" + # Z corner coordinate
"[^\d+-]*([+-]\d+)?" + # X endstop
"[^\d+-]*([+-]\d+)?" + # Y endstop
"[^\d+-]*([+-]\d+)?" # Z endstop
,bdim).groups()
defaults = [200, 200, 100, 0, 0, 0]
defaults = [200, 200, 100, 0, 0, 0, 0, 0, 0]
bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)]
return bdl_float
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment