diff --git a/host/bin/curl.zip b/host/bin/curl.zip new file mode 100644 index 000000000..6db59233d Binary files /dev/null and b/host/bin/curl.zip differ diff --git a/host/bin/start.bat b/host/bin/start.bat index dc15480e2..92dcf2997 100644 --- a/host/bin/start.bat +++ b/host/bin/start.bat @@ -1 +1,103 @@ -java -Dlog4j.configurationFile=log4j2.properties -Djava.library.path=os/win64 -cp lib/*;. org.area515.resinprinter.server.Main > log.out 2> log.err \ No newline at end of file +@if (@CodeSection == @Batch) @then + +@echo off +setlocal + +rem TODO: Check if java is installed and if not install! + +rem // Get repo version from config.properties +rem this is for when the code works properly on windows with updateRepo, watchout the 10 characters could be wrong! +rem for /F "delims=" %%a in ('findstr /I "updateRepo" %HOMEPATH%/3dPrinters/config.properties') do set "updateRepo=%%a" + +for /F "delims=" %%a in ('findstr /I "printerProfileRepo" config.properties') do set "updateRepo=%%a" + +IF NOT "%1"=="" ( + set updateRepo=%1 +) ELSE ( + IF "%updateRepo%"=="" ( + set updateRepo=area515/Photonic3D + ) ELSE ( + rem set updateRepo=%updateRepo:~10% when changing to updateREpo + set updateRepo=%updateRepo:~19% + ) +) + +echo %updateRepo% + +rem // download dependencies (unzip and curl) **WARNING** bitsadmin.exe is deprecated but seems available from xp to win10 for now! +rem // Might break in future but windows has no proper alternative +rem // also it's extremely slow to start connecting! But hey it works(ish) +if NOT exist %CD%\unzip.exe ( +echo installing unzip.exe +bitsadmin.exe /transfer "Unzip.exe" https://github.com/%updateRepo%/raw/master/host/bin/unzip.exe "%cd%\unzip.exe" +) + +if NOT exist %CD%\curl\bin\curl.exe ( + echo installing curl + IF NOT exist curl.zip ( + bitsadmin.exe /transfer "Curl" https://github.com/%updateRepo%/raw/master/host/bin/curl.zip "%cd%\curl.zip" + unzip -o curl.zip + ) ELSE ( + unzip -o curl.zip + ) + del -Q curl.zip +) + +rem // Get latest release file from github +%CD%\curl\bin\curl -outf https://api.github.com/repos/%updateRepo%/releases/latest + +rem // Set location of downloaded release file (json) +set "latestRelease=utf" + +rem // Find current repoversion from build.number +for /F "delims=" %%a in ('findstr /I "repo.version" build.number') do set "repoVersion=%%a" + +rem // Cut off repo.version !WARNING! when this changes it will break the code! +set repoVersion=%repoVersion:~13% + +rem // Jscript call to parse JSON +for /f "delims=" %%I in ('cscript /nologo /e:JScript "%~f0" "%latestRelease%"') do set "%%~I" + + +echo Network TAG: %tag_name% +echo Local TAG : %repoVersion% + +del -Q utf + +rem // Determing if updating is needed +IF "%tag_name%"=="~13" ( + echo Download URL = %browser_download_url% + %CD%\curl\bin\curl -L %browser_download_url% > Update.zip + unzip -o Update + java -Dlog4j.configurationFile=log4j2.properties -Djava.library.path=os/win64 -cp lib/*;. org.area515.resinprinter.server.Main > log.out 2> log.err +) ELSE ( + IF "%tag_name%"=="%repoVersion%" ( + echo No update needed + java -Dlog4j.configurationFile=log4j2.properties -Djava.library.path=os/win64 -cp lib/*;. org.area515.resinprinter.server.Main > log.out 2> log.err + ) ELSE ( + echo Download URL = %browser_download_url% + %CD%\curl\bin\curl -L %browser_download_url% > Update.zip + unzip -o Update + del -Q Update.zip + java -Dlog4j.configurationFile=log4j2.properties -Djava.library.path=os/win64 -cp lib/*;. org.area515.resinprinter.server.Main > log.out 2> log.err + ) +) + +goto :EOF + +@end // end batch / begin JScript chimera + +var fso = WSH.CreateObject('scripting.filesystemobject'), + latestRelease = fso.OpenTextFile(WSH.Arguments(0), 1); + +eval('obj = ' + latestRelease.ReadAll()); +latestRelease.Close(); + +function walk(tree) { + for (var i in tree) { + if (typeof tree[i] === 'object') walk(tree[i]); + else WSH.Echo(i + '=' + tree[i]); + } +} + +walk(obj); diff --git a/host/bin/unzip.exe b/host/bin/unzip.exe new file mode 100644 index 000000000..e9dc44dfe Binary files /dev/null and b/host/bin/unzip.exe differ diff --git a/host/build.gradle b/host/build.gradle index eae46015d..d88bccdb0 100644 --- a/host/build.gradle +++ b/host/build.gradle @@ -26,8 +26,7 @@ dependencies { exclude module: 'jsr311-api' } - //compile 'org.slf4j:log4j-over-slf4j:1.7.22' - //compile 'org.slf4j:jul-to-slf4j:1.7.22' + compile 'commons-beanutils:commons-beanutils:1.9.3' compile 'com.pi4j:pi4j-core:1.1' compile 'net.java.dev.jna:jna:4.2.2' compile 'org.apache.xmlgraphics:xmlgraphics-commons:1.5' @@ -57,9 +56,9 @@ dependencies { compile 'com.jcraft:jsch:0.1.53' compile 'org.scream3r:jssc:2.8.0' - compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8' - compile 'org.apache.logging.log4j:log4j-api:2.8' - compile 'org.apache.logging.log4j:log4j-core:2.8' + compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.2' + compile 'org.apache.logging.log4j:log4j-api:2.8.2' + compile 'org.apache.logging.log4j:log4j-core:2.8.2' compile 'org.apache.james:apache-mime4j:0.6.1' diff --git a/host/conf/config.properties b/host/conf/config.properties index 68378b627..78ae30182 100644 --- a/host/conf/config.properties +++ b/host/conf/config.properties @@ -1,18 +1,22 @@ fakeserial=true removeJobOnCompletion=false printerHostPort=9091 -#hostGUI=resources -#hostGUI=gui-prototypes/material-resin/app -#hostGUI=../../CWH-Reify3D/Customer/resources -hostGUI=resourcesnew visibleCards=printers,printJobs,printables,settings performedOneTimeInstall=false limitLiveStreamToOneCPU=false scriptEngineLanguage=js forceCalibrationOnFirstUse=false +#hostGUI is no longer used, the "skins" key should be used instead +#hostGUI=resources +#hostGUI=gui-prototypes/material-resin/app +#hostGUI=../../CWH-Reify3D/Customer/resources +skins=[{"name":"Main skin", "welcomeFiles":["index.html"], "resourceBase": "resourcesnew", "active": true}] + feature.org.area515.resinprinter.discover.UPNPAdvertiser=true feature.org.area515.resinprinter.usbimport.USBUploader=true +#feature.org.area515.resinprinter.util.cron.CronFeature=true +#featureSettings.org.area515.resinprinter.util.cron.CronFeature=[{"taskName":"Show directory listing in Windows every night at 1:15am", "taskClassName":"org.area515.resinprinter.actions.osscript.ExecuteNativeOSCommandRunnable", "cronString":"15 1 * * *", "taskSettings":{"shellCommands":["cmd", "/c", "dir"]}}] #notify.org.area515.resinprinter.notification.EmailOnCompletionNotifier=true #notify.org.area515.resinprinter.notification.PauseOnErrorNotifier=true @@ -27,6 +31,7 @@ printFileProcessor.org.area515.resinprinter.minercube.MinerCubePrintFileProcesso printFileProcessor.org.area515.resinprinter.printphoto.ImagePrintFileProcessor=true printFileProcessor.org.area515.resinprinter.text.TextFilePrintFileProcessor=true printFileProcessor.org.area515.resinprinter.printphoto.SVGImagePrintFileProcessor=true +printFileProcessor.org.area515.resinprinter.printphoto.micoin.CoinFileProcessor=true displayDevice.org.area515.resinprinter.display.dispmanx.RaspberryPiMainLCDScreen=true displayDevice.org.area515.resinprinter.display.dispmanx.RaspberryPiForceTVScreen=true @@ -34,7 +39,7 @@ displayDevice.org.area515.resinprinter.display.LastAvailableDisplay=true displayDevice.org.area515.resinprinter.display.SimulatedDisplay=true #Only enable this when there is at least one admin user!!! -#useAuthentication=false +#useAuthentication=true UserManagementFeatureImplementation=org.area515.resinprinter.security.keystore.KeystoreLoginService #http://www.optoma.de/uploads/RS232/DS309-RS232-en.pdf @@ -98,7 +103,7 @@ userKeystorePassword=usersKeystorePassword #The following is an optional parameter. #You only need to specify this parameter if you have a certificate signed by a CA. -#This certificate must be imported into into the keystore specified in the keystoreFilename above. +#This certificate must be imported into into the keystore specified in the "keystoreFilename" key in this property file. #The certificate must be imported into the keystore using the alias specified below. #This MUST also be the hostname that the certificate was issued to. #***************************************** diff --git a/host/conf/eclipselog4j2.properties b/host/conf/eclipselog4j2.properties index e31df3788..8ee3a87f5 100644 --- a/host/conf/eclipselog4j2.properties +++ b/host/conf/eclipselog4j2.properties @@ -14,7 +14,7 @@ appenders=console appender.console.type=Console appender.console.name=STDOUT appender.console.layout.type=PatternLayout -appender.console.layout.pattern=[%t] %m%n +appender.console.layout.pattern=%d [%t] %m%n loggers=hex,serial,printer,media,slicer,scan,customizer,stlfileprocessor,servlet #loggers=customizer,stlfileprocessor @@ -41,7 +41,7 @@ logger.printer.additivity=false logger.printer.appenderRefs=consoleRef logger.printer.appenderRef.consoleRef.ref=STDOUT logger.slicer.name=org.area515.resinprinter.slice.ZSlicer -logger.slicer.level=info +logger.slicer.level=debug logger.slicer.additivity=false logger.slicer.appenderRefs=rolling logger.slicer.appenderRef.rolling.ref=STDOUT diff --git a/host/conf/imaginglog4j2.properties b/host/conf/imaginglog4j2.properties new file mode 100644 index 000000000..78098ad1c --- /dev/null +++ b/host/conf/imaginglog4j2.properties @@ -0,0 +1,97 @@ +#For troubleshoot log4j2 +status=error + +#This is a properties configuration as opposed to json/XML... +name=PropertiesConfig + +#Properties we can use elsewhere in this file +property.filename=cwh.log + +#Creates all of the appenders that we need to configure later +appenders=console + +#We only defined one appender above so now +appender.console.type=Console +appender.console.name=STDOUT +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=%d [%t] %m%n + +loggers=hex,serial,printer,media,slicer,scan,customizer,stlfileprocessor,servlet,displayframe,abstractprint,currentrender,platformrender,coinrenderer + +#We need to define each of the loggers that we listed in the previous line +logger.media.name=org.area515.resinprinter.services.MediaService +logger.media.level=debug +logger.media.additivity=false +logger.media.appenderRefs=rolling +logger.media.appenderRef.rolling.ref=STDOUT +logger.hex.name=org.area515.resinprinter.projector.HexCodeBasedProjector +logger.hex.level=debug +logger.hex.additivity=false +logger.hex.appenderRefs=consoleRef +logger.hex.appenderRef.consoleRef.ref=STDOUT +logger.serial.name=org.area515.resinprinter.serial.SerialManager +logger.serial.level=debug +logger.serial.additivity=false +logger.serial.appenderRefs=consoleRef +logger.serial.appenderRef.consoleRef.ref=STDOUT +logger.printer.name=org.area515.resinprinter.printer.PrinterManager +logger.printer.level=debug +logger.printer.additivity=false +logger.printer.appenderRefs=consoleRef +logger.printer.appenderRef.consoleRef.ref=STDOUT +logger.slicer.name=org.area515.resinprinter.slice.ZSlicer +logger.slicer.level=info +logger.slicer.additivity=false +logger.slicer.appenderRefs=rolling +logger.slicer.appenderRef.rolling.ref=STDOUT +logger.scan.name=org.area515.resinprinter.slice.ScanlineFillPolygonWork +logger.scan.level=debug +logger.scan.additivity=false +logger.scan.appenderRefs=rolling +logger.scan.appenderRef.rolling.ref=STDOUT +logger.customizer.name=org.area515.resinprinter.services.CustomizerService +logger.customizer.level=debug +logger.customizer.additivity=false +logger.customizer.appenderRefs=consoleRef +logger.customizer.appenderRef.consoleRef.ref=STDOUT +logger.stlfileprocessor.name=org.area515.resinprinter.job.STLFileProcessor +logger.stlfileprocessor.level=debug +logger.stlfileprocessor.additivity=false +logger.stlfileprocessor.appenderRefs=consoleRef +logger.stlfileprocessor.appenderRef.consoleRef.ref=STDOUT +logger.servlet.name=org.eclipse.jetty.servlet.ServletHandler +logger.servlet.level=debug +logger.servlet.additivity=false +logger.servlet.appenderRefs=consoleRef +logger.servlet.appenderRef.rolling.ref=STDOUT +logger.abstractprint.name=org.area515.resinprinter.job.AbstractPrintFileProcessor +logger.abstractprint.level=TRACE +logger.abstractprint.additivity=false +logger.abstractprint.appenderRefs=consoleRef +logger.abstractprint.appenderRef.rolling.ref=STDOUT +logger.displayframe.name=org.area515.resinprinter.display.PrinterDisplayFrame +logger.displayframe.level=TRACE +logger.displayframe.additivity=false +logger.displayframe.appenderRefs=consoleRef +logger.displayframe.appenderRef.rolling.ref=STDOUT +logger.currentrender.name=org.area515.resinprinter.job.render.CurrentImageRenderer +logger.currentrender.level=TRACE +logger.currentrender.additivity=false +logger.currentrender.appenderRefs=consoleRef +logger.currentrender.appenderRef.rolling.ref=STDOUT +logger.platformrender.name=org.area515.resinprinter.twodim.PlatformImageRenderer +logger.platformrender.level=TRACE +logger.platformrender.additivity=false +logger.platformrender.appenderRefs=consoleRef +logger.platformrender.appenderRef.rolling.ref=STDOUT +logger.coinrenderer.name=org.area515.resinprinter.printphoto.micoin.CoinRenderer +logger.coinrenderer.level=TRACE +logger.coinrenderer.additivity=false +logger.coinrenderer.appenderRefs=consoleRef +logger.coinrenderer.appenderRef.rolling.ref=STDOUT + + +#Default logger +rootLogger.level=info +rootLogger.appenderRefs=consoleRef +rootLogger.appenderRef.consoleRef.ref=STDOUT diff --git a/host/launchers/Photonic 3D (ImagingDebug).launch b/host/launchers/Photonic 3D (ImagingDebug).launch new file mode 100644 index 000000000..2f1949d4e --- /dev/null +++ b/host/launchers/Photonic 3D (ImagingDebug).launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/host/printers/KLD-2150-T1.json b/host/printers/KLD-2150-T1.json new file mode 100644 index 000000000..bb2b6bb27 --- /dev/null +++ b/host/printers/KLD-2150-T1.json @@ -0,0 +1,86 @@ + { + "configuration":{ + "name":"KLD-2150-T1", + "machineConfig":{ + "PlatformXSize":134.0, + "PlatformYSize":75.0, + "PlatformZSize":185.0, + "XRenderSize":1920, + "YRenderSize":1080, + "MotorsDriverConfig":{ + "DriverType":"eGENERIC", + "ComPortSettings":{ + "PortName":"Autodetect 3d printer firmware", + "Speed":115200, + "Databits":8, + "Parity":"None", + "Stopbits":"One", + "Handshake":"None" + } + }, + "MonitorDriverConfig":{ + "ComPortSettings":{ + "PortName":null, + "Speed":9600, + "Databits":8, + "Parity":"None", + "Stopbits":"One", + "Handshake":"None" + }, + "UseMask":false, + "DLP_X_Res":2560.0, + "DLP_Y_Res":1600.0, + "OSMonitorID":":0.0" + }, + "PauseOnPrinterResponseRegEx":null, + "OverrideModelNormalsWithRightHandRule":null + }, + "slicingProfile":{ + "DotsPermmX":13.33157894736842, + "DotsPermmY":13.254237288135593, + "XResolution":2560, + "YResolution":1600, + "Direction":"Bottom_Up", + "LiftDistance":5.0, + "SlideTiltValue":0, + "LiftFeedRate":50.0, + "FlipX":false, + "FlipY":true, + "gCodeHeader":"G21 ;Set units to be mm\nG91 ;Relative Positioning\nG28 ; Home Printer\nM650 D${ZLiftDist} S${ZLiftRate} P0; CWH Template Preferences\nM17 ;Enable motors\n; 3000\nM400\nM106\n", + "gCodeFooter":"M18 ;Disable Motors\nM107 ;switch uv off\nG1 Z40; Lift up\n;", + "gCodePreslice":null, + "gCodeLift":"M400\nM107\nG1 Z${ZLiftDist} F25\nG1 Z${(-ZLiftDist+LayerThickness)} F160 \n; 3000\nM400\nM106", + "gCodeShutter":"", + "ZLiftDistanceCalculator":"var value = 3.0;\nif ($CURSLICE > $NumFirstLayers) {\n value = 3.5555555555555420e+000 * Math.pow($buildAreaMM,0) + 4.3333333333334060e-003 * Math.pow($buildAreaMM,1) + 1.1111111111110492e-006 * Math.pow($buildAreaMM,2);\n}\nvalue", + "ZLiftSpeedCalculator":"var value = 0.25;\nif ($CURSLICE > $NumFirstLayers) {\n value = 4.6666666666666705e+000 * Math.pow($buildAreaMM,0) + -7.0000000000000184e-003 * Math.pow($buildAreaMM,1) + 3.3333333333333490e-006 * Math.pow($buildAreaMM,2);\n}\nvalue", + "ProjectorGradientCalculator":"var bulbCenter = new Packages.java.awt.geom.Point2D.Double($buildPlatformXPixels / 2, $buildPlatformYPixels / 2);\nvar bulbFocus = new Packages.java.awt.geom.Point2D.Double($buildPlatformXPixels / 2, $buildPlatformYPixels / 2);\nvar totalSizeOfGradient = $buildPlatformXPixels > $buildPlatformYPixels?$buildPlatformXPixels:$buildPlatformYPixels;\nvar fractions = [0.0, 1.0];\n//Let's start with 20% opaque in the center of the projector bulb\nvar colors = [new Packages.java.awt.Color(0.0, 0.0, 0.0, 0.2), new Packages.java.awt.Color(0.0, 0.0, 0.0, 0.0)];\nnew Packages.java.awt.RadialGradientPaint(\n\tbulbCenter,\n\ttotalSizeOfGradient,\n\tbulbFocus,\n\tfractions,\n\tcolors,\n\tjava.awt.MultipleGradientPaint.CycleMethod.NO_CYCLE)", + "ExposureTimeCalculator":"var value = $FirstLayerTime;\nif ($CURSLICE > $NumFirstLayers) {\n\tvalue = $LayerTime\n}\nvalue", + "TwoDimensionalSettings":{ + "Font":{ + "Name":"Dialog", + "Size":200 + }, + "PlatformHeightMM":1.5, + "ExtrusionHeightMM":1.5, + "PlatformCalculator":"var extrusionX = printImage.getWidth();\nvar extrusionY = printImage.getHeight();\nplatformGraphics.fillRoundRect(centerX - (extrusionX / 2), centerY - (extrusionY / 2), extrusionX, extrusionY, 50, 50);", + "EdgeDetectionDisabled":false, + "ScaleImageToFitPrintArea":true + }, + "InkConfig":[ + { + "PrintMaterialDetector":null, + "Name":"Default", + "SliceHeight":0.1, + "NumberofBottomLayers":7, + "ResinPriceL":65.0, + "FirstLayerTime":70000, + "PercentageOfPrintMaterialConsideredEmpty":10.0, + "LayerTime":16000 + } + ], + "SelectedInk":"Default", + "zliftDistanceGCode":"M650 D${ZLiftDist} S${ZLiftRate}", + "zliftSpeedGCode":"M650 D${ZLiftDist} S${ZLiftRate}" + } + } + } diff --git a/host/printers/Wanhao D7.json b/host/printers/Wanhao D7.json new file mode 100644 index 000000000..166808646 --- /dev/null +++ b/host/printers/Wanhao D7.json @@ -0,0 +1,100 @@ +{ + "configuration":{ + "name":"Wanhao D7", + "machineConfig":{ + "FileVersion":0, + "PlatformXSize":69.0, + "PlatformYSize":120.0, + "PlatformZSize":185.0, + "MaxXFeedRate":0, + "MaxYFeedRate":0, + "MaxZFeedRate":0, + "XRenderSize":1440, + "YRenderSize":2560, + "MotorsDriverConfig":{ + "DriverType":"eGENERIC", + "ComPortSettings":{ + "PortName":"Autodetect 3d printer firmware", + "Speed":115200, + "Databits":8, + "Parity":"None", + "Stopbits":"One", + "Handshake":"None" + } + }, + "MonitorDriverConfig":{ + "DLP_X_Res":1440.0, + "DLP_Y_Res":2560.0, + "MonitorID": "DISPLAY1", + "OSMonitorID": ":0.0", + "DisplayCommEnabled":false, + "ComPortSettings":{ + "PortName":"Autodetect projector", + "Handshake":"None" + }, + "MonitorTop":0, + "MonitorLeft":0, + "MonitorRight":0, + "MonitorBottom":0, + "UseMask":false + } + }, + "slicingProfile":{ + "gCodeHeader": "G21 ;Set units to be mm\nG91 ;Relative Positioning\nM17 ;Enable motors\nM106 S255 ;Enable LED array", + "gCodeFooter": "M106 S0 ;Turn OFF LED array\nG1 Z100.0 F150.0\nG04 P30000\nM18 ;Disable Motors", + "gCodePreslice": null, + "gCodeLift": "M106 S0 ;Disale LED array\nG1 Z(${ZLiftDist} * ${ZDir}) F{${CURSLICE} < $ZNumLiftFirstLayers?$ZLiftBtmRate:${ZLiftRate}}\nG1 Z((${LayerThickness}-${ZLiftDist}) * ${ZDir}) F$ZRetractRate\n; %d$BlankTime\nM106 S255 ;Enable LED array", + "gCodeShutter": null, + "zliftDistanceGCode":"M650 D${ZLiftDist} S${ZLiftRate}", + "zliftSpeedGCode":"M650 D${ZLiftDist} S${ZLiftRate}", + "selectedInkConfigIndex":0, + "DotsPermmX":20.86957, + "DotsPermmY":21.33333, + "XResolution":1440, + "YResolution":2560, + "BlankTime":0, + "PlatformTemp":0, + "ExportSVG":0, + "Export":false, + "ExportPNG":false, + "Direction":"Bottom_Up", + "LiftDistance":5.0, + "SlideTiltValue":0, + "AntiAliasing":false, + "UseMainLiftGCode":false, + "AntiAliasingValue":0.0, + "LiftFeedRate":50.0, + "LiftRetractRate":0.0, + "FlipX":false, + "FlipY":true, + "ZLiftDistanceCalculator":"var value = 9.0;\nif ($CURSLICE > $NumFirstLayers) {\n value = 3.5555555555555420e+000 * Math.pow($buildAreaMM,0) + 4.3333333333334060e-003 * Math.pow($buildAreaMM,1) + 1.1111111111110492e-006 * Math.pow($buildAreaMM,2);\n}\nvalue", + "ZLiftSpeedCalculator":"var value = 0.25;\nif ($CURSLICE > $NumFirstLayers) {\n value = 4.6666666666666705e+000 * Math.pow($buildAreaMM,0) + -7.0000000000000184e-003 * Math.pow($buildAreaMM,1) + 3.3333333333333490e-006 * Math.pow($buildAreaMM,2);\n}\nvalue", + "ProjectorGradientCalculator":"var bulbCenter = new Packages.java.awt.geom.Point2D.Double($buildPlatformXPixels / 2, $buildPlatformYPixels / 2);\nvar bulbFocus = new Packages.java.awt.geom.Point2D.Double($buildPlatformXPixels / 2, $buildPlatformYPixels / 2);\nvar totalSizeOfGradient = $buildPlatformXPixels > $buildPlatformYPixels?$buildPlatformXPixels:$buildPlatformYPixels;\nvar fractions = [0.0, 1.0];\n//Let's start with 20% opaque in the center of the projector bulb\nvar colors = [new Packages.java.awt.Color(0.0, 0.0, 0.0, 0.2), new Packages.java.awt.Color(0.0, 0.0, 0.0, 0.0)];\nnew Packages.java.awt.RadialGradientPaint(\n\tbulbCenter,\n\ttotalSizeOfGradient,\n\tbulbFocus,\n\tfractions,\n\tcolors,\n\tjava.awt.MultipleGradientPaint.CycleMethod.NO_CYCLE)", + "ExposureTimeCalculator":"var value = $FirstLayerTime;\nif ($CURSLICE > $NumFirstLayers) {\n\tvalue = $LayerTime\n}\nvalue", + "SelectedInk":"Default", + "MinTestExposure":0, + "TestExposureStep":0, + "InkConfig":[ + { + "PercentageOfPrintMaterialConsideredEmpty":10.0, + "Name":"Default", + "SliceHeight":0.1, + "LayerTime":8000, + "FirstLayerTime":20000, + "NumberofBottomLayers":10, + "ResinPriceL":65.0 + } + ] + }, + "MachineConfigurationName":"Wanhao D7", + "SlicingProfileName":"generic resin", + "AutoStart":true + }, + "started":true, + "shutterOpen":false, + "displayDeviceID":"0.0", + "currentSlicePauseTime":0, + "status":"Ready", + "printInProgress":false, + "printPaused":false +} diff --git a/host/resourcesnew/2dProperties.html b/host/resourcesnew/2dProperties.html index fbf0c81a5..a0ee48616 100644 --- a/host/resourcesnew/2dProperties.html +++ b/host/resourcesnew/2dProperties.html @@ -54,6 +54,15 @@

Scale to fit print area

Base Platform Calculator

+ + +
+ + + + + diff --git a/host/resourcesnew/cwh/js/copySlicingProfile.js b/host/resourcesnew/cwh/js/copySlicingProfile.js new file mode 100644 index 000000000..3dbef0a69 --- /dev/null +++ b/host/resourcesnew/cwh/js/copySlicingProfile.js @@ -0,0 +1,17 @@ +(function() { + var cwhApp = angular.module('cwhApp'); + + cwhApp.controller("copySLicingProfileController", function ($scope, $uibModalInstance, title, sliceData, nameProfile) { + $scope.title = title; + $scope.sliceData = sliceData; + $scope.save = function () { + $uibModalInstance.close(sliceData); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + + }) + +})(); diff --git a/host/resourcesnew/cwh/js/editMessage.js b/host/resourcesnew/cwh/js/editMessage.js new file mode 100644 index 000000000..8188d0a04 --- /dev/null +++ b/host/resourcesnew/cwh/js/editMessage.js @@ -0,0 +1,18 @@ +(function() { + var cwhApp = angular.module('cwhApp'); + + cwhApp.controller("EditMessageController", function ($scope, $http, $uibModalInstance, title, editMessage, users) { + $scope.users = users; + $scope.title = title; + $scope.editMessage = editMessage; + + $scope.save = function () { + $uibModalInstance.close(editMessage); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + }) + +})(); \ No newline at end of file diff --git a/host/resourcesnew/cwh/js/editPrinter.js b/host/resourcesnew/cwh/js/editPrinter.js index 8ac6f3526..9fbadea97 100644 --- a/host/resourcesnew/cwh/js/editPrinter.js +++ b/host/resourcesnew/cwh/js/editPrinter.js @@ -10,7 +10,8 @@ $scope.parities = ["Even", "Mark", "None", "Odd", "Space"]; $scope.stopBits = ["None", "One", "1.5", "Two"]; $scope.dataBits = [5, 6, 7, 8, 9]; - + $scope.fullScreenModes = ["AlwaysUseFullScreen", "NeverUseFullScreen", "UseFullScreenWhenExclusiveIsAvailable"]; + $http.get('/services/machine/serialPorts/list').success( function (data) { $scope.serialPorts = data; diff --git a/host/resourcesnew/cwh/js/editResin.js b/host/resourcesnew/cwh/js/editResin.js new file mode 100644 index 000000000..466e1d1cd --- /dev/null +++ b/host/resourcesnew/cwh/js/editResin.js @@ -0,0 +1,28 @@ +(function() { + var cwhApp = angular.module('cwhApp'); + + cwhApp.controller("EditResinController", function ($scope, $http, $uibModalInstance, title) { + + // init a new resin profile + $scope.title = title; + $scope.inkConfigArr = { + FirstLayerTime:'', + LayerTime:'', + Name:'', + NumberofBottomLayers:'', + PercentageOfPrintMaterialConsideredEmpty:'', + ResinPriceL:"", SliceHeight:'' + }; + + $scope.save = function () { + console.log($scope.inkConfigArr); + $uibModalInstance.close($scope.inkConfigArr); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + + }) + +})(); \ No newline at end of file diff --git a/host/resourcesnew/cwh/js/index.js b/host/resourcesnew/cwh/js/index.js index 4fcb3304b..8c5acd9a5 100644 --- a/host/resourcesnew/cwh/js/index.js +++ b/host/resourcesnew/cwh/js/index.js @@ -7,6 +7,20 @@ }; }]); + cwhApp.directive('onEnter', function () { + return function (scope, element, attrs) { + element.bind("keydown keypress", function (event) { + if(event.which === 13) { + scope.$apply(function (){ + scope.$eval(attrs.onEnter); + }); + + event.preventDefault(); + } + }); + }; + }); + cwhApp.factory('photonicUtils', ['$http', '$rootScope', function($http, $rootScope) { return { previewExternalStateId:firstCacheId, @@ -59,6 +73,9 @@ if (processorContainer.printFileProcessor.friendlyName === 'Scalable Vector Graphics') { return "fa-puzzle-piece"; } + if (processorContainer.printFileProcessor.friendlyName === 'Coin') { + return "fa-user-circle"; + } return "fa-question-circle"; }, diff --git a/host/resourcesnew/cwh/js/printables.js b/host/resourcesnew/cwh/js/printables.js index 3b167da82..c64db1640 100644 --- a/host/resourcesnew/cwh/js/printables.js +++ b/host/resourcesnew/cwh/js/printables.js @@ -2,7 +2,7 @@ var cwhApp = angular.module('cwhApp'); cwhApp.controller("PrintablesController", ['$scope', '$http', '$location', '$uibModal', '$anchorScroll', 'cwhWebSocket', 'photonicUtils', function ($scope, $http, $location, $uibModal, $anchorScroll, cwhWebSocket, photonicUtils) { controller = this; - + this.currentPrintable = null; this.currentCustomizer = null; this.currentPrinter = null; @@ -260,7 +260,12 @@ this.writeDrainHoldCode = function writeDrainHoldCode() { controller.currentCustomizer.imageManipulationCalculator = - "var drainHoleInMM = { centerX:centerX, centerY:centerY, widthX:5, widthY:5, depth:5};\n\n" + + "var drainHoleInMM = {\n" + + " centerX:centerX,\n" + + " centerY:centerY,\n" + + " widthX:5,\n" + + " widthY:5,\n" + + " depth:5};\n\n" + "if (($CURSLICE * $LayerThickness) <= drainHoleInMM.depth) {\n" + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + " buildPlatformGraphics.fillOval(\n" + @@ -273,24 +278,107 @@ controller.clearExternalCacheAndSaveCustomizer(); }; + this.writeCenteredTextCode = function writeCenteredTextCode() { + controller.currentCustomizer.imageManipulationCalculator = + "var serial = \"123456\";\n" + + "var twoDFont = job.buildFont();\n" + + "var metrics = buildPlatformGraphics.getFontMetrics(twoDFont);\n" + + "buildPlatformGraphics.setFont(twoDFont);\n" + + "buildPlatformGraphics.setColor(java.awt.Color.WHITE);\n" + + "buildPlatformGraphics.drawString(serial, centerX - metrics.stringWidth(serial) / 2, centerY - metrics.getHeight() / 2 + metrics.getAscent());\n" + + controller.clearExternalCacheAndSaveCustomizer(); + }; + this.writeDuplicationGridCode = function writeDuplicationGridCode() { controller.currentCustomizer.imageManipulationCalculator = - "var gridDataInMM = {distanceBetweenImagesX: 1, distanceBetweenImagesY: 1, numberOfRows:3, numberOfColumns:3 };\n\n" + + "var gridDataInMM = {\n" + + " distanceBetweenImagesX: 1,\n" + + " distanceBetweenImagesY: 1,\n" + + " numberOfRows:3,\n" + + " numberOfColumns:3};\n\n" + "for (var x = 0; x < gridDataInMM.numberOfColumns; x++) {\n" + " for (var y = 0; y < gridDataInMM.numberOfRows; y++) {\n" + " if (x > 0 || y > 0) {\n" + " var currentTransform = new java.awt.geom.AffineTransform(affineTransform);\n" + " currentTransform.translate(\n" + - " (x * gridDataInMM.distanceBetweenImagesX * pixelsPerMMX) + (x * printImage.getWidth()),\n" + - " (y * gridDataInMM.distanceBetweenImagesY * pixelsPerMMY) + (y * printImage.getHeight()));\n" + + " Math.round(x * gridDataInMM.distanceBetweenImagesX * pixelsPerMMX) + (x * printableShape.getWidth()),\n" + + " Math.round(y * gridDataInMM.distanceBetweenImagesY * pixelsPerMMY) + (y * printableShape.getHeight()));\n" + " buildPlatformGraphics.drawImage(printImage, currentTransform, null);\n" + " }\n" + " }\n" + "}\n"; - controller.clearExternalCacheAndSaveCustomizer(); }; + this.writeDuplicationGridWithVariableExposureTimeCode = function writeDuplicationGridWithVariableExposureTimeCode() { + controller.currentCustomizer.imageManipulationCalculator = + "var gridDataInMM = {\n" + + " distanceBetweenImagesX: 1,\n" + + " distanceBetweenImagesY: 1,\n" + + " numberOfRows:3,\n" + + " numberOfColumns:3,\n" + + " exposureTimeDecrementMillis:1000};\n\n" + + "for (var x = 0; x < gridDataInMM.numberOfColumns; x++) {\n" + + " for (var y = 0; y < gridDataInMM.numberOfRows; y++) {\n" + + " if (x > 0 || y > 0) {\n" + + " var currentTransform = new java.awt.geom.AffineTransform(affineTransform);\n" + + " currentTransform.translate(\n" + + " Math.round(x * gridDataInMM.distanceBetweenImagesX * pixelsPerMMX) + (x * printableShape.getWidth()),\n" + + " Math.round(y * gridDataInMM.distanceBetweenImagesY * pixelsPerMMY) + (y * printableShape.getHeight()));\n" + + " buildPlatformGraphics.drawImage(printImage, currentTransform, null);\n" + + " exposureTimers.add({\n" + + " delayMillis:$LayerTime - ((y * gridDataInMM.numberOfColumns) + x) * gridDataInMM.exposureTimeDecrementMillis,\n" + + " parameter:currentTransform.createTransformedShape(printableShape),\n" + + " function:function(blackRect) {\n" + + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + " buildPlatformGraphics.fill(blackRect);\n" + + " }\n" + + " });\n" + + " }\n" + + " }\n" + + "}\n"; + controller.clearExternalCacheAndSaveCustomizer(); + }; + + this.writeDuplicationGridWithSerialNumber = function() { + controller.currentCustomizer.imageManipulationCalculator = + "var gridDataInMM = {\n" + + " distanceBetweenImagesX: 1,\n" + + " distanceBetweenImagesY: 1,\n" + + " numberOfRows:3,\n" + + " numberOfColumns:3,\n" + + " startingSerialNumber:888,\n" + + " serialNumberCenterXPixels: printableShape.getWidth() / 2,\n" + + " serialNumberCenterYPixels: printableShape.getHeight() / 2,\n" + + " serialNumberDepth:0.5};\n\n" + + "var twoDFont = job.buildFont();\n" + + "var metrics = buildPlatformGraphics.getFontMetrics(twoDFont);\n" + + "var oldTransform = buildPlatformGraphics.getTransform();\n" + + "buildPlatformGraphics.setFont(twoDFont);\n" + + "buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + "for (var x = 0; x < gridDataInMM.numberOfColumns; x++) {\n" + + " for (var y = 0; y < gridDataInMM.numberOfRows; y++) {\n" + + " var currentTransform = new java.awt.geom.AffineTransform(affineTransform);\n" + + " if (x > 0 || y > 0) {\n" + + " currentTransform.translate(\n" + + " Math.round(x * gridDataInMM.distanceBetweenImagesX * pixelsPerMMX) + (x * printableShape.getWidth()),\n" + + " Math.round(y * gridDataInMM.distanceBetweenImagesY * pixelsPerMMY) + (y * printableShape.getHeight()));\n" + + " buildPlatformGraphics.drawImage(printImage, currentTransform, null);\n" + + " }\n" + + " if (($CURSLICE * $LayerThickness) <= gridDataInMM.serialNumberDepth) {\n" + + " buildPlatformGraphics.setTransform(currentTransform);\n" + + " var nextSerialNumber = new java.lang.Integer(gridDataInMM.startingSerialNumber + (x * gridDataInMM.numberOfRows) + y).toString(16).toUpperCase();\n" + + " buildPlatformGraphics.drawString(nextSerialNumber, \n" + + " gridDataInMM.serialNumberCenterXPixels - metrics.stringWidth(nextSerialNumber) / 2, \n" + + " gridDataInMM.serialNumberCenterYPixels - metrics.getHeight() / 2 + metrics.getAscent());\n" + + " buildPlatformGraphics.setTransform(oldTransform);\n" + + " }\n" + + " }\n" + + "}\n"; + controller.clearExternalCacheAndSaveCustomizer(); + } + this.write3dTwistCode = function write3dTwistCode() { controller.currentCustomizer.affineTransformSettings.affineTransformScriptCalculator = "var currentTransform = new java.awt.geom.AffineTransform();\n" + @@ -301,6 +389,30 @@ "currentTransform"; } + this.correctAspectRatio = function correctAspectRatio() { + controller.currentCustomizer.affineTransformSettings.affineTransformScriptCalculator = + "var currentTransform = new java.awt.geom.AffineTransform();\n" + + "var scaleXDimension = false;\n" + + "var ppmmx = pixelsPerMMX;\n" + + "var ppmmy = pixelsPerMMY;\n" + + "function reduce(numerator,denominator){\n" + + " var gcd = function gcd(a,b){\n" + + " return b ? gcd(b, a%b) : a;\n" + + " };\n" + + " gcd = gcd(numerator,denominator);\n" + + " return [numerator/gcd, denominator/gcd];\n" + + "}\n" + + "var reduced = reduce(ppmmx, ppmmy);" + + "ppmmx = reduced[0];\n" + + "ppmmy = reduced[1];\n" + + "if (scaleXDimension) {\n" + + " currentTransform.scale(ppmmx / ppmmy, 1);\n" + + "} else {\n" + + " currentTransform.scale(1, ppmmy / ppmmx);\n" + + "}\n" + + "currentTransform"; + } + this.getPrintableIconClass = function getPrintableIconClass(printable) { return photonicUtils.getPrintFileProcessorIconClass(printable); }; diff --git a/host/resourcesnew/cwh/js/printerControls.js b/host/resourcesnew/cwh/js/printerControls.js index 7ab789f65..452536b89 100644 --- a/host/resourcesnew/cwh/js/printerControls.js +++ b/host/resourcesnew/cwh/js/printerControls.js @@ -74,7 +74,7 @@ $http.get("services/printers/motors" + (isOn?"On":"Off") + "/" + printerName).then(gCodeSuccess, errorFunction) } this.executeGCode = function executeGCode() { - $http.get("services/printers/executeGCode/" + printerName + "/" + controller.gCodeToSend).then(gCodeSuccess, errorFunction) + $http.get("services/printers/executeGCode/" + encodeURIComponent(printerName) + "/" + encodeURIComponent(controller.gCodeToSend)).then(gCodeSuccess, errorFunction) } this.projector = function projector(startStop) { $http.get("services/printers/" + startStop + "Projector/" + printerName).then(gCodeSuccess, errorFunction) diff --git a/host/resourcesnew/cwh/js/printers.js b/host/resourcesnew/cwh/js/printers.js index 6d7afa1ab..bf68fd699 100644 --- a/host/resourcesnew/cwh/js/printers.js +++ b/host/resourcesnew/cwh/js/printers.js @@ -6,44 +6,43 @@ var BRANCH = "master"; var REPO = $scope.repo; + var tempSLicingProfile; + this.loadingFontsMessage = "--- Loading fonts from server ---" this.loadingProfilesMessage = "--- Loading slicing profiles from server ---" this.loadingMachineConfigMessage = "--- Loading machine configurations from server ---" this.autodirect = $location.search().autodirect; + function findAPrinterThatTheUserMostLikelyWantsToWorkWith(printerList) { + //There is only one printer. So it's likely they want to work with this printer + if (printerList.length == 1) { + return printerList[0]; + } + + var firstStartedPrinter = null; + for (var i = 0; i < printerList.length; i++) { + //If the user has already selected a printer. It's very likely that they want to work with it... + if (controller.currentPrinter != null && printerList[i].configuration.name === controller.currentPrinter.configuration.name) { + return printerList[i]; + } + + if (firstStartedPrinter == null && printerList[i].started) { + firstStartedPrinter = printerList[i]; + } + + //TODO: Isn't it more likely that they want to work with a printer that is printing than one that is simply just started? + } + + //As the name implies, this will return the first started printer. There is a decent chance they want to work with it. + return firstStartedPrinter; + } + //TODO: Instead of having this method we should understand how the selected printer gets out of sync and fix that function refreshSelectedPrinterAndAutodirectIfNecessary(printerList) { - var foundPrinter = false; - if (printerList.length == 1 && printerList[0].started && controller.autodirect != 'disabled') { - controller.currentPrinter = printerList[0]; - controller.gotoPrinterControls(); - foundPrinter = true; - } else { - var printersStarted = 0; - var currPrinter = null; - for (printer of printerList) { - if (printersStarted > 1) { - break; - } - if (printer.started) { - printersStarted += 1; - currPrinter = printer; - } - - if (controller.currentPrinter != null && printer.configuration.name === controller.currentPrinter.configuration.name) { - controller.currentPrinter = printer; - foundPrinter = true; - } - } - if (printersStarted == 1 && controller.autodirect != 'disabled') { - controller.currentPrinter = currPrinter; - controller.gotoPrinterControls(); - foundPrinter = true; - } - } - if (!foundPrinter) { - controller.currentPrinter = null; - } + controller.currentPrinter = findAPrinterThatTheUserMostLikelyWantsToWorkWith(printerList); + if (controller.autodirect != 'disabled') { + controller.gotoPrinterControls(); + } } function refreshPrinters() { @@ -62,11 +61,12 @@ if (postTargetPrinter) { $http.post(service, targetPrinter).then( function(response) { - if (shouldRefreshPrinterList) { - refreshPrinters(); - refreshSlicingProfiles(); - refreshMachineConfigurations(); + if (!shouldRefreshPrinterList) { + controller.currentPrinter = targetPrinter; } + refreshPrinters(); + refreshSlicingProfiles(); + refreshMachineConfigurations(); }, function(response) { $scope.$emit("HTTPError", {status:response.status, statusText:response.data}); @@ -141,7 +141,7 @@ $http.get(printer.url).success( function (data) { controller.editPrinter = JSON.parse(window.atob(data.content)); - $scope.savePrinter(controller.editPrinter, false); + $scope.savePrinter(controller.editPrinter, true); }).error( function (data, status, headers, config, statusText) { $scope.$emit("HTTPError", {status:status, statusText:data}); @@ -170,6 +170,249 @@ openSavePrinterDialog(editTitle, true); } + this.writePegExposureCode = function writePegExposureCode() { + controller.currentPrinter.configuration.slicingProfile.TwoDimensionalSettings.PlatformCalculator = + "var pegSettingsMM = {\n" + + " rows: 5,\n" + + " columns: 5,\n" + + " fontDepth: .5,\n" + + " fontPointSize: 42,\n" + + " startingOverhangDegrees: 45,\n" + + " degreeIncrement: 5,\n" + + " pegDiameter: 3,\n" + + " pegStandHeight: 1,\n" + + " pegStandWidth: 5,\n" + + " distanceBetweenStands: 1,\n" + + " exposureTimeDecrementMillis: 1000};\n\n" + + "var pegStandCount = pegSettingsMM.pegStandHeight / $LayerThickness;\n" + + "var fontCount = pegSettingsMM.fontDepth / $LayerThickness;\n" + + "var pegSettingsPixels = {\n" + + " pegDiameterX: pegSettingsMM.pegDiameter * pixelsPerMMX,\n" + + " pegDiameterY: pegSettingsMM.pegDiameter * pixelsPerMMY,\n" + + " pegStandWidthX: pegSettingsMM.pegStandWidth * pixelsPerMMX,\n" + + " pegStandWidthY: pegSettingsMM.pegStandWidth * pixelsPerMMY,\n" + + " distanceBetweenStandsX: pegSettingsMM.distanceBetweenStands * pixelsPerMMX,\n" + + " distanceBetweenStandsY: pegSettingsMM.distanceBetweenStands * pixelsPerMMY,\n" + + " pegStandDifferenceOffsetX: ((pegSettingsMM.pegStandWidth * pixelsPerMMX) - (pegSettingsMM.pegDiameter * pixelsPerMMX)) / 2,\n" + + " pegStandDifferenceOffsetY: ((pegSettingsMM.pegStandWidth * pixelsPerMMY) - (pegSettingsMM.pegDiameter * pixelsPerMMY)) / 2\n" + + "}\n" + + "if ($CURSLICE < pegStandCount) {\n" + + " for (var x = 0; x < pegSettingsMM.columns; x++) {\n" + + " for (var y = 0; y < pegSettingsMM.rows; y++) {\n" + + " var overhangAngle = pegSettingsMM.startingOverhangDegrees + y * pegSettingsMM.degreeIncrement;\n" + + " var startingX = x * pegSettingsPixels.pegStandWidthX + x * pegSettingsPixels.distanceBetweenStandsX;\n" + + " var startingY = y * pegSettingsPixels.pegStandWidthY + y * pegSettingsPixels.distanceBetweenStandsY;\n" + + " buildPlatformGraphics.setColor(java.awt.Color.WHITE);\n" + + " buildPlatformGraphics.fillRect(\n" + + " startingX,\n" + + " startingY,\n" + + " pegSettingsPixels.pegStandWidthX,\n" + + " pegSettingsPixels.pegStandWidthY);\n" + + " if ($CURSLICE < fontCount) {\n" + + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + " buildPlatformGraphics.setFont(new java.awt.Font(\"Dialog\", 0, pegSettingsMM.fontPointSize));\n" + + " buildPlatformGraphics.drawString(overhangAngle + \"\", startingX, startingY + pegSettingsPixels.pegStandWidthY);\n" + + " }\n" + + " exposureTimers.add({\n" + + " delayMillis:$LayerTime - (pegSettingsMM.exposureTimeDecrementMillis * x),\n" + + " parameter:{x:startingX, y:startingY, width:pegSettingsPixels.pegStandWidthX, height:pegSettingsPixels.pegStandWidthY},\n" + + " function:function(blackRect) {\n" + + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + " buildPlatformGraphics.fillRect(\n" + + " blackRect.x,\n" + + " blackRect.y,\n" + + " blackRect.width,\n" + + " blackRect.height);\n" + + " }\n" + + " });\n" + + " }\n" + + " }\n" + + "} else {\n" + + " for (var x = 0; x < pegSettingsMM.columns; x++) {\n" + + " for (var y = 0; y < pegSettingsMM.rows; y++) {\n" + + " var overhangAngle = pegSettingsMM.startingOverhangDegrees + y * pegSettingsMM.degreeIncrement;\n" + + " var singleOverhangIncrement = java.lang.Math.tan(java.lang.Math.toRadians(overhangAngle)) * $LayerThickness * pixelsPerMMX;\n" + + " var circleOffsetX = pegSettingsPixels.pegStandDifferenceOffsetX * ((x + 1) * 2 - 1) + (singleOverhangIncrement * ($CURSLICE - pegStandCount)) + (x * pegSettingsPixels.pegDiameterX) + (x * pegSettingsPixels.distanceBetweenStandsX);\n" + + " var circleOffsetY = pegSettingsPixels.pegStandDifferenceOffsetY * ((y + 1) * 2 - 1) + (y * pegSettingsPixels.pegDiameterY) + (y * pegSettingsPixels.distanceBetweenStandsY);\n" + + " buildPlatformGraphics.fillOval(\n" + + " circleOffsetX,\n" + + " circleOffsetY,\n" + + " pegSettingsPixels.pegDiameterX,\n" + + " pegSettingsPixels.pegDiameterY);\n" + + " exposureTimers.add({\n" + + " delayMillis:$LayerTime - (pegSettingsMM.exposureTimeDecrementMillis * x),\n" + + " parameter:{x:circleOffsetX, y:circleOffsetY, width:pegSettingsPixels.pegDiameterX, height:pegSettingsPixels.pegDiameterY},\n" + + " function:function(blackRect) {\n" + + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + " buildPlatformGraphics.fillOval(\n" + + " blackRect.x,\n" + + " blackRect.y,\n" + + " blackRect.width,\n" + + " blackRect.height);\n" + + " }\n" + + " });\n" + + " }\n" + + " }\n" + + "}\n"; + } + + this.writeHBridgeCode = function writeHBridgeCode() { + controller.currentPrinter.configuration.slicingProfile.TwoDimensionalSettings.PlatformCalculator = + "var hBridgeInMM = {\n" + + " wallWidth:1,\n" + + " gapLength:4,\n" + + " firstGapWidth:3,\n" + + " numberOfGapsInRow:6,\n" + + " gapWidthIncrement:3,\n" + + " distanceBetweenRows:1,\n" + + " numberOfRows:5,\n" + + " exposureTimeDecrementMillis:1000\n" + + " };\n\n" + + "var wallWidthX = hBridgeInMM.wallWidth * pixelsPerMMX;\n" + + "var wallWidthY = hBridgeInMM.wallWidth * pixelsPerMMY;\n" + + "var gapLengthY = hBridgeInMM.gapLength * pixelsPerMMY;\n" + + "var lastTermOfSeries = hBridgeInMM.firstGapWidth + hBridgeInMM.gapWidthIncrement * (hBridgeInMM.numberOfGapsInRow - 1);\n" + + "var totalWidthX = ((hBridgeInMM.wallWidth + hBridgeInMM.wallWidth * hBridgeInMM.numberOfGapsInRow) + (hBridgeInMM.numberOfGapsInRow * (hBridgeInMM.firstGapWidth + lastTermOfSeries) / 2)) * pixelsPerMMX;\n" + + "var startX = centerX - totalWidthX / 2;\n" + + "var startY = hBridgeInMM.numberOfRows * (hBridgeInMM.gapLength * 2 + hBridgeInMM.wallWidth) + (hBridgeInMM.numberOfRows - 1) * hBridgeInMM.distanceBetweenRows;\n" + + "startY = centerY - startY * pixelsPerMMY / 2;\n" + + "var currentY = startY;\n" + + "buildPlatformGraphics.setColor(java.awt.Color.WHITE);\n" + + "for (var currentRow = 0; currentRow < hBridgeInMM.numberOfRows; currentRow++) {\n" + + " var currentX = startX;\n" + + " if ($CURSLICE < job.totalSlices) {\n" + + " for (var currentGap = 0; currentGap < hBridgeInMM.numberOfGapsInRow; currentGap ++) {\n" + + " buildPlatformGraphics.fillRect(currentX, currentY, wallWidthX, gapLengthY * 2 + wallWidthY);\n" + + " currentX += wallWidthX + (hBridgeInMM.firstGapWidth + (hBridgeInMM.gapWidthIncrement * currentGap)) * pixelsPerMMX;\n" + + " }\n" + + " buildPlatformGraphics.fillRect(currentX, currentY, wallWidthX, gapLengthY * 2 + wallWidthY);\n" + + " buildPlatformGraphics.fillRect(startX, currentY + gapLengthY, totalWidthX, wallWidthY);\n" + + " } else {\n" + + " buildPlatformGraphics.fillRect(startX, currentY, totalWidthX, gapLengthY * 2 + wallWidthY);\n" + + " exposureTimers.add({\n" + + " delayMillis:$LayerTime - (hBridgeInMM.exposureTimeDecrementMillis * currentRow),\n" + + " parameter:{x:startX, y:currentY, width:totalWidthX, height:gapLengthY * 2 + wallWidthY},\n" + + " function:function(blackRect) {\n" + + " buildPlatformGraphics.setColor(java.awt.Color.BLACK);\n" + + " buildPlatformGraphics.fillRect(blackRect.x, blackRect.y, blackRect.width, blackRect.height);\n" + + " }\n" + + " });\n" + + " }\n" + + " currentY += gapLengthY * 2 + wallWidthY + hBridgeInMM.distanceBetweenRows * pixelsPerMMY;\n" + + "}\n"; + } + + function createNewResinProfile(newResinProfile) { + // this adds the new resinprofile in the current selected slicingprofile + var newSlicingProfile = controller.currentPrinter.configuration.slicingProfile; + newSlicingProfile.InkConfig.push(newResinProfile); + + // this re-uploads the changed profile + $http.put("services/machine/slicingProfiles", newSlicingProfile).then( + function (data) { + // for some reason this is needed when it is the currently loaded profile, otherwise it won't show after refresh + $http.post('/services/printers/save', controller.currentPrinter).success( + function () { + refreshSlicingProfiles(); + $scope.$emit("MachineResponse", {machineResponse: {command:"Settings Saved!", message:"Your new resin profile has been added!.", response:true}, successFunction:null, afterErrorFunction:null}); + }).error( + function (data, status, headers, config, statusText) { + $scope.$emit("HTTPError", {status:status, statusText:data}); + }) + }, + function (error) { + $scope.$emit("HTTPError", {status:error.status, statusText:error.data}); + } + ) + } + + this.copySlicingProfile = function copySlicingProfile(editTitle) { + controller.currentSlicingProfile = JSON.parse(JSON.stringify(controller.currentPrinter.configuration.slicingProfile)); + controller.currentSlicingProfile.name = controller.currentSlicingProfile.name + " (Copy) "; + openCopySlicingProfileDialog(controller.currentSlicingProfile, editTitle, controller.currentSlicingProfile.name); + } + + function SaveEditSlicingProfile(savedProfile){ + $http.put("services/machine/slicingProfiles", savedProfile).then( + function (data) { + refreshSlicingProfiles(); + $scope.$emit("MachineResponse", {machineResponse: {command:"Settings Saved!", message:"Your slicing profile has been copied!.", response:true}, successFunction:null, afterErrorFunction:null}); + }, + function (error) { + $scope.$emit("HTTPError", {status:error.status, statusText:error.data}); + } + ) + } + + this.openSaveResinDialog = function openSaveResinDialog(editTitle) { + var editPrinterModal = $uibModal.open({ + animation: true, + templateUrl: 'editResin.html', + controller: 'EditResinController', + size: "lg", + resolve: { + title: function () {return editTitle;}, + editPrinter: function () {return controller.editPrinter;} + } + }); + editPrinterModal.result.then(function (newResinProfile) { + createNewResinProfile(newResinProfile) + }); + } + + function openCopySlicingProfileDialog(data, editTitle, currentSlicingProfileName) { + var copySlicingProfileModal = $uibModal.open({ + animation: true, + templateUrl: 'copySlicingProfile.html', + controller: 'copySLicingProfileController', + size: "lg", + resolve: { + title: function () {return editTitle;}, + sliceData: function () {return data;}, + nameProfile: function() {return currentSlicingProfileName;} + } + }); + copySlicingProfileModal.result.then(function (savedProfile) { + SaveEditSlicingProfile(savedProfile); + }); + } + + this.deleteSlicingProfile = function deleteSlicingProfile(profileName, newProfile) { + + var profileNameEn = encodeURIComponent(profileName); + $http.delete("/services/machine/slicingProfiles/" + profileNameEn).success(function (data) { + refreshSlicingProfiles(); + $scope.$emit("MachineResponse", {machineResponse: {command:"Settings removed!", message:"Your slicing profile has been removed succesfully!.", response:true}, successFunction:null, afterErrorFunction:null}); + + }).error( + function (data, status, headers, config, statusText) { + $scope.$emit("HTTPError", {status:status, statusText:data}); + }) + } + + this.deleteCurrentResinProfile = function deleteCurrentResinProfile(slicingProfile) { + // removes the selected resinprofile from the old profile + slicingProfile.InkConfig.splice(slicingProfile.selectedInkConfigIndex,1); + + // this re-uploads the changed profile + $http.put("services/machine/slicingProfiles", slicingProfile).then( + function (data) { + // for some reason this is needed when it is the currently loaded profile, otherwise it won't show after refresh + $http.post('/services/printers/save', controller.currentPrinter).success( + function () { + refreshSlicingProfiles(); + $scope.$emit("MachineResponse", {machineResponse: {command:"Settings Saved!", message:"Your resin profile has been removed!.", response:true}, successFunction:null, afterErrorFunction:null}); + }).error( + function (data, status, headers, config, statusText) { + $scope.$emit("HTTPError", {status:status, statusText:data}); + }) + }, + function (error) { + $scope.$emit("HTTPError", {status:error.status, statusText:error.data}); + } + ) + } + this.startCurrentPrinter = function startCurrentPrinter() { $('#start-btn').attr('class', 'fa fa-refresh fa-spin'); executeActionAndRefreshPrinters("Start Printer", "No printer selected to start.", '/services/printers/start/', controller.currentPrinter, false, true); @@ -190,6 +433,9 @@ } this.gotoPrinterControls = function gotoPrinterControls() { + if (controller.currentPrinter == null) { + return; + } $location.path('/printerControlsPage').search({printerName: controller.currentPrinter.configuration.name}) }; diff --git a/host/resourcesnew/cwh/js/users.js b/host/resourcesnew/cwh/js/users.js index 3ea80b26e..7d84b1163 100644 --- a/host/resourcesnew/cwh/js/users.js +++ b/host/resourcesnew/cwh/js/users.js @@ -1,11 +1,28 @@ (function() { var cwhApp = angular.module('cwhApp'); cwhApp.controller("UsersController", ['$scope', '$http', '$location', '$anchorScroll', '$uibModal', 'photonicUtils', function ($scope, $http, $location, $anchorScroll, $uibModal, photonicUtils) { + $scope.guid = function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } + $scope.refreshUsers = function refreshUsers() { $http.get('/services/users/list').success(function(data) { $scope.users = data; }); } + + $scope.refreshMessages = function refreshMessages() { + $http.get('/services/messages/list').success( + function (data) { + $scope.messages = data; + }); + } $scope.openSaveUserDialog = function openSaveUserDialog(editTitle, isNewUser) { var editUserModal = $uibModal.open({ @@ -19,12 +36,31 @@ } }); editUserModal.result.then(function (savedUser) {$scope.saveUser(savedUser, isNewUser)}); + } + + $scope.openSaveMessageDialog = function openSaveMessageDialog(editTitle, isNewUser) { + var editUserModal = $uibModal.open({ + animation: true, + templateUrl: 'editMessage.html', + controller: 'EditMessageController', + size: "lg", + resolve: { + title: function () {return editTitle;}, + editMessage: function () {return $scope.editMessage;}, + users: function () {return $scope.users;} + } + }); + editUserModal.result.then(function (savedUser) {$scope.saveMessage(savedUser, isNewUser)}); } $scope.changeCurrentUser = function changeCurrentUser(newUser) { $scope.currentUser = newUser; } + $scope.changeCurrentMessage = function changeCurrentMessage(newMessage) { + $scope.currentMessage = newMessage; + } + $scope.deleteCurrentUser = function deleteCurrentUser() { $http.delete("/services/users/" + $scope.currentUser.userId).success(function (data) { $scope.currentUser = null; @@ -32,6 +68,15 @@ }).error(function (data, status, headers, config, statusText) { $scope.$emit("HTTPError", {status:status, statusText:data}); }); + } + + $scope.deleteCurrentMessage = function deleteCurrentMessage() { + $http.delete("/services/messages/" + $scope.currentMessage.id).success(function (data) { + $scope.currentMessage = null; + $scope.refreshMessages(); + }).error(function (data, status, headers, config, statusText) { + $scope.$emit("HTTPError", {status:status, statusText:data}); + }); } $scope.editCurrentUser = function editCurrentUser(editTitle) { @@ -50,6 +95,16 @@ }); } + $scope.saveMessage = function saveMessage(message, isNewUser) { + $http.put("/services/messages", message).success(function (data) { + $scope.refreshMessages(); + $scope.currentMessage = data; + $scope.editMessage = null; + }).error(function (data, status, headers, config, statusText) { + $scope.$emit("HTTPError", {status:status, statusText:data}); + }); + } + $scope.createNewUser = function createNewUser(editTitle) { if ($scope.currentUser == null) { $scope.editUser = { @@ -62,13 +117,41 @@ } else { $scope.editUser = JSON.parse(JSON.stringify($scope.currentUser)); $scope.editUser.name = $scope.editUser.name + " (Copy)"; + $scope.editUser.userId = null; $scope.editUser.credential = null; $scope.editUser.remote = false; } $scope.openSaveUserDialog(editTitle, true); } - + + $scope.createNewMessage = function createNewMessage(editTitle) { + $scope.editMessage = { + fromUser: $scope.myUser, + message: "test message" + }; + + if ($scope.currentUser != null) { + $scope.editMessage.toUser = $scope.currentUser; + } + + if ($scope.currentMessage != null) { + $scope.editMessage.toUser = $scope.currentMessage.fromUser; + } + + if (editTitle == null) { + editTitle = "Message from " + $scope.myUser.name; + } + + $scope.openSaveMessageDialog(editTitle, true); + } + + //TODO: We need to add in a watch for messages with a web socket $scope.refreshUsers(); + $scope.refreshMessages(); + + $http.get('/services/users/whoAmI').success(function(data) { + $scope.myUser = data; + }); }]) })(); diff --git a/host/resourcesnew/editMessage.html b/host/resourcesnew/editMessage.html new file mode 100644 index 000000000..aa2e93569 --- /dev/null +++ b/host/resourcesnew/editMessage.html @@ -0,0 +1,37 @@ +
+ + -
- +
+
-
- - - \ No newline at end of file +
\ No newline at end of file diff --git a/host/src/main/java/org/area515/resinprinter/actions/osscript/ExecuteNativeOSCommandRunnable.java b/host/src/main/java/org/area515/resinprinter/actions/osscript/ExecuteNativeOSCommandRunnable.java new file mode 100644 index 000000000..093cf9a95 --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/actions/osscript/ExecuteNativeOSCommandRunnable.java @@ -0,0 +1,35 @@ +package org.area515.resinprinter.actions.osscript; + +import java.util.Arrays; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.area515.util.IOUtilities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ExecuteNativeOSCommandRunnable implements Runnable { + private static final Logger logger = LogManager.getLogger(); + private String[] shellCommands; + + @JsonProperty + public String[] getShellCommands() { + return shellCommands; + } + public void setShellCommands(String[] shellCommands) { + this.shellCommands = shellCommands; + } + + @Override + public void run() { + logger.info("Executing: " + Arrays.toString(shellCommands)); + String[] output = IOUtilities.executeNativeCommand(shellCommands, null); + for (String line : output) { + logger.info(line); + } + } + + public String toString() { + return Arrays.toString(shellCommands); + } +} diff --git a/host/src/main/java/org/area515/resinprinter/client/Main.java b/host/src/main/java/org/area515/resinprinter/client/Main.java index 02d468189..7152f9b2c 100644 --- a/host/src/main/java/org/area515/resinprinter/client/Main.java +++ b/host/src/main/java/org/area515/resinprinter/client/Main.java @@ -4,7 +4,6 @@ import java.awt.Color; import java.awt.Cursor; import java.awt.Desktop; -import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; @@ -19,12 +18,10 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; import javax.swing.Icon; import javax.swing.ImageIcon; @@ -34,7 +31,6 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; -import javax.swing.JWindow; import javax.swing.SwingConstants; import org.apache.commons.cli.CommandLine; diff --git a/host/src/main/java/org/area515/resinprinter/discover/UPNPAdvertiser.java b/host/src/main/java/org/area515/resinprinter/discover/UPNPAdvertiser.java index fabd7876a..18298c6fb 100644 --- a/host/src/main/java/org/area515/resinprinter/discover/UPNPAdvertiser.java +++ b/host/src/main/java/org/area515/resinprinter/discover/UPNPAdvertiser.java @@ -16,7 +16,6 @@ import org.area515.resinprinter.plugin.Feature; import org.area515.resinprinter.server.HostInformation; import org.area515.resinprinter.server.HostProperties; -import org.fourthline.cling.DefaultUpnpServiceConfiguration; import org.fourthline.cling.UpnpServiceImpl; import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder; import org.fourthline.cling.controlpoint.ControlPoint; @@ -72,7 +71,7 @@ public UPNPSetup getSetup() { } @Override - public void start(URI webPresentationURI) { + public void start(URI webPresentationURI, String settings) { try { UDN udn = UDN.uniqueSystemIdentifier(getSetup().deviceName + getSetup().deviceReleaseString + getSetup().manufacturer); DeviceType type = new UDADeviceType(getSetup().deviceType, getSetup().deviceVersion); @@ -90,7 +89,7 @@ public void start(URI webPresentationURI) { null); List icons = new ArrayList(); - File iconFiles[] = new File(HostProperties.Instance().getHostGUIDir(), "favicon").listFiles(new FilenameFilter() { + File iconFiles[] = new File(HostProperties.Instance().getFirstActiveSkin().getResourceBase(), "favicon").listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { name = name.toLowerCase(); diff --git a/host/src/main/java/org/area515/resinprinter/display/FullScreenMode.java b/host/src/main/java/org/area515/resinprinter/display/FullScreenMode.java new file mode 100644 index 000000000..1c15e5303 --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/display/FullScreenMode.java @@ -0,0 +1,7 @@ +package org.area515.resinprinter.display; + +public enum FullScreenMode { + AlwaysUseFullScreen, + NeverUseFullScreen, + UseFullScreenWhenExclusiveIsAvailable +} diff --git a/host/src/main/java/org/area515/resinprinter/display/GraphicsDeviceOutputInterface.java b/host/src/main/java/org/area515/resinprinter/display/GraphicsDeviceOutputInterface.java index 2595b0e9b..ffbb3acf7 100644 --- a/host/src/main/java/org/area515/resinprinter/display/GraphicsDeviceOutputInterface.java +++ b/host/src/main/java/org/area515/resinprinter/display/GraphicsDeviceOutputInterface.java @@ -10,6 +10,8 @@ import javax.swing.JFrame; +import org.area515.resinprinter.printer.PrinterConfiguration; + public class GraphicsDeviceOutputInterface implements GraphicsOutputInterface { private String displayName; private GraphicsDevice device; @@ -68,7 +70,7 @@ public void showGridImage(int pixels) { } @Override - public void showImage(BufferedImage image) { + public void showImage(BufferedImage image, boolean performFullUpdate) { throw new IllegalStateException("You should never call showImage from this class"); } @@ -78,7 +80,7 @@ public String buildIDString() { } @Override - public GraphicsOutputInterface initializeDisplay(String displayId) { + public GraphicsOutputInterface initializeDisplay(String displayId, PrinterConfiguration configuration) { GraphicsDevice device; try { device = ((GraphicsDeviceOutputInterface)DisplayManager.Instance().getDisplayDevice(displayId)).device; @@ -93,8 +95,10 @@ public GraphicsOutputInterface initializeDisplay(String displayId) { refreshFrame.setMinimumSize(dim); refreshFrame.setSize(dim); refreshFrame.setVisible(true); - if (device.isFullScreenSupported()) { - device.setFullScreenWindow(refreshFrame);//TODO: Does projector not support full screen + FullScreenMode fullScreenMode = configuration.getMachineConfig().getMonitorDriverConfig().getFullScreenMode(); + if (fullScreenMode == FullScreenMode.AlwaysUseFullScreen || + (fullScreenMode == FullScreenMode.UseFullScreenWhenExclusiveIsAvailable && device.isFullScreenSupported())) { + device.setFullScreenWindow(refreshFrame); } //This can only be done with a real graphics device since it would reassign the printer Simulation //OLD getConfiguration().getMachineConfig().setOSMonitorID(device.getDefaultConfiguration().getDevice().getIDstring()); diff --git a/host/src/main/java/org/area515/resinprinter/display/GraphicsOutputInterface.java b/host/src/main/java/org/area515/resinprinter/display/GraphicsOutputInterface.java index d6fe13c45..eb3e76a30 100644 --- a/host/src/main/java/org/area515/resinprinter/display/GraphicsOutputInterface.java +++ b/host/src/main/java/org/area515/resinprinter/display/GraphicsOutputInterface.java @@ -6,18 +6,20 @@ import java.awt.Rectangle; import java.awt.image.BufferedImage; +import org.area515.resinprinter.printer.PrinterConfiguration; + public interface GraphicsOutputInterface { - public boolean isDisplayBusy(); //It's not necessary, but it's helpful to let the user know that the display might be busy in a gui... + public boolean isDisplayBusy(); //It's not necessary, but it's helpful to let the user know(via a gui) that the display might be busy public void resetSliceCount(); public void dispose(); public void showBlankImage(); public void showCalibrationImage(int xPixels, int yPixels); public void showGridImage(int pixels); - public void showImage(BufferedImage image); + public void showImage(BufferedImage image, boolean incrementSlice); public Rectangle getBoundary(); public String getIDstring(); public String buildIDString(); - public GraphicsOutputInterface initializeDisplay(String displayId); + public GraphicsOutputInterface initializeDisplay(String displayId, PrinterConfiguration configuration); public static void showGrid(Graphics2D g2, Rectangle screenSize, int gridSquareSize) { g2.setBackground(Color.black); diff --git a/host/src/main/java/org/area515/resinprinter/display/PrinterDisplayFrame.java b/host/src/main/java/org/area515/resinprinter/display/PrinterDisplayFrame.java index 2488fc287..e6cca3433 100644 --- a/host/src/main/java/org/area515/resinprinter/display/PrinterDisplayFrame.java +++ b/host/src/main/java/org/area515/resinprinter/display/PrinterDisplayFrame.java @@ -9,11 +9,13 @@ import java.awt.image.BufferedImage; import javax.swing.JFrame; +import javax.swing.JPanel; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.printer.Printer.DisplayState; -import org.area515.util.Log4jTimer; +import org.area515.resinprinter.printer.PrinterConfiguration; +import org.area515.util.Log4jUtil; public class PrinterDisplayFrame extends JFrame implements GraphicsOutputInterface { private static final long serialVersionUID = 5024551291098098753L; @@ -28,7 +30,46 @@ public class PrinterDisplayFrame extends JFrame implements GraphicsOutputInterfa private int sliceNumber; private boolean isSimulatedDisplay; private String displayId; - private GraphicsDevice device; + + private class DoubleBufferedJPanel extends JPanel { + private static final long serialVersionUID = 5629943117146058839L; + + @Override + protected void paintComponent(Graphics g) { + //we need to add this method back in because some UV light engines require it. + super.paintComponent(g); + + Graphics2D g2 = (Graphics2D)g; + Rectangle screenSize = getGraphicsConfiguration().getBounds(); + switch (displayState) { + case Blank : + g2.setBackground(Color.black); + g2.clearRect(0, 0, screenSize.width, screenSize.height); + logger.debug("Blank realized:{}", () -> Log4jUtil.completeGlobalTimer(IMAGE_REALIZE_TIMER)); + return; + case Grid : + GraphicsOutputInterface.showGrid(g2, screenSize, gridSquareSize); + logger.debug("Grid realized:{}", () -> Log4jUtil.completeGlobalTimer(IMAGE_REALIZE_TIMER)); + return; + case Calibration : + GraphicsOutputInterface.showCalibration(g2, screenSize, calibrationX, calibrationY); + logger.debug("Calibration realized:{}", () -> Log4jUtil.completeGlobalTimer(IMAGE_REALIZE_TIMER)); + return; + case CurrentSlice : + logger.trace("Writing paintComponent1aboutToDisplay:{}", () -> Log4jUtil.logImage(displayImage, "paintComponent1aboutToDisplay.png")); + + g2.drawImage(displayImage, null, screenSize.width / 2 - displayImage.getWidth() / 2, screenSize.height / 2 - displayImage.getHeight() / 2); + if (isSimulatedDisplay) { + g2.setColor(Color.RED); + g2.setFont(getFont()); + g2.drawString("Slice:" + sliceNumber, getInsets().left, getInsets().top + g2.getFontMetrics().getHeight()); + } + logger.debug("Image realized:{}", () -> Log4jUtil.completeGlobalTimer(IMAGE_REALIZE_TIMER)); + return; + } + + } + } public PrinterDisplayFrame(String displayId) throws HeadlessException { super(); @@ -36,15 +77,18 @@ public PrinterDisplayFrame(String displayId) throws HeadlessException { this.isSimulatedDisplay = true; getRootPane().setBackground(Color.black); getContentPane().setBackground(Color.black); + setBackground(Color.black); + add(new DoubleBufferedJPanel()); IMAGE_REALIZE_TIMER += hashCode(); } public PrinterDisplayFrame(GraphicsDevice device) { super(device.getDefaultConfiguration()); - this.device = device; this.isSimulatedDisplay = false; getRootPane().setBackground(Color.black); getContentPane().setBackground(Color.black); + setBackground(Color.black); + add(new DoubleBufferedJPanel()); IMAGE_REALIZE_TIMER += hashCode(); } @@ -54,53 +98,19 @@ public DisplayState getDisplayState() { public void setDisplayState(DisplayState displayState) { this.displayState = displayState; } - - @Override - public void paint(Graphics g) { - Graphics2D g2 = (Graphics2D)g; - //we need to add this method back in because some UV light engines require it. - super.paint(g); - - Rectangle screenSize = getGraphicsConfiguration().getBounds(); - switch (displayState) { - case Blank : - g2.setBackground(Color.black); - g2.clearRect(0, 0, screenSize.width, screenSize.height); - logger.debug("Blank realized:{}", () -> Log4jTimer.completeGlobalTimer(IMAGE_REALIZE_TIMER)); - return; - case Grid : - GraphicsOutputInterface.showGrid(g2, screenSize, gridSquareSize); - logger.debug("Grid realized:{}", () -> Log4jTimer.completeGlobalTimer(IMAGE_REALIZE_TIMER)); - return; - case Calibration : - GraphicsOutputInterface.showCalibration(g2, screenSize, calibrationX, calibrationY); - logger.debug("Calibration realized:{}", () -> Log4jTimer.completeGlobalTimer(IMAGE_REALIZE_TIMER)); - return; - case CurrentSlice : - g2.drawImage(displayImage, null, screenSize.width / 2 - displayImage.getWidth() / 2, screenSize.height / 2 - displayImage.getHeight() / 2); - if (isSimulatedDisplay) { - g2.setColor(Color.RED); - g2.setFont(getFont()); - g2.drawString("Slice:" + sliceNumber, getInsets().left, getInsets().top + g2.getFontMetrics().getHeight()); - } - logger.debug("Image realized:{}", () -> Log4jTimer.completeGlobalTimer(IMAGE_REALIZE_TIMER)); - return; - } - - } public void resetSliceCount() { sliceNumber = 0; } public void showBlankImage() { - logger.debug("Blank assigned:{}", () -> Log4jTimer.startGlobalTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Blank assigned:{}", () -> Log4jUtil.startGlobalTimer(IMAGE_REALIZE_TIMER)); setDisplayState(DisplayState.Blank); repaint(); } public void showCalibrationImage(int xPixels, int yPixels) { - logger.debug("Calibration assigned:{}", () -> Log4jTimer.startGlobalTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Calibration assigned:{}", () -> Log4jUtil.startGlobalTimer(IMAGE_REALIZE_TIMER)); setDisplayState(DisplayState.Calibration); calibrationX = xPixels; calibrationY = yPixels; @@ -108,15 +118,17 @@ public void showCalibrationImage(int xPixels, int yPixels) { } public void showGridImage(int pixels) { - logger.debug("Grid assigned:{}", () -> Log4jTimer.startGlobalTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Grid assigned:{}", () -> Log4jUtil.startGlobalTimer(IMAGE_REALIZE_TIMER)); setDisplayState(DisplayState.Grid); gridSquareSize = pixels; repaint(); } - public void showImage(BufferedImage image) { - logger.debug("Image assigned:{}", () -> Log4jTimer.startGlobalTimer(IMAGE_REALIZE_TIMER)); - sliceNumber++; + public void showImage(BufferedImage image, boolean performFullUpdate) { + logger.debug("Image assigned:{}", () -> Log4jUtil.startGlobalTimer(IMAGE_REALIZE_TIMER)); + if (performFullUpdate) { + sliceNumber++; + } setDisplayState(DisplayState.CurrentSlice); displayImage = image; repaint(); @@ -143,7 +155,7 @@ public String buildIDString() { } @Override - public GraphicsOutputInterface initializeDisplay(String displayId) { + public GraphicsOutputInterface initializeDisplay(String displayId, PrinterConfiguration configuration) { throw new IllegalStateException("You should never call initializeDisplay from this class"); } } diff --git a/host/src/main/java/org/area515/resinprinter/display/SimulatedDisplay.java b/host/src/main/java/org/area515/resinprinter/display/SimulatedDisplay.java index 0034d3032..879ee7899 100644 --- a/host/src/main/java/org/area515/resinprinter/display/SimulatedDisplay.java +++ b/host/src/main/java/org/area515/resinprinter/display/SimulatedDisplay.java @@ -6,6 +6,8 @@ import javax.swing.JFrame; +import org.area515.resinprinter.printer.PrinterConfiguration; + public class SimulatedDisplay extends GraphicsDeviceOutputInterface { public static final String NAME = "Simulated display"; public int displayIndex; @@ -29,7 +31,7 @@ public String buildIDString() { } @Override - public GraphicsOutputInterface initializeDisplay(String displayId) { + public GraphicsOutputInterface initializeDisplay(String displayId, PrinterConfiguration configuration) { PrinterDisplayFrame refreshFrame = new PrinterDisplayFrame(displayId); refreshFrame.setTitle(displayId); refreshFrame.setVisible(true); diff --git a/host/src/main/java/org/area515/resinprinter/display/dispmanx/DispManXDevice.java b/host/src/main/java/org/area515/resinprinter/display/dispmanx/DispManXDevice.java index c8dc26463..0c2e79216 100644 --- a/host/src/main/java/org/area515/resinprinter/display/dispmanx/DispManXDevice.java +++ b/host/src/main/java/org/area515/resinprinter/display/dispmanx/DispManXDevice.java @@ -1,21 +1,16 @@ package org.area515.resinprinter.display.dispmanx; import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; import java.awt.Rectangle; -import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.area515.resinprinter.display.GraphicsDeviceOutputInterface; import org.area515.resinprinter.display.GraphicsOutputInterface; import org.area515.resinprinter.display.InappropriateDeviceException; -import org.area515.util.Log4jTimer; +import org.area515.resinprinter.printer.PrinterConfiguration; +import org.area515.util.Log4jUtil; import com.sun.jna.Memory; import com.sun.jna.ptr.IntByReference; @@ -162,13 +157,13 @@ private Memory loadBitmapARGB8888(BufferedImage image, Memory destPixels, IntByR destPixels = new Memory(pitch * image.getHeight()); } - logger.debug("loadBitmapARGB8888 alg started:{}", () -> Log4jTimer.startTimer(IMAGE_REALIZE_TIMER)); + logger.debug("loadBitmapARGB8888 alg started:{}", () -> Log4jUtil.splitTimer(IMAGE_REALIZE_TIMER)); for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { destPixels.setInt((y*(pitch / bytesPerPixel) + x) * bytesPerPixel, image.getRGB(x, y)); } } - logger.debug("loadBitmapARGB8888 alg complete:{}", () -> Log4jTimer.completeTimer(IMAGE_REALIZE_TIMER)); + logger.debug("loadBitmapARGB8888 alg complete:{}", () -> Log4jUtil.splitTimer(IMAGE_REALIZE_TIMER)); width.setValue(image.getWidth()); height.setValue(image.getHeight()); @@ -203,19 +198,19 @@ private void initializeCalibrationAndGridImage() { @Override public void showCalibrationImage(int xPixels, int yPixels) { - logger.debug("Calibration assigned:{}", () -> Log4jTimer.startTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Calibration assigned:{}", () -> Log4jUtil.startTimer(IMAGE_REALIZE_TIMER)); showBlankImage(); initializeCalibrationAndGridImage(); Graphics2D graphics = (Graphics2D)calibrationAndGridImage.createGraphics(); GraphicsOutputInterface.showCalibration(graphics, bounds, xPixels, yPixels); graphics.dispose(); calibrationAndGridPixels = showImage(calibrationAndGridPixels, calibrationAndGridImage); - logger.debug("Calibration realized:{}", () -> Log4jTimer.completeTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Calibration realized:{}", () -> Log4jUtil.completeTimer(IMAGE_REALIZE_TIMER)); } @Override public void showGridImage(int pixels) { - logger.debug("Grid assigned:{}", () -> Log4jTimer.startTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Grid assigned:{}", () -> Log4jUtil.startTimer(IMAGE_REALIZE_TIMER)); showBlankImage(); initializeCalibrationAndGridImage(); Graphics2D graphics = (Graphics2D)calibrationAndGridImage.createGraphics(); @@ -223,7 +218,7 @@ public void showGridImage(int pixels) { graphics.dispose(); calibrationAndGridPixels = showImage(calibrationAndGridPixels, calibrationAndGridImage); - logger.debug("Grid realized:{}", () -> Log4jTimer.completeTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Grid realized:{}", () -> Log4jUtil.completeTimer(IMAGE_REALIZE_TIMER)); } private Memory showImage(Memory memory, BufferedImage image) { @@ -300,8 +295,8 @@ private Memory showImage(Memory memory, BufferedImage image) { } @Override - public void showImage(BufferedImage image) { - logger.debug("Image assigned:{}", () -> Log4jTimer.startTimer(IMAGE_REALIZE_TIMER)); + public void showImage(BufferedImage image, boolean performFullUpdate) { + logger.debug("Image assigned:{}", () -> Log4jUtil.startTimer(IMAGE_REALIZE_TIMER)); if (image.getWidth() == imageWidth && image.getHeight() == imageHeight) { imagePixels = showImage(imagePixels, image); } else { @@ -309,7 +304,7 @@ public void showImage(BufferedImage image) { } imageWidth = image.getWidth(); imageHeight = image.getHeight(); - logger.debug("Image realized:{}", () -> Log4jTimer.completeTimer(IMAGE_REALIZE_TIMER)); + logger.debug("Image realized:{}", () -> Log4jUtil.completeTimer(IMAGE_REALIZE_TIMER)); } @Override @@ -339,7 +334,7 @@ public String buildIDString() { } @Override - public GraphicsOutputInterface initializeDisplay(String displayId) { + public GraphicsOutputInterface initializeDisplay(String displayId, PrinterConfiguration configuration) { return this; } } diff --git a/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetector.java b/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetector.java index 7002a1e1e..ca9845956 100644 --- a/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetector.java +++ b/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetector.java @@ -3,9 +3,10 @@ import java.io.IOException; import org.area515.resinprinter.printer.Printer; +import org.area515.util.DynamicJSonSettings; public interface PrintMaterialDetector { - public void initializeDetector(PrintMaterialDetectorSettings settings); + public void initializeDetector(DynamicJSonSettings settings); /** * This method is executed synchronously with the printing process to ensure the print material is settled to a * point where it can be measured properly. For example, a VisualPrintMaterialDetector would want to take a diff --git a/host/src/main/java/org/area515/resinprinter/inkdetection/gpio/GpioDigitalPinInkDetector.java b/host/src/main/java/org/area515/resinprinter/inkdetection/gpio/GpioDigitalPinInkDetector.java index 0d4a5bb44..3c1be5a3a 100644 --- a/host/src/main/java/org/area515/resinprinter/inkdetection/gpio/GpioDigitalPinInkDetector.java +++ b/host/src/main/java/org/area515/resinprinter/inkdetection/gpio/GpioDigitalPinInkDetector.java @@ -3,8 +3,8 @@ import java.io.IOException; import org.area515.resinprinter.inkdetection.PrintMaterialDetector; -import org.area515.resinprinter.inkdetection.PrintMaterialDetectorSettings; import org.area515.resinprinter.printer.Printer; +import org.area515.util.DynamicJSonSettings; import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; @@ -33,12 +33,12 @@ public float getPercentageOfPrintMaterialRemaining(Printer printer) throws IOExc @Override public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) { - remainingResin = (pinLowIsLow && event.getState() == PinState.LOW) || + remainingResin = (pinLowIsLow && event.getState() == PinState.LOW) || (!pinLowIsLow && event.getState() == PinState.HIGH)?0:Float.MAX_VALUE; } @Override - public void initializeDetector(PrintMaterialDetectorSettings settings) { + public void initializeDetector(DynamicJSonSettings settings) { Pin rPin = null; if (settings != null) { Object pin = settings.getSettings().get("Pin"); @@ -49,6 +49,12 @@ public void initializeDetector(PrintMaterialDetectorSettings settings) { } else if (pin instanceof Number) { rPin = RaspiPin.getPinByAddress(((Number)pin).intValue()); } + Object pinFormat = settings.getSettings().get("PinLowIsLowInk"); + if (pinFormat instanceof String) { + pinLowIsLow = Boolean.getBoolean((String)pinFormat); + } else if (pinFormat instanceof Boolean) { + pinLowIsLow = (Boolean)pinFormat; + } } else { rPin = RaspiPin.getPinByAddress(0); } diff --git a/host/src/main/java/org/area515/resinprinter/inkdetection/visual/VisualPrintMaterialDetector.java b/host/src/main/java/org/area515/resinprinter/inkdetection/visual/VisualPrintMaterialDetector.java index 4d0c271b7..1cdf51964 100644 --- a/host/src/main/java/org/area515/resinprinter/inkdetection/visual/VisualPrintMaterialDetector.java +++ b/host/src/main/java/org/area515/resinprinter/inkdetection/visual/VisualPrintMaterialDetector.java @@ -18,10 +18,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.inkdetection.PrintMaterialDetector; -import org.area515.resinprinter.inkdetection.PrintMaterialDetectorSettings; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.server.Main; import org.area515.resinprinter.services.MediaService; +import org.area515.util.DynamicJSonSettings; public class VisualPrintMaterialDetector implements PrintMaterialDetector { private static final Logger logger = LogManager.getLogger(); @@ -176,7 +176,7 @@ float getPrintMaterialRemainingFromPhoto( } @Override - public void initializeDetector(PrintMaterialDetectorSettings settings) { + public void initializeDetector(DynamicJSonSettings settings) { //No custom settings right now. } } diff --git a/host/src/main/java/org/area515/resinprinter/job/AbstractPrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/job/AbstractPrintFileProcessor.java index 18eac24d0..8cebbaf53 100644 --- a/host/src/main/java/org/area515/resinprinter/job/AbstractPrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/job/AbstractPrintFileProcessor.java @@ -5,17 +5,22 @@ import java.awt.Paint; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; -import javax.imageio.ImageIO; +import javax.script.Bindings; +import javax.script.Invocable; +import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptException; +import javax.xml.bind.JAXBException; import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; @@ -24,26 +29,27 @@ import org.area515.resinprinter.exception.NoPrinterFoundException; import org.area515.resinprinter.exception.SliceHandlingException; import org.area515.resinprinter.job.Customizer.PrinterStep; +import org.area515.resinprinter.job.render.CurrentImageRenderer; import org.area515.resinprinter.job.render.RenderingCache; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.notification.NotificationManager; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.printer.PrinterConfiguration; import org.area515.resinprinter.printer.SlicingProfile; import org.area515.resinprinter.printer.SlicingProfile.InkConfig; import org.area515.resinprinter.server.HostProperties; +import org.area515.resinprinter.server.Main; import org.area515.resinprinter.services.CustomizerService; -import org.area515.resinprinter.services.PrinterService; import org.area515.resinprinter.slice.StlError; -import org.area515.util.Log4jTimer; +import org.area515.util.Log4jUtil; import org.area515.util.TemplateEngine; public abstract class AbstractPrintFileProcessor implements PrintFileProcessor{ private static final Logger logger = LogManager.getLogger(); public static final String EXPOSURE_TIMER = "exposureTime"; - private Map renderingCacheByPrintJob; public static class DataAid { - public ScriptEngine scriptEngine; + private Integer renderingSlice; public Printer printer; public PrintJob printJob; public PrinterConfiguration configuration; @@ -61,10 +67,11 @@ public static class DataAid { private AffineTransform affineTransform; public RenderingCache cache = new RenderingCache(); public Customizer customizer; - + public Customizer originalCustomizer; + public CurrentImageRenderer currentlyRenderingImage; + public DataAid(PrintJob printJob) throws JobManagerException { this.printJob = printJob; - this.scriptEngine = HostProperties.Instance().buildScriptEngine(); printer = printJob.getPrinter(); printJob.setStartTime(System.currentTimeMillis()); configuration = printer.getConfiguration(); @@ -97,7 +104,13 @@ public DataAid(PrintJob printJob) throws JobManagerException { if (customizer.getZScale() == null) { customizer.setZScale(1.0); } + //We must make sure our customizer is perfectly setup at this point, everyone should be able to depend on our customizer after this setup process + try { + originalCustomizer = HostProperties.deepCopyJAXB(customizer, Customizer.class); + } catch (JAXBException e) { + throw new JobManagerException("Couldn't copy customizer", e); + } //This file processor requires an ink configuration if (inkConfiguration == null) { @@ -108,10 +121,19 @@ public DataAid(PrintJob printJob) throws JobManagerException { sliceHeight = inkConfiguration.getSliceHeight(); } - public AffineTransform getAffineTransform(BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { + //This puts the customizer back into the original state and saves it so that completed prints will start from the original location that the user requested. + public void saveOriginalCustomizer() { + CustomizerService.INSTANCE.addOrUpdateCustomizer(originalCustomizer); + } + + public void clearAffineTransformCache() { + affineTransform = null; + } + + public AffineTransform getAffineTransform(ScriptEngine engine, BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { if (customizer != null && customizer.getAffineTransformSettings() != null) { if (this.affineTransform == null || customizer.getAffineTransformSettings().getAffineTransformScriptCalculator() != null) { - this.affineTransform = customizer.createAffineTransform(this, buildPlatformImage, printImage); + this.affineTransform = customizer.createAffineTransform(this, engine, buildPlatformImage, printImage); } } else { this.affineTransform = new AffineTransform(); @@ -121,8 +143,54 @@ public AffineTransform getAffineTransform(BufferedImage buildPlatformImage, Buff return this.affineTransform; } + + public int getRenderingSlice() { + if (customizer == null) { + return -1; + } + + if (renderingSlice == null) { + startSlice(); + } + + return renderingSlice; + } + + public int startSlice(){ + if (renderingSlice == null) { + if (customizer == null) { + return -1; + } + + renderingSlice = customizer.getNextSlice(); + return renderingSlice; + } + + return renderingSlice++; + } + + public int completeRenderingSlice() { + int sliceJustRendered = customizer.getNextSlice(); + customizer.setNextSlice(sliceJustRendered + 1); + customizer.setNextStep(PrinterStep.PerformPreSlice); + CustomizerService.INSTANCE.addOrUpdateCustomizer(customizer); + return sliceJustRendered; + } + } + + public Future startImageRendering(DataAid aid, Object imageIndexToBuild) { + aid.currentlyRenderingImage = createRenderer(aid, imageIndexToBuild); + aid.startSlice(); + if (aid.currentlyRenderingImage == null) { + return null; + } + + return Main.GLOBAL_EXECUTOR.submit(aid.currentlyRenderingImage); } + + public abstract CurrentImageRenderer createRenderer(DataAid aid, Object imageIndexToBuild); + @Override public Double getBuildAreaMM(PrintJob printJob) { DataAid aid = getDataAid(printJob); @@ -162,17 +230,12 @@ protected BufferedImage getCurrentImageFromCache(PrintJob printJob) { } } - private final Map getRenderingCacheByPrintJob() { - if (renderingCacheByPrintJob == null) { - renderingCacheByPrintJob = new HashMap<>(); - } - - return renderingCacheByPrintJob; - } - public final DataAid initializeJobCacheWithDataAid(PrintJob printJob) throws InappropriateDeviceException, JobManagerException { DataAid aid = createDataAid(printJob); - getRenderingCacheByPrintJob().put(printJob, aid); + printJob.setDataAid(aid); + + //Notify the client that the printJob has changed the current slice from -1 to 1 and totalSlices are properly set now as well. + NotificationManager.jobChanged(aid.printer, aid.printJob); return aid; } @@ -214,11 +277,19 @@ public void performHeader(DataAid aid) throws InappropriateDeviceException, IOEx aid.printer.getBulbHours(); } - public JobStatus performPreSlice(DataAid aid, List errors) throws InappropriateDeviceException { + public JobStatus performPreSlice(DataAid aid, ScriptEngine engine, List errors) throws InappropriateDeviceException { if (aid == null) { throw new IllegalStateException("initializeDataAid must be called before this method"); } + + //Create exposure timers for new slice if they don't already exist + Bindings binding = engine.getBindings(ScriptContext.ENGINE_SCOPE); + if (!binding.containsKey("exposureTimers")) { + ArrayList timers = new ArrayList<>(); + binding.put("exposureTimers", timers); + } + //Start timer aid.currentSliceTime = System.currentTimeMillis(); //Show the errors to our users if the stl file is broken, but we'll keep on processing like normal @@ -244,11 +315,70 @@ public JobStatus performPreSlice(DataAid aid, List errors) throws Inap return null; } - public JobStatus printImageAndPerformPostProcessing(DataAid aid, BufferedImage sliceImage) throws ExecutionException, InterruptedException, InappropriateDeviceException, ScriptException { + private Future[] startAllExposureTimers(final DataAid aid, ScriptEngine engine, final BufferedImage sliceImage) throws ScriptException { + if (!(engine instanceof Invocable)) { + throw new ScriptException("Script engine:" + engine + " not invocable, can't use exposureTimers."); + } + Invocable invocable = (Invocable)engine; + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + if (!bindings.containsKey("exposureTimers")) { + return new Future[0]; + } + + Number timerValue = (Number)engine.eval("exposureTimers.length"); + logger.info("TimerCountLength:" + timerValue); + if (timerValue == null) { + return new Future[0]; + } + + int timerCount = timerValue.intValue(); + if (timerCount == 0) { + return new Future[0]; + } + + int delay[] = new int[timerCount]; + final Object function[] = new Object[timerCount]; + final Object parameter[] = new Object[timerCount]; + for (int t = 0; t < timerCount; t++) { + parameter[t] = engine.eval("exposureTimers[" + t + "].parameter"); + delay[t] = ((Number)engine.eval("exposureTimers[" + t + "].delayMillis")).intValue(); + function[t] = engine.eval("exposureTimers[" + t + "]"); + } + + bindings.put("buildPlatformImage", sliceImage); + bindings.put("buildPlatformGraphics", sliceImage.getGraphics()); + bindings.put("buildPlatformRaster", sliceImage.getRaster()); + + Future[] futures = new Future[timerCount]; + for (int t = 0; t < timerCount; t++) { + final int i = t; + logger.info("Exposure timer[{}] will start in: {}ms", i, delay[t]); + futures[i] = Main.GLOBAL_EXECUTOR.schedule(new Runnable() { + @Override + public void run() { + try { + logger.info("Exposure timer[{}] started:{}", i, invocable.invokeMethod(function[i], "function", parameter[i])); + aid.printer.showImage(sliceImage, false); + logger.info("Exposure timer[{}] complete", i); + } catch (NoSuchMethodException e) { + logger.error("Exposure timer function[" + i + "] not found", e); + } catch (ScriptException e) { + logger.error("Exposure timer[" + i + "] threw exception", e); + } + }}, delay[t], TimeUnit.MILLISECONDS); + } + return futures; + } + + public JobStatus printImageAndPerformPostProcessing(DataAid aid, ScriptEngine engine, BufferedImage sliceImage) throws ExecutionException, InterruptedException, InappropriateDeviceException, ScriptException { if (aid == null) { throw new IllegalStateException("initializeDataAid must be called before this method"); } + if (engine == null) { + throw new IllegalStateException("You must specify a script engine"); + } + if (sliceImage == null) { throw new IllegalStateException("You must specify a sliceImage to display"); } @@ -264,14 +394,16 @@ public JobStatus printImageAndPerformPostProcessing(DataAid aid, BufferedImage s //Determine the dynamic amount of time we should expose our resin if (!aid.printJob.isExposureTimeOverriden() && aid.slicingProfile.getExposureTimeCalculator() != null && aid.slicingProfile.getExposureTimeCalculator().trim().length() > 0) { - Number value = calculate(aid, aid.slicingProfile.getExposureTimeCalculator(), "exposure time script"); + Number value = calculate(aid, engine, aid.slicingProfile.getExposureTimeCalculator(), "exposure time script"); if (value != null) { aid.printJob.setExposureTime(value.intValue()); } } - - aid.printer.showImage(sliceImage); - logger.info("ExposureStart:{}", ()->Log4jTimer.startTimer(EXPOSURE_TIMER)); + + Future[] timerFutures = startAllExposureTimers(aid, engine, sliceImage); + + aid.printer.showImage(sliceImage, true); + logger.info("ExposureStart:{}", ()->Log4jUtil.startTimer(EXPOSURE_TIMER)); if (aid.slicingProfile.getgCodeShutter() != null && aid.slicingProfile.getgCodeShutter().trim().length() > 0) { aid.printer.setShutterOpen(true); @@ -283,14 +415,24 @@ public JobStatus printImageAndPerformPostProcessing(DataAid aid, BufferedImage s if (aid.slicingProfile.getgCodeShutter() != null && aid.slicingProfile.getgCodeShutter().trim().length() > 0) { aid.printer.setShutterOpen(false); - aid.printer.getGCodeControl().executeGCodeWithTemplating(aid.printJob, aid.slicingProfile.getgCodeShutter(), true); + aid.printer.getGCodeControl().executeGCodeWithTemplating(aid.printJob, aid.slicingProfile.getgCodeShutter(), false); } //Blank the screen aid.printer.showBlankImage(); - logger.info("ExposureTime:{}", ()->Log4jTimer.completeTimer(EXPOSURE_TIMER)); + logger.info("ExposureTime:{}", ()->Log4jUtil.completeTimer(EXPOSURE_TIMER)); + + //End all timers + for (int t = 0; t < timerFutures.length; t++) { + logger.info("Exposure timer[{}] cancel:{}", t, timerFutures[t].cancel(true)); + } + //Reset exposureTimers to blank object + Bindings binding = engine.getBindings(ScriptContext.ENGINE_SCOPE); + ArrayList timers = new ArrayList<>(); + binding.put("exposureTimers", timers); + //Perform two actions at once here: // 1. Pause if the user asked us to pause // 2. Get out if the print is cancelled @@ -299,13 +441,13 @@ public JobStatus printImageAndPerformPostProcessing(DataAid aid, BufferedImage s } if (!aid.printJob.isZLiftDistanceOverriden() && aid.slicingProfile.getzLiftDistanceCalculator() != null && aid.slicingProfile.getzLiftDistanceCalculator().trim().length() > 0) { - Number value = calculate(aid, aid.slicingProfile.getzLiftDistanceCalculator(), "lift distance script"); + Number value = calculate(aid, engine, aid.slicingProfile.getzLiftDistanceCalculator(), "lift distance script"); if (value != null) { aid.printJob.setZLiftDistance(value.doubleValue()); } } if (!aid.printJob.isZLiftSpeedOverriden() && aid.slicingProfile.getzLiftSpeedCalculator() != null && aid.slicingProfile.getzLiftSpeedCalculator().trim().length() > 0) { - Number value = calculate(aid, aid.slicingProfile.getzLiftSpeedCalculator(), "lift speed script"); + Number value = calculate(aid, engine, aid.slicingProfile.getzLiftSpeedCalculator(), "lift speed script"); if (value != null) { aid.printJob.setZLiftSpeed(value.doubleValue()); } @@ -327,7 +469,7 @@ public JobStatus printImageAndPerformPostProcessing(DataAid aid, BufferedImage s aid.printJob.getZLiftSpeed(), buildArea); //Perform area and cost manipulations for current slice - aid.printJob.addNewSlice(System.currentTimeMillis() - aid.currentSliceTime, buildArea); + aid.printJob.completeRenderingSlice(System.currentTimeMillis() - aid.currentSliceTime, buildArea); //Notify the client that the printJob has increased the currentSlice NotificationManager.jobChanged(aid.printer, aid.printJob); @@ -354,12 +496,14 @@ public JobStatus performFooter(DataAid aid) throws IOException, InappropriateDev aid.printer.setProjectorPowerStatus(false); } + aid.saveOriginalCustomizer(); + return JobStatus.Completed; } - private Number calculate(DataAid aid, String calculator, String calculationName) throws ScriptException { + private Number calculate(DataAid aid, ScriptEngine engine, String calculator, String calculationName) throws ScriptException { try { - Number num = (Number)TemplateEngine.runScript(aid.printJob, aid.printer, aid.scriptEngine, calculator, calculationName, null); + Number num = (Number)TemplateEngine.runScript(aid.printJob, aid.printer, engine, calculator, calculationName, null); if (num == null || Double.isNaN(num.doubleValue())) { return null; } @@ -369,7 +513,7 @@ private Number calculate(DataAid aid, String calculator, String calculationName) } } - public void applyBulbMask(DataAid aid, Graphics2D g2, int width, int height) throws ScriptException { + public void applyBulbMask(DataAid aid, ScriptEngine engine, Graphics2D g2, int width, int height) throws ScriptException { if (aid == null) { throw new IllegalStateException("initializeDataAid must be called before this method"); } @@ -384,7 +528,7 @@ public void applyBulbMask(DataAid aid, Graphics2D g2, int width, int height) thr try { if (aid.maskPaint == null) { - aid.maskPaint = (Paint)TemplateEngine.runScript(aid.printJob, aid.printer, aid.scriptEngine, aid.slicingProfile.getProjectorGradientCalculator(), "projector gradient script", null); + aid.maskPaint = (Paint)TemplateEngine.runScript(aid.printJob, aid.printer, engine, aid.slicingProfile.getProjectorGradientCalculator(), "projector gradient script", null); } g2.setPaint(aid.maskPaint); g2.fillRect(0, 0, width, height); @@ -393,99 +537,60 @@ public void applyBulbMask(DataAid aid, Graphics2D g2, int width, int height) thr } } - public BufferedImage applyImageTransforms(DataAid aid, BufferedImage img) throws ScriptException, JobManagerException { + public BufferedImage applyImageTransforms(DataAid aid, ScriptEngine engineForManipulation, BufferedImage imageToRender) throws ScriptException, JobManagerException { if (aid == null) { throw new IllegalStateException("initializeDataAid must be called before this method"); } - if (img == null) { + if (imageToRender == null) { throw new IllegalStateException("BufferedImage is null"); } if (aid.optimizeWithPreviewMode) { - return img; + return imageToRender; } - /*try { - ImageIO.write(img, "png", new File("start.png")); - } catch (IOException e) { - }//*/ + logger.trace("Writing applyImageTransforms1Begin:{}", () -> Log4jUtil.logImage(imageToRender, "applyImageTransforms1Begin.png")); + + BufferedImage imageToRenderAfterTransformations = new BufferedImage(aid.xResolution, aid.yResolution, BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphicsAfterTransformations = (Graphics2D)imageToRenderAfterTransformations.getGraphics(); + graphicsAfterTransformations.setColor(Color.BLACK); + graphicsAfterTransformations.fillRect(0, 0, aid.xResolution, aid.yResolution); + + logger.trace("Writing applyImageTransforms2AfterFill:{}", () -> Log4jUtil.logImage(imageToRenderAfterTransformations, "applyImageTransforms2AfterFill.png")); + + AffineTransform transform = aid.getAffineTransform(engineForManipulation, imageToRenderAfterTransformations, imageToRender); + graphicsAfterTransformations.drawImage(imageToRender, transform, null); + + logger.trace("Writing applyImageTransforms3AfterDraw:{}", () -> Log4jUtil.logImage(imageToRenderAfterTransformations, "applyImageTransforms3AfterDraw.png")); - BufferedImage after = new BufferedImage(aid.xResolution, aid.yResolution, BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D g = (Graphics2D)after.getGraphics(); - g.setColor(Color.BLACK); - g.fillRect(0, 0, aid.xResolution, aid.yResolution); - - /*try { - ImageIO.write(after, "png", new File("afterFill.png")); - } catch (IOException e) { - }//*/ - - AffineTransform transform = aid.getAffineTransform(after, img); - g.drawImage(img, transform, null); - - /*try { - ImageIO.write(after, "png", new File("afterDraw.png")); - } catch (IOException e) { - }//*/ if (aid.customizer.getImageManipulationCalculator() != null && aid.customizer.getImageManipulationCalculator().trim().length() > 0) { Map overrides = new HashMap<>(); overrides.put("affineTransform", transform); - TemplateEngine.runScriptInImagingContext(after, img, aid.printJob, aid.printer, aid.scriptEngine, overrides, aid.customizer.getImageManipulationCalculator(), "Image manipulation script", false); - } - /*try { - ImageIO.write(after, "png", new File("afterImageManipulation.png")); - } catch (IOException e) { - }//*/ - applyBulbMask(aid, (Graphics2D)after.getGraphics(), aid.xResolution, aid.yResolution); - /*try { - ImageIO.write(after, "png", new File("afterBulbMask.png")); - } catch (IOException e) { - }//*/ + TemplateEngine.runScriptInImagingContext(imageToRenderAfterTransformations, imageToRender, aid.printJob, aid.printer, engineForManipulation, overrides, aid.customizer.getImageManipulationCalculator(), "Image manipulation script", false); + } + + logger.trace("Writing applyImageTransforms4AfterImageManipulation:{}", () -> Log4jUtil.logImage(imageToRenderAfterTransformations, "applyImageTransforms4AfterImageManipulation.png")); + + //TODO: I was using imageToRenderAfterTransformations.getGraphics() but recently changed to graphicsAfterTransformations + applyBulbMask(aid, engineForManipulation, graphicsAfterTransformations, aid.xResolution, aid.yResolution); - return after; + logger.trace("Writing applyImageTransforms5AfterBulbMask:{}", () -> Log4jUtil.logImage(imageToRenderAfterTransformations, "applyImageTransforms5AfterBulbMask.png")); + return imageToRenderAfterTransformations; } - public BufferedImage buildPreviewSlice(Customizer customizer, File jobFile, Previewable previewable) throws NoPrinterFoundException, SliceHandlingException { - //find the first activePrinter - String printerName = customizer.getPrinterName(); - Printer activePrinter = null; - if (printerName == null || printerName.isEmpty()) { - //if customizer doesn't have a printer stored, set first active printer as printer - try { - activePrinter = PrinterService.INSTANCE.getFirstAvailablePrinter(); - } catch (NoPrinterFoundException e) { - throw new NoPrinterFoundException("No printers found for slice preview. You must have a started printer or specify a valid printer in the Customizer."); - } - - } else { - try { - activePrinter = PrinterService.INSTANCE.getPrinter(printerName); - } catch (InappropriateDeviceException e) { - logger.warn("Could not locate printer {}", printerName, e); - } - } - + public BufferedImage buildPreviewSlice(Customizer customizer, DataAid dataAid) throws NoPrinterFoundException, SliceHandlingException { try { - //instantiate a new print job based on the jobFile and set its printer to activePrinter - PrintJob printJob = new PrintJob(jobFile); - printJob.setPrinter(activePrinter); - printJob.setCustomizer(customizer); - printJob.setPrintFileProcessor(this); - printJob.setCurrentSlice(customizer.getNextSlice()); - - //instantiate new dataaid - DataAid dataAid = createDataAid(printJob); //TODO: Eventually we should just use the internal cache inside the dataAid - BufferedImage image = customizer.getOrigSliceCache(); - - if (image == null) { + RenderingContext data = dataAid.cache.getOrCreateIfMissing(customizer); + BufferedImage preImage = data.getPreTransformedImage(); + if (preImage == null) { dataAid.optimizeWithPreviewMode = true; - image = previewable.renderPreviewImage(dataAid); + preImage = ((Previewable)this).renderPreviewImage(dataAid); dataAid.optimizeWithPreviewMode = false; - customizer.setOrigSliceCache(image); + data.setPreTransformedImage(preImage); } - image = applyImageTransforms(dataAid, image); - return image; + preImage = applyImageTransforms(dataAid, data.getScriptEngine(), preImage); + return preImage; } catch (ScriptException | JobManagerException e) { throw new SliceHandlingException(e); } @@ -496,10 +601,19 @@ public DataAid createDataAid(PrintJob printJob) throws JobManagerException { } public DataAid getDataAid(PrintJob job) { - return getRenderingCacheByPrintJob().get(job); + if (job == null) { + return null; + } + + return job.getDataAid(); } + //TODO: Not sure this is necessary. What if we just killed the rendering cache public void clearDataAid(PrintJob job) { - getRenderingCacheByPrintJob().remove(job); + if (job == null) { + return; + } + + job.setDataAid(null); } } diff --git a/host/src/main/java/org/area515/resinprinter/job/CreationWorkshopSceneFileProcessor.java b/host/src/main/java/org/area515/resinprinter/job/CreationWorkshopSceneFileProcessor.java index ee2c87e16..2855115b1 100644 --- a/host/src/main/java/org/area515/resinprinter/job/CreationWorkshopSceneFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/job/CreationWorkshopSceneFileProcessor.java @@ -12,9 +12,13 @@ import java.nio.charset.Charset; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.Enumeration; -import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -27,14 +31,19 @@ import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.area515.resinprinter.exception.SliceHandlingException; +import org.area515.resinprinter.job.render.CurrentImageRenderer; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.notification.NotificationManager; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.server.HostProperties; +import org.area515.resinprinter.twodim.SimpleImageRenderer; import org.area515.util.IOUtilities; -public class CreationWorkshopSceneFileProcessor extends AbstractPrintFileProcessor { +import se.sawano.java.text.AlphanumericComparator; + +public class CreationWorkshopSceneFileProcessor extends AbstractPrintFileProcessor implements Previewable { private static final Logger logger = LogManager.getLogger(); - private HashMap currentlyDisplayedImage = new HashMap(); @Override public String[] getFileExtensions() { @@ -55,8 +64,53 @@ public boolean acceptsFile(File processingFile) { } @Override - public BufferedImage getCurrentImage(PrintJob processingFile) { - return currentlyDisplayedImage.get(processingFile); + public CurrentImageRenderer createRenderer(DataAid aid, Object imageIndexToBuild) { + return new SimpleImageRenderer(aid, this, imageIndexToBuild); + } + + protected SortedMap findImages(File jobFile) throws JobManagerException { + String [] extensions = {"png", "PNG"}; + boolean recursive = true; + + Collection files = + FileUtils.listFiles(buildExtractionDirectory(jobFile.getName()), + extensions, recursive); + + TreeMap images = new TreeMap<>(new AlphanumericComparator()); + + for (File file : files) { + images.put(file.getName(), file); + } + + return images; + } + + @Override + public BufferedImage renderPreviewImage(DataAid dataAid) throws SliceHandlingException { + try { + prepareEnvironment(dataAid.printJob.getJobFile(), dataAid.printJob); + + SortedMap imageFiles = findImages(dataAid.printJob.getJobFile()); + + dataAid.printJob.setTotalSlices(imageFiles.size()); + Iterator imgIter = imageFiles.values().iterator(); + + // Preload first image then loop + int sliceIndex = dataAid.customizer.getNextSlice(); + while (imgIter.hasNext() && sliceIndex > 0) { + sliceIndex--; + imgIter.next(); + } + + if (!imgIter.hasNext()) { + throw new IOException("No Image Found for index:" + dataAid.customizer.getNextSlice()); + } + File imageFile = imgIter.next(); + RenderingContext stdImage = startImageRendering(dataAid, imageFile).get(); + return stdImage.getPrintableImage(); + } catch (IOException | JobManagerException | InterruptedException | ExecutionException e) { + throw new SliceHandlingException(e); + } } @Override @@ -104,29 +158,27 @@ public JobStatus processFile(final PrintJob printJob) throws Exception { } else { if (startOfLastImageDisplay > -1) { //printJob.setCurrentSliceTime(System.currentTimeMillis() - startOfLastImageDisplay); - printJob.addNewSlice(System.currentTimeMillis() - startOfLastImageDisplay, null); + printJob.completeRenderingSlice(System.currentTimeMillis() - startOfLastImageDisplay, null); } startOfLastImageDisplay = System.currentTimeMillis(); - BufferedImage oldImage = null; - if (currentlyDisplayedImage != null) { - oldImage = currentlyDisplayedImage.get(printJob); - } + RenderingContext data = aid.cache.getOrCreateIfMissing(Boolean.TRUE); + BufferedImage oldImage = data.getPrintableImage(); int incoming = Integer.parseInt(matcher.group(1)); //printJob.setCurrentSlice(incoming); String imageNumber = String.format("%0" + padLength + "d", incoming); String imageFilename = FilenameUtils.removeExtension(gCodeFile.getName()) + imageNumber + ".png"; File imageFile = new File(gCodeFile.getParentFile(), imageFilename); BufferedImage newImage = ImageIO.read(imageFile); - newImage = applyImageTransforms(aid, newImage); + newImage = applyImageTransforms(aid, data.getScriptEngine(), newImage); // applyBulbMask(aid, (Graphics2D)newImage.getGraphics(), newImage.getWidth(), newImage.getHeight()); - currentlyDisplayedImage.put(printJob, newImage); + data.setPrintableImage(newImage); logger.info("Show picture: {}", imageFilename); //Notify the client that the printJob has increased the currentSlice NotificationManager.jobChanged(printer, printJob); - printer.showImage(currentlyDisplayedImage.get(printJob)); + printer.showImage(data.getPrintableImage(), true); if (oldImage != null) { oldImage.flush(); @@ -217,14 +269,8 @@ public JobStatus processFile(final PrintJob printJob) throws Exception { } catch (IOException e) { } } - - if (currentlyDisplayedImage != null) { - BufferedImage image = currentlyDisplayedImage.get(printJob); - if (image != null) { - currentlyDisplayedImage.get(printJob).flush(); - currentlyDisplayedImage.remove(printJob); - } - } + aid.cache.clearCache(Boolean.TRUE); + clearDataAid(printJob); } } @@ -290,7 +336,7 @@ public void prepareEnvironment(File processingFile, PrintJob printJob) throws Jo try { unpackDir(processingFile); } catch (IOException e) { - throw new JobManagerException("Couldn't unpack new job:" + processingFile + " into working directory:" + extractDirectory); + throw new JobManagerException("Couldn't unpack new job:" + processingFile + " into working directory:" + extractDirectory, e); } } @@ -373,8 +419,6 @@ private void unpackDir(File jobFile) throws IOException, JobManagerException { String basename = FilenameUtils.removeExtension(jobFile.getName()); logger.info("BaseName: {}", FilenameUtils.removeExtension(basename)); //findGcodeFile(jobFile); - } catch (IOException ioe) { - throw ioe; } finally { zipFile.close(); } diff --git a/host/src/main/java/org/area515/resinprinter/job/Customizer.java b/host/src/main/java/org/area515/resinprinter/job/Customizer.java index 60f235b46..17a46d28d 100644 --- a/host/src/main/java/org/area515/resinprinter/job/Customizer.java +++ b/host/src/main/java/org/area515/resinprinter/job/Customizer.java @@ -4,22 +4,21 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.ref.SoftReference; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; +import javax.script.ScriptEngine; import javax.script.ScriptException; +import javax.xml.bind.annotation.XmlRootElement; import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.util.TemplateEngine; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; +@XmlRootElement public class Customizer { private String name; private String printerName; @@ -28,7 +27,6 @@ public class Customizer { private boolean supportsAffineTransformSettings; //True means supported/False means not supported private AffineTransformSettings affineTransformSettings;//null means it's not used event if this customizer does support the AffineTransform private String externalImageAffectingState; - private SoftReference origSliceCache = null; private Double zScale = 1.0; private String cacheId; private int nextSlice = 0; @@ -123,9 +121,9 @@ public void setAffineTransformScriptCalculator(String affineTransformScriptCalcu this.affineTransformScriptCalculator = affineTransformScriptCalculator; } - public AffineTransform createAffineTransform(DataAid aid, BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { + public AffineTransform createAffineTransform(DataAid aid, ScriptEngine engine, BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { if (affineTransformScriptCalculator != null && affineTransformScriptCalculator.trim().length() > 0) { - return (AffineTransform)TemplateEngine.runScriptInImagingContext(buildPlatformImage, printImage, aid.printJob, aid.printer, aid.scriptEngine, null, affineTransformScriptCalculator, "Affine transform rendering script", false); + return (AffineTransform)TemplateEngine.runScriptInImagingContext(buildPlatformImage, printImage, aid.printJob, aid.printer, engine, null, affineTransformScriptCalculator, "Affine transform rendering script", false); //AffineTransform affineTransform = (AffineTransform)TemplateEngine.runScript(aid.printJob, aid.printer, aid.scriptEngine, affineTransformScriptCalculator, "Affine transform rendering script", overrides); } @@ -169,8 +167,8 @@ public void setNextStep(PrinterStep nextStep) { this.nextStep = nextStep; } - public AffineTransform createAffineTransform(DataAid aid, BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { - return this.affineTransformSettings.createAffineTransform(aid, buildPlatformImage, printImage); + public AffineTransform createAffineTransform(DataAid aid, ScriptEngine scriptEngine, BufferedImage buildPlatformImage, BufferedImage printImage) throws ScriptException { + return this.affineTransformSettings.createAffineTransform(aid, scriptEngine, buildPlatformImage, printImage); } public String getPrintableExtension() { @@ -242,7 +240,7 @@ public String getCacheId() { return cacheId; } - cacheId = "";//This is a trick to stop the original cacheId from being included into the next cacheId computed + cacheId = "";//This is a trick to stop the original cacheId from being included into the next computed cacheId try { MessageDigest m = MessageDigest.getInstance("SHA1"); m.reset(); @@ -263,22 +261,35 @@ public void setCacheId(String cacheId) { this.cacheId = null; } - @JsonIgnore - public BufferedImage getOrigSliceCache() { - if (origSliceCache == null) { - return null; - } - - return origSliceCache.get(); + public String toString() { + return name + " " + getCacheId(); } - - @JsonIgnore - public void setOrigSliceCache(BufferedImage origSliceCache) { - if (origSliceCache == null) { - this.origSliceCache = null; - return; - } - - this.origSliceCache = new SoftReference(origSliceCache); + + @Override + public int hashCode() { + String cacheId = getCacheId(); + final int prime = 31; + int result = 1; + result = prime * result + ((cacheId == null) ? 0 : cacheId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Customizer other = (Customizer) obj; + String cacheId = getCacheId(); + String otherCacheId = other.getCacheId(); + if (cacheId == null) { + if (otherCacheId != null) + return false; + } else if (!cacheId.equals(otherCacheId)) + return false; + return true; } } diff --git a/host/src/main/java/org/area515/resinprinter/job/InkDetector.java b/host/src/main/java/org/area515/resinprinter/job/InkDetector.java index d6118da45..a6d030f4b 100644 --- a/host/src/main/java/org/area515/resinprinter/job/InkDetector.java +++ b/host/src/main/java/org/area515/resinprinter/job/InkDetector.java @@ -7,10 +7,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.inkdetection.PrintMaterialDetector; -import org.area515.resinprinter.inkdetection.PrintMaterialDetectorSettings; import org.area515.resinprinter.notification.NotificationManager; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.server.Main; +import org.area515.util.DynamicJSonSettings; public class InkDetector { public static final String DETECTION_ERROR = "Error occurred while performing ink detection"; @@ -48,7 +48,7 @@ public Boolean call() throws Exception { } } - public InkDetector(Printer printer, PrintJob job, PrintMaterialDetector detector, PrintMaterialDetectorSettings settings, float percentageConsideredEmpty) { + public InkDetector(Printer printer, PrintJob job, PrintMaterialDetector detector, DynamicJSonSettings settings, float percentageConsideredEmpty) { this.printer = printer; this.detector = detector; this.printMaterialRemainingForEmpty = percentageConsideredEmpty; diff --git a/host/src/main/java/org/area515/resinprinter/job/PrintJob.java b/host/src/main/java/org/area515/resinprinter/job/PrintJob.java index e320d7983..a1268feb4 100644 --- a/host/src/main/java/org/area515/resinprinter/job/PrintJob.java +++ b/host/src/main/java/org/area515/resinprinter/job/PrintJob.java @@ -1,12 +1,13 @@ package org.area515.resinprinter.job; +import java.awt.Font; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import javax.script.Compilable; import javax.script.CompiledScript; @@ -15,15 +16,16 @@ import javax.xml.bind.annotation.XmlTransient; import org.area515.resinprinter.display.InappropriateDeviceException; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.printer.SlicingProfile.InkConfig; +import org.area515.resinprinter.services.PrinterService; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; public class PrintJob { private volatile int totalSlices = 0; - private volatile int currentSlice = 0; private volatile long currentSliceTime = 0; private volatile long averageSliceTime = 0; private volatile long startTime = 0; @@ -41,10 +43,11 @@ public class PrintJob { private volatile boolean overrideZLiftDistance; private volatile double zLiftDistance; + private DataAid dataAid; private UUID id = UUID.randomUUID(); private File jobFile; private Printer printer; - private Future futureJobStatus; + private CompletableFuture futureJobStatus; private CountDownLatch futureJobStatusAssigned = new CountDownLatch(1); private Map scriptsByName = new HashMap<>(); @@ -58,6 +61,14 @@ public UUID getId() { return id; } + @JsonIgnore + DataAid getDataAid() { + return dataAid; + } + void setDataAid(DataAid dataAid) { + this.dataAid = dataAid; + } + @JsonIgnore public File getJobFile() { return jobFile; @@ -71,6 +82,25 @@ public String getJobName() { return jobFile.getName(); } + public Font buildFont() { + org.area515.resinprinter.printer.SlicingProfile.Font cwhFont = dataAid != null && dataAid.slicingProfile != null && dataAid.slicingProfile.getTwoDimensionalSettings() != null? + dataAid.slicingProfile.getTwoDimensionalSettings().getFont(): + new org.area515.resinprinter.printer.SlicingProfile.Font(); + if (cwhFont == null) { + cwhFont = PrinterService.DEFAULT_FONT; + } + + if (cwhFont.getName() == null) { + cwhFont.setName(PrinterService.DEFAULT_FONT.getName()); + } + + if (cwhFont.getSize() == 0) { + cwhFont.setSize(PrinterService.DEFAULT_FONT.getSize()); + } + + return new Font(cwhFont.getName(), Font.PLAIN, cwhFont.getSize()); + } + public long getElapsedTime() { return elapsedTime; } @@ -92,11 +122,28 @@ public void setTotalSlices(int totalSlices){ this.totalSlices = totalSlices; } + @JsonIgnore + public int getRenderingSlice(){ + if (dataAid == null) { + return -1; + } + + return dataAid.getRenderingSlice(); + } + public int getCurrentSlice(){ - return currentSlice; + if (dataAid == null || dataAid.customizer == null) { + return -1; + } + + return dataAid.customizer.getNextSlice(); } public void setCurrentSlice(int currentSlice){ - this.currentSlice = currentSlice; + if (dataAid == null || dataAid.customizer == null) { + return; + } + + this.dataAid.customizer.setNextSlice(currentSlice); } public long getCurrentSliceTime(){ @@ -151,9 +198,10 @@ public JobStatus getStatus() { return JobStatus.Failed; } - public void initializePrintJob(Future futureJobStatus) { + public void initializePrintJob(CompletableFuture futureJobStatus) { this.futureJobStatus = futureJobStatus; futureJobStatusAssigned.countDown(); + futureJobStatus.whenComplete((s, e) -> scriptsByName.clear()); } public String getErrorDescription() { @@ -299,15 +347,15 @@ public void setCurrentSliceCost(double currentSliceCost) { this.currentSliceCost = currentSliceCost; } - public void addNewSlice(long sliceTime, Double buildAreaInMM) { + public void completeRenderingSlice(long sliceTime, Double buildAreaInMM) { sliceTime -= getPrinter().getCurrentSlicePauseTime(); getPrinter().setCurrentSlicePauseTime(0); InkConfig inkConfig = getPrinter().getConfiguration().getSlicingProfile().getSelectedInkConfig(); + int currentSlice = dataAid.completeRenderingSlice(); averageSliceTime = ((averageSliceTime * currentSlice) + sliceTime) / (currentSlice + 1); elapsedTime = System.currentTimeMillis() - startTime; currentSliceTime = sliceTime; - currentSlice++; if (buildAreaInMM != null && buildAreaInMM > 0) { double buildVolume = buildAreaInMM * inkConfig.getSliceHeight(); diff --git a/host/src/main/java/org/area515/resinprinter/job/PrintJobManager.java b/host/src/main/java/org/area515/resinprinter/job/PrintJobManager.java index 4e3424b9d..399becb6f 100644 --- a/host/src/main/java/org/area515/resinprinter/job/PrintJobManager.java +++ b/host/src/main/java/org/area515/resinprinter/job/PrintJobManager.java @@ -5,8 +5,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,49 +24,6 @@ public class PrintJobManager { private ConcurrentHashMap printJobsByJobId = new ConcurrentHashMap(); - public class JobCloser implements Runnable { - private Printer printer; - private Future futureJobStatus; - private PrintJob newJob; - - public JobCloser(Printer printer, Future futureJobStatus, PrintJob job) { - this.printer = printer; - this.futureJobStatus = futureJobStatus; - this.newJob = job; - } - - @Override - public void run() { - try { - //You can't change the state of the job after this point. Too many processes are waiting for the future jobStatus for a final outcome of the job - printer.setStatus(futureJobStatus.get()); - logger.info("Job Success:{}", Thread.currentThread().getName()); - NotificationManager.jobChanged(printer, newJob); - } catch (Throwable e) { - logger.error("Job Failed:" + Thread.currentThread().getName(), e); - printer.setStatus(JobStatus.Failed); - NotificationManager.jobChanged(printer, newJob); - } finally { - newJob.setElapsedTime(System.currentTimeMillis() - newJob.getStartTime()); - - //Don't need to close the printer or dissassociate the serial and display devices - printer.showBlankImage(); - if (HostProperties.Instance().isRemoveJobOnCompletion()) { - PrintJobManager.Instance().removeJob(newJob); - } - - //If we don't do this, the next print will carry the last pause along with it and make falsify the slicing time. - printer.setCurrentSlicePauseTime(0); - PrinterManager.Instance().removeAssignment(newJob); - newJob.setPrintFileProcessor(new StubPrintFileProcessor<>(newJob.getPrintFileProcessor())); - - //Set the jobstatus back on the printer to Ready - printer.setStatus(JobStatus.Ready); - logger.info("Job Ended:{}", Thread.currentThread().getName()); - } - } - } - public static PrintJobManager Instance() { if (INSTANCE == null) { INSTANCE = new PrintJobManager(); @@ -104,23 +61,52 @@ public PrintJob createJob(File job, final Printer printer, Customizer customizer logger.info(newJob.getCustomizer()); } - //TODO: These should be set by customizer - newJob.setCurrentSlice(0); + //TODO: These should be set by customizer. Right now, each FileProcessor sets those values independently newJob.setCurrentSlice(0); newJob.setTotalSlices(0); - Future futureJobStatus = null; + CompletableFuture futureJobStatus = null; try { PrinterManager.Instance().assignPrinter(newJob, printer); - PrintJobProcessingThread worker = new PrintJobProcessingThread(newJob, printer); - futureJobStatus = Main.GLOBAL_EXECUTOR.submit(worker); + PrintJobSupplier worker = new PrintJobSupplier(newJob, printer); + futureJobStatus = CompletableFuture.supplyAsync(worker, Main.GLOBAL_EXECUTOR); newJob.setPrintFileProcessor(worker.getPrintFileProcessor()); newJob.initializePrintJob(futureJobStatus); } catch (AlreadyAssignedException e) { printJobsByJobId.remove(newJob.getId()); throw e; - } finally { + } finally { //Trigger all job completion tasks after job is complete - Main.GLOBAL_EXECUTOR.submit(new JobCloser(printer, futureJobStatus, newJob)); + futureJobStatus.whenComplete((status, exeption) -> { + try { + //You can't change the state of the job after this point. Too many processes are waiting for the future jobStatus for a final outcome of the job + if (status != null) { + printer.setStatus(status); + logger.info("Job Success:{}", Thread.currentThread().getName()); + NotificationManager.jobChanged(printer, newJob); + } else { + logger.error("Job Failed:" + Thread.currentThread().getName(), exeption); + printer.setStatus(JobStatus.Failed); + NotificationManager.jobChanged(printer, newJob); + } + } finally { + newJob.setElapsedTime(System.currentTimeMillis() - newJob.getStartTime()); + + //Don't need to close the printer or dissassociate the serial and display devices + printer.showBlankImage(); + if (HostProperties.Instance().isRemoveJobOnCompletion()) { + PrintJobManager.Instance().removeJob(newJob); + } + + //If we don't do this, the next print will carry the last pause along with it and make falsify the slicing time. + printer.setCurrentSlicePauseTime(0); + PrinterManager.Instance().removeAssignment(newJob); + newJob.setPrintFileProcessor(new StubPrintFileProcessor<>(newJob.getPrintFileProcessor())); + + //Set the jobstatus back on the printer to Ready + printer.setStatus(JobStatus.Ready); + logger.info("Job Ended:{}", Thread.currentThread().getName()); + } + }); } return newJob; } diff --git a/host/src/main/java/org/area515/resinprinter/job/PrintJobProcessingThread.java b/host/src/main/java/org/area515/resinprinter/job/PrintJobProcessingThread.java deleted file mode 100644 index 8470cba39..000000000 --- a/host/src/main/java/org/area515/resinprinter/job/PrintJobProcessingThread.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.area515.resinprinter.job; - -import java.util.concurrent.Callable; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.area515.resinprinter.notification.NotificationManager; -import org.area515.resinprinter.printer.Printer; -import org.area515.resinprinter.server.HostProperties; - -public class PrintJobProcessingThread implements Callable { - private static final Logger logger = LogManager.getLogger(); - private PrintJob printJob = null; - private Printer printer; - private PrintFileProcessor processor; - - public PrintJobProcessingThread(PrintJob printJob, Printer printer) { - this.printJob = printJob; - this.printer = printer; - for (PrintFileProcessor currentProcessor : HostProperties.Instance().getPrintFileProcessors()) { - if (currentProcessor.acceptsFile(printJob.getJobFile())) { - processor = currentProcessor; - } - } - } - - public PrintFileProcessor getPrintFileProcessor() { - return processor; - } - - @Override - public JobStatus call() throws Exception { - logger.info("Starting:{} on Printer:{} executing on Thread:{}", printJob, printer, Thread.currentThread().getName()); - printer.setStatus(JobStatus.Printing); - printJob.setStartTime(System.currentTimeMillis()); - NotificationManager.jobChanged(printer, printJob); - processor.prepareEnvironment(printJob.getJobFile(), printJob); - JobStatus status = processor.processFile(printJob); - if (status == JobStatus.Cancelling) { - status = JobStatus.Cancelled; - } - printer.setStatus(status); - return status; - } -} \ No newline at end of file diff --git a/host/src/main/java/org/area515/resinprinter/job/PrintJobSupplier.java b/host/src/main/java/org/area515/resinprinter/job/PrintJobSupplier.java new file mode 100644 index 000000000..5ed54b96e --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/job/PrintJobSupplier.java @@ -0,0 +1,50 @@ +package org.area515.resinprinter.job; + +import java.util.function.Supplier; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.area515.resinprinter.notification.NotificationManager; +import org.area515.resinprinter.printer.Printer; +import org.area515.resinprinter.server.HostProperties; + +public class PrintJobSupplier implements Supplier { + private static final Logger logger = LogManager.getLogger(); + private PrintJob printJob = null; + private Printer printer; + private PrintFileProcessor processor; + + public PrintJobSupplier(PrintJob printJob, Printer printer) { + this.printJob = printJob; + this.printer = printer; + for (PrintFileProcessor currentProcessor : HostProperties.Instance().getPrintFileProcessors()) { + if (currentProcessor.acceptsFile(printJob.getJobFile())) { + processor = currentProcessor; + } + } + } + + public PrintFileProcessor getPrintFileProcessor() { + return processor; + } + + @Override + public JobStatus get() { + try { + logger.info("Starting:{} on Printer:{} executing on Thread:{}", printJob, printer, Thread.currentThread().getName()); + printer.setStatus(JobStatus.Printing); + printJob.setStartTime(System.currentTimeMillis()); + NotificationManager.jobChanged(printer, printJob); + processor.prepareEnvironment(printJob.getJobFile(), printJob); + JobStatus status = processor.processFile(printJob); + if (status == JobStatus.Cancelling) { + status = JobStatus.Cancelled; + } + printer.setStatus(status); + return status; + } catch (Exception e) { + logger.error("Execution failure.", e); + return JobStatus.Failed; + } + } +} \ No newline at end of file diff --git a/host/src/main/java/org/area515/resinprinter/job/STLFileProcessor.java b/host/src/main/java/org/area515/resinprinter/job/STLFileProcessor.java index 0199bc71b..c1374d3b9 100644 --- a/host/src/main/java/org/area515/resinprinter/job/STLFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/job/STLFileProcessor.java @@ -12,14 +12,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.exception.SliceHandlingException; -import org.area515.resinprinter.job.render.RenderedData; +import org.area515.resinprinter.job.render.CurrentImageRenderer; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.printer.BuildDirection; -import org.area515.resinprinter.server.Main; import org.area515.resinprinter.slice.CloseOffMend; import org.area515.resinprinter.slice.StlError; import org.area515.resinprinter.slice.ZSlicer; import org.area515.resinprinter.stl.Triangle3d; -import org.area515.util.Log4jTimer; +import org.area515.util.Log4jUtil; public class STLFileProcessor extends AbstractPrintFileProcessor, Set> implements Previewable { public static String STL_OVERHEAD = "stlOverhead"; @@ -32,6 +32,11 @@ public String[] getFileExtensions() { return new String[]{"stl"}; } + @Override + public CurrentImageRenderer createRenderer(DataAid aid, Object imageIndexToBuild) { + return new STLImageRenderer(aid, this, imageIndexToBuild, false); + } + @Override public boolean acceptsFile(File processingFile) { return processingFile.getName().toLowerCase().endsWith("stl"); @@ -64,46 +69,40 @@ public JobStatus processFile(PrintJob printJob) throws Exception { int endPoint = dataAid.slicingProfile.getDirection() == BuildDirection.Bottom_Up?(slicer.getZMaxIndex() + 1 - dataAid.customizer.getNextSlice()): (slicer.getZMinIndex() + 1); dataAid.slicer.setZIndex(startPoint); Boolean nextRenderingPointer = (Boolean)dataAid.cache.getCurrentRenderingPointer(); - Future currentImage = Main.GLOBAL_EXECUTOR.submit(new STLImageRenderer(dataAid, this, nextRenderingPointer, false)); - + Future currentImage = startImageRendering(dataAid, nextRenderingPointer); + //renderingImage //Everything needs to be setup in the dataByPrintJob before we start the header performHeader(dataAid); for (int z = startPoint; dataAid.slicingProfile.getDirection().isSliceAvailable(z, endPoint) && dataAid.printer.isPrintActive(); z += dataAid.slicingProfile.getDirection().getVector()) { //Performs all of the duties that are common to most print files - JobStatus status = performPreSlice(dataAid, slicer.getStlErrors()); + JobStatus status = performPreSlice(dataAid, dataAid.currentlyRenderingImage.getScriptEngine(), slicer.getStlErrors()); if (status != null) { return status; } - logger.info("SliceOverheadStart:{}", ()->Log4jTimer.startTimer(STL_OVERHEAD)); + logger.info("SliceOverheadStart:{}", ()->Log4jUtil.startTimer(STL_OVERHEAD)); //Wait until the image has been properly rendered. Most likely, it's already done though... - BufferedImage image = currentImage.get().getPrintableImage(); + RenderingContext renderedData = currentImage.get(); - logger.info("SliceOverhead:{}", ()->Log4jTimer.completeTimer(STL_OVERHEAD)); + logger.info("SliceOverhead:{}", ()->Log4jUtil.completeTimer(STL_OVERHEAD)); //Now that the image has been rendered, we can make the switch to use the pointer that we were using while we were rendering dataAid.cache.setCurrentRenderingPointer(nextRenderingPointer); - //Start the exposure timer - // logger.info("ExposureStart:{}", ()->Log4jTimer.startTimer(EXPOSURE_TIMER)); - - //Cure the current image - //dataAid.printer.showImage(image); - //Get the next pointer in line to start rendering the image into nextRenderingPointer = !nextRenderingPointer; //Render the next image while we are waiting for the current image to cure if (z < slicer.getZMaxIndex() + 1) { slicer.setZIndex(z); - currentImage = Main.GLOBAL_EXECUTOR.submit(new STLImageRenderer(dataAid, this, nextRenderingPointer, false)); + currentImage = startImageRendering(dataAid, nextRenderingPointer); } //Performs all of the duties that are common to most print files - status = printImageAndPerformPostProcessing(dataAid, image); + status = printImageAndPerformPostProcessing(dataAid, renderedData.getScriptEngine(), renderedData.getPrintableImage()); if (status != null) { return status; } diff --git a/host/src/main/java/org/area515/resinprinter/job/StaticJobStatusFuture.java b/host/src/main/java/org/area515/resinprinter/job/StaticJobStatusFuture.java deleted file mode 100644 index 82a5de39e..000000000 --- a/host/src/main/java/org/area515/resinprinter/job/StaticJobStatusFuture.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.area515.resinprinter.job; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class StaticJobStatusFuture implements Future { - private JobStatus status; - - public StaticJobStatusFuture(JobStatus status) { - this.status = status; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean isDone() { - return true; - } - - @Override - public JobStatus get() throws InterruptedException, ExecutionException { - return status; - } - - @Override - public JobStatus get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return status; - } -} diff --git a/host/src/main/java/org/area515/resinprinter/job/ZipImagesFileProcessor.java b/host/src/main/java/org/area515/resinprinter/job/ZipImagesFileProcessor.java index 7788e40bf..f718399b1 100644 --- a/host/src/main/java/org/area515/resinprinter/job/ZipImagesFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/job/ZipImagesFileProcessor.java @@ -1,25 +1,15 @@ package org.area515.resinprinter.job; -import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; -import java.util.Collection; import java.util.Iterator; import java.util.SortedMap; -import java.util.TreeMap; import java.util.concurrent.Future; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.area515.resinprinter.exception.SliceHandlingException; -import org.area515.resinprinter.job.render.RenderedData; -import org.area515.resinprinter.server.Main; -import org.area515.resinprinter.twodim.SimpleImageRenderer; +import org.area515.resinprinter.job.render.RenderingContext; -import se.sawano.java.text.AlphanumericComparator; - -public class ZipImagesFileProcessor extends CreationWorkshopSceneFileProcessor implements Previewable { +public class ZipImagesFileProcessor extends CreationWorkshopSceneFileProcessor { private static final Logger logger = LogManager.getLogger(); @Override @@ -27,11 +17,6 @@ public String[] getFileExtensions() { return new String[]{"imgzip"}; } - @Override - public BufferedImage getCurrentImage(PrintJob printJob) { - return getCurrentImageFromCache(printJob); - } - @Override public boolean acceptsFile(File processingFile) { if (processingFile.getName().toLowerCase().endsWith(".imgzip") || processingFile.getName().toLowerCase().endsWith(".zip")) { @@ -69,28 +54,27 @@ public JobStatus processFile(PrintJob printJob) throws Exception { // Preload first image then loop if (imgIter.hasNext()) { File imageFile = imgIter.next(); - - Future prepareImage = Main.GLOBAL_EXECUTOR.submit(new SimpleImageRenderer(dataAid, this, imageFile)); + Future prepareImage = startImageRendering(dataAid, imageFile); boolean slicePending = true; do { - JobStatus status = performPreSlice(dataAid, null); + JobStatus status = performPreSlice(dataAid, dataAid.currentlyRenderingImage.getScriptEngine(), null); if (status != null) { return status; } - RenderedData imageData = prepareImage.get(); + RenderingContext imageData = prepareImage.get(); dataAid.cache.setCurrentRenderingPointer(imageFile); if (imgIter.hasNext()) { imageFile = imgIter.next(); - prepareImage = Main.GLOBAL_EXECUTOR.submit(new SimpleImageRenderer(dataAid, this, imageFile)); + prepareImage = startImageRendering(dataAid, imageFile); } else { slicePending = false; } - status = printImageAndPerformPostProcessing(dataAid, imageData.getPrintableImage()); + status = printImageAndPerformPostProcessing(dataAid, imageData.getScriptEngine(), imageData.getPrintableImage()); if (status != null) { return status; @@ -106,8 +90,8 @@ public JobStatus processFile(PrintJob printJob) throws Exception { @Override public Double getBuildAreaMM(PrintJob processingFile) { - DataAid aid = super.getDataAid(processingFile); - + DataAid aid = getDataAid(processingFile); + aid.cache.getOrCreateIfMissing(aid.cache.getCurrentRenderingPointer()); if (aid == null || aid.cache.getCurrentArea() == null) { return null; } @@ -115,55 +99,8 @@ public Double getBuildAreaMM(PrintJob processingFile) { return aid.cache.getCurrentArea() / (aid.xPixelsPerMM * aid.yPixelsPerMM); } - @Override - public BufferedImage renderPreviewImage(DataAid dataAid) throws SliceHandlingException { - try { - prepareEnvironment(dataAid.printJob.getJobFile(), dataAid.printJob); - - SortedMap imageFiles = findImages(dataAid.printJob.getJobFile()); - - dataAid.printJob.setTotalSlices(imageFiles.size()); - Iterator imgIter = imageFiles.values().iterator(); - - // Preload first image then loop - int sliceIndex = dataAid.customizer.getNextSlice(); - while (imgIter.hasNext() && sliceIndex > 0) { - sliceIndex--; - imgIter.next(); - } - - if (!imgIter.hasNext()) { - throw new IOException("No Image Found for index:" + dataAid.customizer.getNextSlice()); - } - File imageFile = imgIter.next(); - - SimpleImageRenderer renderer = new SimpleImageRenderer(dataAid, this, imageFile); - RenderedData stdImage = renderer.call(); - return stdImage.getPrintableImage(); - } catch (IOException | JobManagerException e) { - throw new SliceHandlingException(e); - } - } - @Override public String getFriendlyName() { return "Zip of Slice Images"; } - - private SortedMap findImages(File jobFile) throws JobManagerException { - String [] extensions = {"png", "PNG"}; - boolean recursive = true; - - Collection files = - FileUtils.listFiles(buildExtractionDirectory(jobFile.getName()), - extensions, recursive); - - TreeMap images = new TreeMap<>(new AlphanumericComparator()); - - for (File file : files) { - images.put(file.getName(), file); - } - - return images; - } } diff --git a/host/src/main/java/org/area515/resinprinter/job/render/CurrentImageRenderer.java b/host/src/main/java/org/area515/resinprinter/job/render/CurrentImageRenderer.java index b3f8f31ed..0512d219c 100644 --- a/host/src/main/java/org/area515/resinprinter/job/render/CurrentImageRenderer.java +++ b/host/src/main/java/org/area515/resinprinter/job/render/CurrentImageRenderer.java @@ -3,8 +3,9 @@ import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.util.concurrent.Callable; -import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.script.ScriptEngine; import javax.script.ScriptException; import org.apache.logging.log4j.LogManager; @@ -12,8 +13,9 @@ import org.area515.resinprinter.job.AbstractPrintFileProcessor; import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.JobManagerException; +import org.area515.util.Log4jUtil; -public abstract class CurrentImageRenderer implements Callable { +public abstract class CurrentImageRenderer implements Callable { private static final Logger logger = LogManager.getLogger(); protected Object imageIndexToBuild; protected AbstractPrintFileProcessor processor; @@ -29,30 +31,39 @@ public BufferedImage buildImage(int renderedWidth, int renderedHeight) { return new BufferedImage(renderedWidth, renderedHeight, BufferedImage.TYPE_4BYTE_ABGR); } - public RenderedData call() throws JobManagerException { + public RenderingContext call() throws JobManagerException { long startTime = System.currentTimeMillis(); - Lock lock = aid.cache.getSpecificLock(imageIndexToBuild); - lock.lock(); + RenderingContext preImageCache = aid.cache.getOrCreateIfMissing(imageIndexToBuild); + ReentrantLock preImageLock = preImageCache.getLock(); + preImageLock.lock(); try { - RenderedData imageData = aid.cache.getOrCreateIfMissing(imageIndexToBuild); - BufferedImage image = renderImage(imageData.getPreTransformedImage()); - imageData.setPreTransformedImage(image); - BufferedImage after = processor.applyImageTransforms(aid, image); - imageData.setPrintableImage(after); + //Do not try to optimize this call out, we need to depend on our ImageRenderer to determine if they want to load a file or not, see: org.area515.resinprinter.twodim.SimpleImageRenderer + BufferedImage image = renderImage(preImageCache.getPreTransformedImage()); + preImageCache.setPreTransformedImage(image); + logger.trace("Writing applyTransformsToRenderedData1pre" + imageIndexToBuild + ":{}", () -> Log4jUtil.logImage(image, "applyTransformsToRenderedData1pre" + imageIndexToBuild + ".png")); + + BufferedImage after = processor.applyImageTransforms(aid, preImageCache.getScriptEngine(), image); + preImageCache.setPrintableImage(after); + logger.trace("Writing applyTransformsToRenderedData2pre" + imageIndexToBuild + ":{}", () -> Log4jUtil.logImage(image, "applyTransformsToRenderedData2pre" + imageIndexToBuild + ".png")); + if (!aid.optimizeWithPreviewMode) { - long pixelArea = computePixelArea(image); - imageData.setArea((double)pixelArea); + long pixelArea = computePixelArea(image);//TODO: shouldn't this be after? + preImageCache.setArea((double)pixelArea); logger.info("Loaded {} with {} non-black pixels in {}ms", imageIndexToBuild, pixelArea, System.currentTimeMillis()-startTime); } - return imageData; + return preImageCache; } catch (ScriptException e) { logger.error(e); throw new JobManagerException("Unable to render image", e); } finally { - lock.unlock(); + preImageLock.unlock(); } } + public ScriptEngine getScriptEngine() { + return aid.cache.getOrCreateIfMissing(imageIndexToBuild).getScriptEngine(); + } + abstract public BufferedImage renderImage(BufferedImage image) throws JobManagerException; /** diff --git a/host/src/main/java/org/area515/resinprinter/job/render/RenderingCache.java b/host/src/main/java/org/area515/resinprinter/job/render/RenderingCache.java index d064315f6..3c9d51005 100644 --- a/host/src/main/java/org/area515/resinprinter/job/render/RenderingCache.java +++ b/host/src/main/java/org/area515/resinprinter/job/render/RenderingCache.java @@ -13,17 +13,17 @@ public class RenderingCache { private static final Logger logger = LogManager.getLogger(); - private LoadingCache imageSync = CacheBuilder.newBuilder().softValues().build( - new CacheLoader() { + private LoadingCache imageSync = CacheBuilder.newBuilder().softValues().build( + new CacheLoader() { @Override - public RenderedData load(Object key) throws Exception { - return new RenderedData(); + public RenderingContext load(Object key) throws Exception { + return new RenderingContext(); } }); private Object currentImagePointer = Boolean.TRUE; - public RenderedData getOrCreateIfMissing(Object imageToBuild) { + public RenderingContext getOrCreateIfMissing(Object imageToBuild) { try { return imageSync.get(imageToBuild); } catch (ExecutionException e) { @@ -36,10 +36,6 @@ public void clearCache(Object imageToBuild) { imageSync.invalidate(imageToBuild); } - public ReentrantLock getSpecificLock(Object imageToBuild) { - return getOrCreateIfMissing(imageToBuild).getLock(); - } - public ReentrantLock getCurrentLock() { return getOrCreateIfMissing(currentImagePointer).getLock(); } diff --git a/host/src/main/java/org/area515/resinprinter/job/render/RenderedData.java b/host/src/main/java/org/area515/resinprinter/job/render/RenderingContext.java similarity index 72% rename from host/src/main/java/org/area515/resinprinter/job/render/RenderedData.java rename to host/src/main/java/org/area515/resinprinter/job/render/RenderingContext.java index 80988caa8..12ebc2757 100644 --- a/host/src/main/java/org/area515/resinprinter/job/render/RenderedData.java +++ b/host/src/main/java/org/area515/resinprinter/job/render/RenderingContext.java @@ -3,13 +3,18 @@ import java.awt.image.BufferedImage; import java.util.concurrent.locks.ReentrantLock; -public class RenderedData { +import javax.script.ScriptEngine; + +import org.area515.resinprinter.server.HostProperties; + +public class RenderingContext { private BufferedImage image; private BufferedImage preTransformedImage; private Double area; private ReentrantLock lock = new ReentrantLock(); - - public RenderedData() { + private ScriptEngine scriptEngine = HostProperties.Instance().buildScriptEngine(); + + public RenderingContext() { } public void setPrintableImage(BufferedImage image) { @@ -36,4 +41,8 @@ public Double getArea() { public ReentrantLock getLock() { return lock; } + + public ScriptEngine getScriptEngine() { + return scriptEngine; + } } diff --git a/host/src/main/java/org/area515/resinprinter/minercube/MinerCubePrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/minercube/MinerCubePrintFileProcessor.java index 81dc61d89..158202c7c 100644 --- a/host/src/main/java/org/area515/resinprinter/minercube/MinerCubePrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/minercube/MinerCubePrintFileProcessor.java @@ -5,9 +5,7 @@ import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -17,21 +15,26 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.area515.resinprinter.display.InappropriateDeviceException; import org.area515.resinprinter.job.AbstractPrintFileProcessor; import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.JobStatus; import org.area515.resinprinter.job.PrintJob; +import org.area515.resinprinter.job.render.CurrentImageRenderer; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.server.Main; public class MinerCubePrintFileProcessor extends AbstractPrintFileProcessor { private static final Logger logger = LogManager.getLogger(); - private Map minerCubesByPrintJob = new HashMap(); - - private class PrintCube { - Future cube; - BufferedImage currentImage; - } + private class MinerDataAid extends DataAid { + public Future cube; + + public MinerDataAid(PrintJob printJob) throws JobManagerException { + super(printJob); + } + } + @Override public String[] getFileExtensions() { return new String[]{"cubemaze"}; @@ -39,32 +42,32 @@ public String[] getFileExtensions() { @Override public boolean acceptsFile(File processingFile) { - return processingFile.getName().toLowerCase().endsWith("cubemaze"); } @Override - public BufferedImage getCurrentImage(PrintJob printJob) { - PrintCube printCube = minerCubesByPrintJob.get(printJob); - return printCube.currentImage; + public Double getBuildAreaMM(PrintJob printJob) { + //TODO: haven't built any of this + return null; } @Override - public Double getBuildAreaMM(PrintJob printJob) { - //TODO: haven't built any of this + public CurrentImageRenderer createRenderer(DataAid aid, Object imageIndexToBuild) { + //Won't get called because we aren't using renders. return null; } @Override public JobStatus processFile(PrintJob printJob) throws Exception { + final String MAIN_IMAGE = "lastExtrusionImage"; + try { - DataAid data = initializeJobCacheWithDataAid(printJob); + MinerDataAid data = (MinerDataAid)getDataAid(printJob); + MinerCube cube = data.cube.get(); //Everything needs to be setup in the dataByPrintJob before we start the header performHeader(data); - - PrintCube printCube = minerCubesByPrintJob.get(printJob); - MinerCube cube = printCube.cube.get(); + cube.startPrint(data.xPixelsPerMM, data.yPixelsPerMM, data.sliceHeight); //TODO: need to set the total slices for a percentage complete: printJob.setTotalSlices(); @@ -73,9 +76,13 @@ public JobStatus processFile(PrintJob printJob) throws Exception { int firstSlices = data.inkConfiguration.getNumberOfFirstLayers(); List rects = cube.buildNextPrintSlice(centerX, centerY); + RenderingContext renderedData = data.cache.getOrCreateIfMissing(Boolean.TRUE); + while (cube.hasPrintSlice()) { + data.startSlice(); + //Performs all of the duties that are common to most print files - JobStatus status = performPreSlice(data, null); + JobStatus status = performPreSlice(data, renderedData.getScriptEngine(), null); if (status != null) { return status; } @@ -86,15 +93,14 @@ public JobStatus processFile(PrintJob printJob) throws Exception { graphics.fillRect(0, 0, data.xResolution, data.yResolution); graphics.setColor(Color.white); for (Rectangle currentRect : rects) { - //graphics.fillRect(currentRect.x, currentRect.y, currentRect.width, currentRect.height); graphics.fillRect(currentRect.x, currentRect.y, currentRect.width, currentRect.height); } - image = applyImageTransforms(data, image); - //applyBulbMask(data, graphics, data.xResolution, data.yResolution); + image = applyImageTransforms(data, renderedData.getScriptEngine(), image); - //Performs all of the duties that are common to most print files - status = printImageAndPerformPostProcessing(data, printCube.currentImage = image); + //Performs all of the duties that are common to most print files] + renderedData.setPrintableImage(image); + status = printImageAndPerformPostProcessing(data, renderedData.getScriptEngine(), image); if (status != null) { return status; } @@ -108,10 +114,15 @@ public JobStatus processFile(PrintJob printJob) throws Exception { return performFooter(data); } finally { - minerCubesByPrintJob.remove(printJob); + clearDataAid(printJob); } } + @Override + public DataAid createDataAid(PrintJob printJob) throws JobManagerException { + return new MinerDataAid(printJob); + } + @Override public void prepareEnvironment(File processingFile, PrintJob printJob) throws JobManagerException { JAXBContext jaxbContext; @@ -126,12 +137,15 @@ public MinerCube call() throws Exception { return cube; } }); - PrintCube printCube = new PrintCube(); - printCube.cube = future; - minerCubesByPrintJob.put(printJob, printCube); + MinerDataAid aid = (MinerDataAid)initializeJobCacheWithDataAid(printJob); + aid.cube = future; } catch (JAXBException e) { logger.error("Marshalling error while processing file:" + processingFile, e); throw new JobManagerException("I was expecting a MinerCube XML file. I don't understand this file."); + } catch (InappropriateDeviceException e) { + String error = "Couldn't initialize data aid for file:" + processingFile; + logger.error(error, e); + throw new JobManagerException(error); } } diff --git a/host/src/main/java/org/area515/resinprinter/notification/WebSocketPrintJobNotifier.java b/host/src/main/java/org/area515/resinprinter/notification/WebSocketPrintJobNotifier.java index 34d47d15f..a8f76820e 100644 --- a/host/src/main/java/org/area515/resinprinter/notification/WebSocketPrintJobNotifier.java +++ b/host/src/main/java/org/area515/resinprinter/notification/WebSocketPrintJobNotifier.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URI; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.CloseReason; @@ -22,7 +23,6 @@ import org.area515.resinprinter.display.InappropriateDeviceException; import org.area515.resinprinter.job.JobStatus; import org.area515.resinprinter.job.PrintJob; -import org.area515.resinprinter.job.StaticJobStatusFuture; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.slice.StlError; import org.area515.util.JacksonEncoder; @@ -116,7 +116,7 @@ public void fileUploadComplete(File fileUploaded) { try { //This just mocks up a printJob it's not a real print job, it's just something we can notify our clients with. PrintJob job = new PrintJob(fileUploaded); - job.initializePrintJob(new StaticJobStatusFuture(JobStatus.Ready)); + job.initializePrintJob(CompletableFuture.completedFuture(JobStatus.Ready)); currentSession.getAsyncRemote().sendObject(new PrintJobEvent(job, NotificationEvent.FileUploadComplete)); } catch (Exception e) { logger.error("Error sending event to websocket:" + currentSession.getId(), e); diff --git a/host/src/main/java/org/area515/resinprinter/plugin/Feature.java b/host/src/main/java/org/area515/resinprinter/plugin/Feature.java index e9616fc6e..9a45a5fc5 100644 --- a/host/src/main/java/org/area515/resinprinter/plugin/Feature.java +++ b/host/src/main/java/org/area515/resinprinter/plugin/Feature.java @@ -3,6 +3,6 @@ import java.net.URI; public interface Feature { - public void start(URI uri) throws Exception; + public void start(URI uri, String settings) throws Exception; public void stop(); } diff --git a/host/src/main/java/org/area515/resinprinter/plugin/FeatureManager.java b/host/src/main/java/org/area515/resinprinter/plugin/FeatureManager.java index 5d51bfb1e..3aa4d52d7 100644 --- a/host/src/main/java/org/area515/resinprinter/plugin/FeatureManager.java +++ b/host/src/main/java/org/area515/resinprinter/plugin/FeatureManager.java @@ -1,10 +1,9 @@ package org.area515.resinprinter.plugin; import java.net.URI; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,14 +13,14 @@ public class FeatureManager { private static final Logger logger = LogManager.getLogger(); - private static List features = null; + private static Map features = null; private static void initFeatures() { - features = new ArrayList(); - List> featureClasses = HostProperties.Instance().getFeatures(); - for (Class currentClass : featureClasses) { + features = new HashMap(); + Map, String> featureClasses = HostProperties.Instance().getFeatures(); + for (Entry, String> currentEntry : featureClasses.entrySet()) { try { - features.add(currentClass.newInstance()); + features.put(currentEntry.getKey().newInstance(), currentEntry.getValue()); } catch (Exception e) { logger.error("Couldn't create feature", e); } @@ -34,9 +33,9 @@ public static void start(URI uri) { initFeatures(); } - for (Feature currentFeature : features) { + for (Entry currentFeature : features.entrySet()) { try { - currentFeature.start(uri); + currentFeature.getKey().start(uri, currentFeature.getValue()); } catch (Exception e) { logger.error("Couldn't start feature", e); } @@ -44,7 +43,7 @@ public static void start(URI uri) { } public static void shutdown() { - for (Feature currentFeature : features) { + for (Feature currentFeature : features.keySet()) { currentFeature.stop(); } } @@ -55,7 +54,7 @@ public static Map getFriendshipFeatures() { } Map friendshipFeatures = new HashMap(); - for (Feature currentFeature : features) { + for (Feature currentFeature : features.keySet()) { if (currentFeature instanceof FriendshipFeature) { friendshipFeatures.put(((FriendshipFeature) currentFeature).getName(), (FriendshipFeature)currentFeature); } @@ -69,7 +68,7 @@ public static UserManagementFeature getUserManagementFeature() { initFeatures(); } - for (Feature currentFeature : features) { + for (Feature currentFeature : features.keySet()) { if (currentFeature instanceof UserManagementFeature) { return (UserManagementFeature)currentFeature; } diff --git a/host/src/main/java/org/area515/resinprinter/printer/MachineConfig.java b/host/src/main/java/org/area515/resinprinter/printer/MachineConfig.java index 759b7a128..34bf575b3 100644 --- a/host/src/main/java/org/area515/resinprinter/printer/MachineConfig.java +++ b/host/src/main/java/org/area515/resinprinter/printer/MachineConfig.java @@ -8,6 +8,8 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; +import org.area515.resinprinter.display.FullScreenMode; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -67,6 +69,8 @@ public static class MonitorDriverConfig { private int monitorBottom; @XmlElement(name="UseMask") private boolean useMask; + @XmlElement(name="FullScreenMode") + private FullScreenMode fullScreenMode; @XmlTransient public boolean isUseMask() { @@ -99,6 +103,18 @@ public ComPortSettings getComPortSettings() { public void setComPortSettings(ComPortSettings comPortSettings) { this.comPortSettings = comPortSettings; } + + @XmlTransient + public FullScreenMode getFullScreenMode() { + if (fullScreenMode == null) { + return FullScreenMode.NeverUseFullScreen; + } + + return fullScreenMode; + } + public void setFullScreenMode(FullScreenMode fullScreenMode) { + this.fullScreenMode = fullScreenMode; + } } @XmlAttribute(name="FileVersion") @@ -249,8 +265,7 @@ public Boolean getRestartSerialOnTimeout() { public void setRestartSerialOnTimeout(Boolean restartSerialOnTimeout) { this.restartSerialOnTimeout = restartSerialOnTimeout; } - - + @XmlTransient public String getOSMonitorID() { return monitorDriverConfig.osMonitorID; diff --git a/host/src/main/java/org/area515/resinprinter/printer/Printer.java b/host/src/main/java/org/area515/resinprinter/printer/Printer.java index 394f4bb3f..c7cb40255 100644 --- a/host/src/main/java/org/area515/resinprinter/printer/Printer.java +++ b/host/src/main/java/org/area515/resinprinter/printer/Printer.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -public class Printer { +public class Printer implements Named { private static final Logger logger = LogManager.getLogger(); private PrinterConfiguration configuration; @@ -78,6 +78,9 @@ public Printer(PrinterConfiguration configuration) throws InappropriateDeviceExc public String getName() { return configuration.getName(); } + public void setName(String name) { + configuration.setName(name); + } @XmlTransient @JsonProperty @@ -176,7 +179,7 @@ public JobStatus togglePause() { public void initializeAndAssignGraphicsOutputInterface(final GraphicsOutputInterface device, final String displayDeviceID) { this.displayDeviceID = displayDeviceID; - this.refreshFrame = device.initializeDisplay(displayDeviceID); + this.refreshFrame = device.initializeDisplay(displayDeviceID, getConfiguration()); Rectangle screenSize = refreshFrame.getBoundary(); getConfiguration().getMachineConfig().getMonitorDriverConfig().setDLP_X_Res(screenSize.width); @@ -199,8 +202,8 @@ public void showGridImage(int pixels) { refreshFrame.showGridImage(pixels); } - public void showImage(BufferedImage image) { - refreshFrame.showImage(image); + public void showImage(BufferedImage image, boolean performFullUpdate) { + refreshFrame.showImage(image, performFullUpdate); } @JsonIgnore diff --git a/host/src/main/java/org/area515/resinprinter/printer/SlicingProfile.java b/host/src/main/java/org/area515/resinprinter/printer/SlicingProfile.java index 57f71b154..6d0b152e6 100644 --- a/host/src/main/java/org/area515/resinprinter/printer/SlicingProfile.java +++ b/host/src/main/java/org/area515/resinprinter/printer/SlicingProfile.java @@ -9,9 +9,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.inkdetection.PrintMaterialDetector; -import org.area515.resinprinter.inkdetection.PrintMaterialDetectorSettings; import org.area515.resinprinter.job.InkDetector; import org.area515.resinprinter.job.PrintJob; +import org.area515.util.DynamicJSonSettings; import org.area515.util.TemplateEngine; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -133,13 +133,13 @@ public static class InkConfig { private double resinPriceL; private InkDetector detector; @XmlElement(name="PrintMaterialDetectorSettings") - private PrintMaterialDetectorSettings printMaterialDetectorSettings; + private DynamicJSonSettings printMaterialDetectorSettings; @XmlTransient - public PrintMaterialDetectorSettings getPrintMaterialDetectorSettings() { + public DynamicJSonSettings getPrintMaterialDetectorSettings() { return printMaterialDetectorSettings; } - public void setPrintMaterialDetectorSettings(PrintMaterialDetectorSettings printMaterialDetectorSettings) { + public void setPrintMaterialDetectorSettings(DynamicJSonSettings printMaterialDetectorSettings) { this.printMaterialDetectorSettings = printMaterialDetectorSettings; } diff --git a/host/src/main/java/org/area515/resinprinter/printphoto/ImagePrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/printphoto/ImagePrintFileProcessor.java index d9b7f77b8..4ea8719ac 100644 --- a/host/src/main/java/org/area515/resinprinter/printphoto/ImagePrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/printphoto/ImagePrintFileProcessor.java @@ -7,6 +7,7 @@ import javax.imageio.ImageIO; import org.area515.resinprinter.job.AbstractPrintFileProcessor; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.twodim.TwoDimensionalImageRenderer; @@ -51,9 +52,8 @@ public boolean isThreeDimensionalGeometryAvailable() { } @Override - public TwoDimensionalImageRenderer createRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild) { - return new TwoDimensionalImageRenderer(aid, processor, imageIndexToBuild) { - @Override + public TwoDimensionalImageRenderer createTwoDimensionalRenderer(DataAid aid, Object imageIndexToBuild) { + return new TwoDimensionalImageRenderer(aid, this, imageIndexToBuild) { public BufferedImage loadImageFromFile(PrintJob job) throws JobManagerException { try { return ImageIO.read(job.getJobFile()); diff --git a/host/src/main/java/org/area515/resinprinter/printphoto/SVGImagePrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/printphoto/SVGImagePrintFileProcessor.java index 6e819ea7d..31ab986e3 100644 --- a/host/src/main/java/org/area515/resinprinter/printphoto/SVGImagePrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/printphoto/SVGImagePrintFileProcessor.java @@ -14,6 +14,7 @@ import org.apache.batik.util.SVGConstants; import org.apache.commons.io.FileUtils; import org.area515.resinprinter.job.AbstractPrintFileProcessor; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.twodim.TwoDimensionalImageRenderer; @@ -30,9 +31,10 @@ public boolean acceptsFile(File processingFile) { return name.endsWith("svg"); } + @Override - public TwoDimensionalImageRenderer createRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild) { - return new TwoDimensionalImageRenderer(aid, processor, imageIndexToBuild) { + public TwoDimensionalImageRenderer createTwoDimensionalRenderer(DataAid aid, Object imageIndexToBuild) { + return new TwoDimensionalImageRenderer(aid, this, imageIndexToBuild) { @Override public BufferedImage loadImageFromFile(PrintJob processingFile) throws JobManagerException { try { diff --git a/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinFileProcessor.java b/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinFileProcessor.java new file mode 100644 index 000000000..4cfce8984 --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinFileProcessor.java @@ -0,0 +1,43 @@ +package org.area515.resinprinter.printphoto.micoin; + +import java.io.File; + +import org.area515.resinprinter.job.PrintJob; +import org.area515.resinprinter.job.render.CurrentImageRenderer; +import org.area515.resinprinter.printphoto.ImagePrintFileProcessor; +import org.area515.resinprinter.twodim.TwoDimensionalImageRenderer; + +public class CoinFileProcessor extends ImagePrintFileProcessor { + @Override + public String[] getFileExtensions() { + return new String[]{"coin"}; + } + + @Override + public boolean acceptsFile(File processingFile) { + //TODO: this could be smarter by loading the file instead of just checking the file type + String name = processingFile.getName().toLowerCase(); + return name.endsWith("coin"); + } + + @Override + public String getFriendlyName() { + return "Coin"; + } + + @Override + protected CurrentImageRenderer buildPlatformRenderer(DataAid aid, Object nextRenderingPointer, int totalPlatformSlices, CurrentImageRenderer platformSizeInitializer) { + return new CoinRenderer(aid, this, nextRenderingPointer); + } + + @Override + public TwoDimensionalImageRenderer createTwoDimensionalRenderer(DataAid aid, Object imageIndexToBuild) { + return new CoinRenderer(aid, this, imageIndexToBuild); + } + + //Double our extrusion count because head = extrusion1; middle = platform; tails = extrusion2; + @Override + protected void setupSlices(PrintJob printJob, TwoDimensionalDataAid dataAid, int suggestedPlatformSlices, int suggestedExtrusionSlices) { + super.setupSlices(printJob, dataAid, suggestedPlatformSlices, suggestedExtrusionSlices * 2); + } +} diff --git a/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinRenderer.java b/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinRenderer.java new file mode 100644 index 000000000..b5cd48904 --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/printphoto/micoin/CoinRenderer.java @@ -0,0 +1,71 @@ +package org.area515.resinprinter.printphoto.micoin; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.area515.resinprinter.job.AbstractPrintFileProcessor; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; +import org.area515.resinprinter.job.JobManagerException; +import org.area515.resinprinter.job.PrintJob; +import org.area515.resinprinter.job.render.RenderingContext; +import org.area515.resinprinter.twodim.TwoDimensionalImageRenderer; +import org.area515.util.Log4jUtil; + +public class CoinRenderer extends TwoDimensionalImageRenderer { + private static final Logger logger = LogManager.getLogger(); + private static final String FIRST_IMAGE = "FirstImage"; + private static final String MIDDLE_IMAGE = "MiddleImage"; + private static final String LAST_IMAGE = "LastImage"; + + public CoinRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild) { + super(aid, processor, imageIndexToBuild); + } + + public BufferedImage renderImage(BufferedImage imageToDisplay) throws JobManagerException { + if (imageToDisplay == null) { + imageToDisplay = scaleImageAndDetectEdges(aid.printJob); + RenderingContext firstData = aid.cache.getOrCreateIfMissing(FIRST_IMAGE); + RenderingContext middleData = aid.cache.getOrCreateIfMissing(MIDDLE_IMAGE); + RenderingContext lastData = aid.cache.getOrCreateIfMissing(LAST_IMAGE); + int width = imageToDisplay.getWidth(); + int height = imageToDisplay.getHeight(); + firstData.setPreTransformedImage(imageToDisplay.getSubimage(0, 0, width/3, height)); + middleData.setPreTransformedImage(imageToDisplay.getSubimage(width/3, 0, width/3, height)); + lastData.setPreTransformedImage(imageToDisplay.getSubimage(width * 2/3, 0, width/3, height)); + + logger.trace("Writing scaleImageAndDetectEdges1" + FIRST_IMAGE + ":{}", () -> Log4jUtil.logImage(firstData.getPreTransformedImage(), "scaleImageAndDetectEdges1" + FIRST_IMAGE + ".png")); + logger.trace("Writing scaleImageAndDetectEdges2" + MIDDLE_IMAGE + ":{}", () -> Log4jUtil.logImage(middleData.getPreTransformedImage(), "scaleImageAndDetectEdges2" + MIDDLE_IMAGE + ".png")); + logger.trace("Writing scaleImageAndDetectEdges3" + LAST_IMAGE + ":{}", () -> Log4jUtil.logImage(lastData.getPreTransformedImage(), "scaleImageAndDetectEdges3" + LAST_IMAGE + ".png")); + } + + CoinFileProcessor processor = (CoinFileProcessor)aid.printJob.getPrintFileProcessor(); + int extrusionCount = processor.getSuggested2DExtrusionLayerCount(aid); + int platformCount = processor.getSuggestedPlatformLayerCount(aid); + int imageIndex = aid.printJob.getCurrentSlice(); + + if (imageIndex < extrusionCount) { + logger.trace("Writing call1" + FIRST_IMAGE + ":{}", () -> Log4jUtil.logImage(aid.cache.getOrCreateIfMissing(FIRST_IMAGE).getPreTransformedImage(), "call1" + FIRST_IMAGE + ".png")); + return aid.cache.getOrCreateIfMissing(FIRST_IMAGE).getPreTransformedImage(); + } + + if (imageIndex < extrusionCount + platformCount) { + logger.trace("Writing call2" + MIDDLE_IMAGE + ":{}", () -> Log4jUtil.logImage(aid.cache.getOrCreateIfMissing(MIDDLE_IMAGE).getPreTransformedImage(), "call2" + MIDDLE_IMAGE + ".png")); + return aid.cache.getOrCreateIfMissing(MIDDLE_IMAGE).getPreTransformedImage(); + } + + logger.trace("Writing call3" + LAST_IMAGE + ":{}", () -> Log4jUtil.logImage(aid.cache.getOrCreateIfMissing(LAST_IMAGE).getPreTransformedImage(), "call3" + LAST_IMAGE + ".png")); + return aid.cache.getOrCreateIfMissing(LAST_IMAGE).getPreTransformedImage(); + } + + public BufferedImage loadImageFromFile(PrintJob job) throws JobManagerException { + try { + return ImageIO.read(job.getJobFile()); + } catch (IOException e) { + throw new JobManagerException("Couldn't load image file:" + job.getJobFile(), e); + } + } +} diff --git a/host/src/main/java/org/area515/resinprinter/security/JettySecurityUtils.java b/host/src/main/java/org/area515/resinprinter/security/JettySecurityUtils.java index dcbc4a470..b0a1bdd11 100644 --- a/host/src/main/java/org/area515/resinprinter/security/JettySecurityUtils.java +++ b/host/src/main/java/org/area515/resinprinter/security/JettySecurityUtils.java @@ -170,7 +170,7 @@ public static boolean isCertificateAndPrivateKeyAvailable(File keyFile, String p } } - public static void secureContext(String ipAddress, ServletContextHandler context, Server jettyServer) throws Exception { + public static void secureContext(String ipAddress, ServletContextHandler context, Server jettyServer) throws InvalidNameException, IOException, GeneralSecurityException { if (HostProperties.Instance().getExternallyAccessableName() != null) { ipAddress = HostProperties.Instance().getExternallyAccessableName(); } diff --git a/host/src/main/java/org/area515/resinprinter/security/keystore/KeystoreLoginService.java b/host/src/main/java/org/area515/resinprinter/security/keystore/KeystoreLoginService.java index 92b90e4ea..20b88dab5 100644 --- a/host/src/main/java/org/area515/resinprinter/security/keystore/KeystoreLoginService.java +++ b/host/src/main/java/org/area515/resinprinter/security/keystore/KeystoreLoginService.java @@ -161,7 +161,7 @@ public void logout(UserIdentity user) { } @Override - public void start(URI uri) { + public void start(URI uri, String settings) { if (!keyFile.exists()) { return; } diff --git a/host/src/main/java/org/area515/resinprinter/security/keystore/X509FriendshipFeature.java b/host/src/main/java/org/area515/resinprinter/security/keystore/X509FriendshipFeature.java index c1947ec4e..c7c8fa730 100644 --- a/host/src/main/java/org/area515/resinprinter/security/keystore/X509FriendshipFeature.java +++ b/host/src/main/java/org/area515/resinprinter/security/keystore/X509FriendshipFeature.java @@ -59,7 +59,7 @@ void remoteAcceptedFriendRequest(UUID local, PhotonicUser remote) { } @Override - public void start(URI startURI) throws Exception { + public void start(URI startURI, String settings) throws Exception { this.server = RendezvousClient.getServer(startURI); } diff --git a/host/src/main/java/org/area515/resinprinter/serial/ConsoleCommPort.java b/host/src/main/java/org/area515/resinprinter/serial/ConsoleCommPort.java index ec2d7fa85..62ec37242 100644 --- a/host/src/main/java/org/area515/resinprinter/serial/ConsoleCommPort.java +++ b/host/src/main/java/org/area515/resinprinter/serial/ConsoleCommPort.java @@ -13,8 +13,18 @@ public class ConsoleCommPort implements SerialCommunicationsPort { private int readCount; private int timeout; - public ConsoleCommPort() { - this.name = this.name + ":" + consoleNumber++; + private ConsoleCommPort() { + } + + private ConsoleCommPort(int consoleNumber) { + this.name = this.name + ":" + consoleNumber; + } + + public static ConsoleCommPort getSelectableConsoleCommPort() { + return new ConsoleCommPort(); + } + public static ConsoleCommPort getNextAvailableConsoleCommPort() { + return new ConsoleCommPort(consoleNumber++); } @Override diff --git a/host/src/main/java/org/area515/resinprinter/serial/CustomCommPort.java b/host/src/main/java/org/area515/resinprinter/serial/CustomCommPort.java index 1684102b6..bb79d9d3d 100644 --- a/host/src/main/java/org/area515/resinprinter/serial/CustomCommPort.java +++ b/host/src/main/java/org/area515/resinprinter/serial/CustomCommPort.java @@ -5,7 +5,6 @@ import org.area515.resinprinter.display.AlreadyAssignedException; import org.area515.resinprinter.display.InappropriateDeviceException; import org.area515.resinprinter.printer.ComPortSettings; -import org.area515.resinprinter.printer.Printer; public class CustomCommPort implements SerialCommunicationsPort { private String name; diff --git a/host/src/main/java/org/area515/resinprinter/serial/SerialManager.java b/host/src/main/java/org/area515/resinprinter/serial/SerialManager.java index a74a1f668..3624d8da1 100644 --- a/host/src/main/java/org/area515/resinprinter/serial/SerialManager.java +++ b/host/src/main/java/org/area515/resinprinter/serial/SerialManager.java @@ -296,7 +296,7 @@ public SerialCommunicationsPort getSerialDevice(String comport) throws Inappropr throw new InappropriateDeviceException("No communications port was specified"); } if(ConsoleCommPort.GCODE_RESPONSE_SIMULATION.equalsIgnoreCase(comport)){ - return new ConsoleCommPort(); + return ConsoleCommPort.getNextAvailableConsoleCommPort(); } for (SerialCommunicationsPort current : getSerialDevices()) { if (current.getName().equals(comport)) { @@ -339,8 +339,7 @@ public List getSerialDevices() { idents.add(new CustomCommPort(AUTO_DETECT_PROJECTOR)); if (HostProperties.Instance().getFakeSerial()) { - ConsoleCommPort consolePort = new ConsoleCommPort(); - idents.add(consolePort); + idents.add(ConsoleCommPort.getSelectableConsoleCommPort()); } return idents; diff --git a/host/src/main/java/org/area515/resinprinter/server/HostProperties.java b/host/src/main/java/org/area515/resinprinter/server/HostProperties.java index cf5f61b27..f47d9dd7b 100644 --- a/host/src/main/java/org/area515/resinprinter/server/HostProperties.java +++ b/host/src/main/java/org/area515/resinprinter/server/HostProperties.java @@ -11,7 +11,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -36,6 +38,7 @@ import org.area515.resinprinter.display.AlreadyAssignedException; import org.area515.resinprinter.display.GraphicsOutputInterface; import org.area515.resinprinter.display.InappropriateDeviceException; +import org.area515.resinprinter.job.Customizer; import org.area515.resinprinter.job.PrintFileProcessor; import org.area515.resinprinter.network.LinuxNetworkManager; import org.area515.resinprinter.network.NetworkManager; @@ -68,28 +71,29 @@ public class HostProperties { public static String MACHINE_EXTENSION = ".machine"; public File MACHINE_DIR = new File(System.getProperty("user.home"), "Machines"); private static String PRINTER_EXTENSION = ".printer"; - private File printerDir = new File(System.getProperty("user.home"), "3dPrinters"); - + private File printerDir = new File(System.getProperty("user.home"), "3dPrinters"); + public static String CUSTOMIZER_EXTENSION = ".customizer"; + private File CUSTOMIZER_DIR = new File(System.getProperty("user.home"), "Customizers"); + private static HostProperties INSTANCE = null; private File uploadDir; private File printDir; - private String hostGUI; private boolean fakeSerial = false; private boolean removeJobOnCompletion = true; private boolean forceCalibrationOnFirstUse = false; private boolean limitLiveStreamToOneCPU = false; private ConcurrentHashMap configurations; - private List> featureClasses = new ArrayList>(); + private Map, String> featureClasses = new HashMap, String>(); private List> notificationClasses = new ArrayList>(); private List printFileProcessors = new ArrayList(); private List displayDevices = new ArrayList(); private Class serialPortClass; private Class networkManagerClass; - private int versionNumber; private String releaseTagName; private List visibleCards; private String hexCodeBasedProjectorsJson; + private String skinsStringJson; private String forwardHeader; private CountDownLatch hostReady = new CountDownLatch(1); private String scriptEngineLanguage = null; @@ -188,8 +192,8 @@ private HostProperties() { uploadDirString = configurationProperties.getProperty("uploaddir"); fakeSerial = new Boolean(configurationProperties.getProperty("fakeserial", "false")); - hostGUI = configurationProperties.getProperty("hostGUI", "resources"); visibleCards = Arrays.asList(configurationProperties.getProperty("visibleCards", "printers,printJobs,printables,users,settings").split(",")); + skinsStringJson = configurationProperties.getProperty("skins", "[{\"name\":\"Main skin\", \"welcomeFiles\":[\"index.htm\"], \"resourceBase\": \"resourcesnew\", \"active\": true}]"); //This loads features for (Entry currentProperty : configurationProperties.entrySet()) { @@ -198,7 +202,7 @@ private HostProperties() { currentPropertyString = currentPropertyString.replace("feature.", ""); if ("true".equalsIgnoreCase(currentProperty.getValue() + "")) { try { - featureClasses.add((Class)Class.forName(currentPropertyString)); + featureClasses.put((Class)Class.forName(currentPropertyString), configurationProperties.getProperty("featureSettings." + currentPropertyString)); } catch (NoClassDefFoundError | UnsatisfiedLinkError | ClassNotFoundException e) { logger.error("Failed to load Feature:" + currentPropertyString, e); } @@ -273,7 +277,7 @@ private HostProperties() { try { userManagementFeature = configurationProperties.getProperty("UserManagementFeatureImplementation", KeystoreLoginService.class.getName()); Class userManagementFeatureClass = (Class)Class.forName(userManagementFeature); - featureClasses.add(userManagementFeatureClass); + featureClasses.put(userManagementFeatureClass, configurationProperties.getProperty("featureSettings." + userManagementFeatureClass.getName())); } catch (ClassNotFoundException e) { logger.error("Failed to load UserManagementFeatureImplementation:{}", userManagementFeature); } @@ -329,7 +333,7 @@ private HostProperties() { Properties newProperties = new Properties(); try { newProperties.load(new FileInputStream(versionFile)); - versionNumber = Integer.valueOf((String)newProperties.get("build.number")) - 1; + //versionNumber = Integer.valueOf((String)newProperties.get("build.number")) - 1; releaseTagName = (String)newProperties.get("repo.version"); } catch (IOException e) { logger.error("Version file is missing:{}", versionFile); @@ -355,6 +359,16 @@ private HostProperties() { this.sharedScriptEngine = this.buildScriptEngine(); } + public Skin getFirstActiveSkin() { + for (Skin skin : getSkins()) { + if (skin.isActive()) { + return skin; + } + } + + return null; + } + private Properties getClasspathProperties() { InputStream stream = HostProperties.class.getClassLoader().getResourceAsStream("config.properties"); if (stream == null) { @@ -464,10 +478,6 @@ public String getPrinterProfileRepo() { public int getPrinterHostPort() { return printerHostPort; } - - public String getHostGUIDir() { - return hostGUI; - } public File getUploadDir(){ return uploadDir; @@ -513,7 +523,7 @@ public Class getNetworkManagerClass() { return networkManagerClass; } - public List> getFeatures() { + public Map, String> getFeatures() { return featureClasses; } @@ -666,6 +676,18 @@ public List getAutodetectProjectors() { } } + public List getSkins() { + ObjectMapper mapper = new ObjectMapper(new JsonFactory()); + List projectors; + try { + projectors = mapper.readValue(skinsStringJson, new TypeReference>(){}); + return projectors; + } catch (IOException e) { + logger.error("Problem loading skins json.", e); + return new ArrayList(); + } + } + public List getConfigurations(File searchDirectory, String extension, Class clazz) { List configurations = new ArrayList(); @@ -698,6 +720,50 @@ public boolean accept(File dir, String name) { return configurations; } + public Customizer loadCustomizer(String customizerName) throws InappropriateDeviceException { + if (!CUSTOMIZER_DIR.exists() && !CUSTOMIZER_DIR.mkdirs()) { + throw new InappropriateDeviceException("Couldn't create customizer directory:" + CUSTOMIZER_DIR); + } + + File currentFile = new File(CUSTOMIZER_DIR, customizerName + CUSTOMIZER_EXTENSION); + if (!currentFile.exists()) { + return null; + } + + JAXBContext jaxbContext; + try { + jaxbContext = JAXBContext.newInstance(Customizer.class); + Unmarshaller jaxbUnMarshaller = jaxbContext.createUnmarshaller(); + + Customizer customizer = (Customizer)jaxbUnMarshaller.unmarshal(currentFile); + + logger.info("Loaded customizer for:{}", customizer); + return customizer; + } catch (JAXBException e) { + throw new InappropriateDeviceException("Problem marshalling customizer from:" + currentFile, e); + } + } + + public Customizer saveCustomizer(Customizer customizer) throws InappropriateDeviceException { + if (!CUSTOMIZER_DIR.exists() && !CUSTOMIZER_DIR.mkdirs()) { + throw new InappropriateDeviceException("Couldn't create customizer directory:" + CUSTOMIZER_DIR); + } + + File currentFile = new File(CUSTOMIZER_DIR, customizer.getName() + CUSTOMIZER_EXTENSION); + JAXBContext jaxbContext; + try { + jaxbContext = JAXBContext.newInstance(Customizer.class); + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + jaxbMarshaller.marshal(customizer, currentFile); + logger.info("Saved customizer for:{}", customizer); + NotificationManager.hostSettingsChanged(); + return customizer; + } catch (JAXBException e) { + throw new InappropriateDeviceException("Problem marshalling customizer from:" + currentFile, e); + } + } + public List getPrinterConfigurations() { if (configurations != null) { return new ArrayList(configurations.values()); @@ -746,7 +812,7 @@ public boolean accept(File dir, String name) { //We do not want to start the printer here configurations.put(configuration.getName(), configuration); - logger.info("Created printer configuration for:{}", configuration); + logger.info("Loaded printer configuration for:{}", configuration); } catch (JAXBException e) { logger.error("Problem marshalling printer configurations from:" + currentFile, e); } @@ -761,7 +827,7 @@ public PrinterConfiguration getPrinterConfiguration(String name) { return configurations.get(name); } - private static T deepCopyJAXB(T object, Class clazz) throws JAXBException { + public static T deepCopyJAXB(T object, Class clazz) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(clazz); JAXBElement contentObject = new JAXBElement(new QName(clazz.getSimpleName()), clazz, object); JAXBSource source = new JAXBSource(jaxbContext, contentObject); @@ -866,6 +932,7 @@ private void overwriteOverriddenConfigurationProperties(Properties overridenConf IOUtils.closeQuietly(stream); } } + public void hostStartupComplete() { Properties oneTimeInstall = new Properties(); oneTimeInstall.setProperty("performedOneTimeInstall", "true"); diff --git a/host/src/main/java/org/area515/resinprinter/server/Main.java b/host/src/main/java/org/area515/resinprinter/server/Main.java index 3a73da3e1..c42ff3cda 100644 --- a/host/src/main/java/org/area515/resinprinter/server/Main.java +++ b/host/src/main/java/org/area515/resinprinter/server/Main.java @@ -1,15 +1,20 @@ package org.area515.resinprinter.server; +import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; +import java.net.SocketException; import java.net.URI; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; +import javax.naming.InvalidNameException; +import javax.servlet.ServletException; import javax.websocket.server.ServerContainer; import org.apache.logging.log4j.LogManager; @@ -37,6 +42,8 @@ import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + /* * References: * http://news-anand.blogspot.com/2012/05/today-i-am-going-tell-you-how-to-create.html @@ -48,15 +55,8 @@ public class Main { private static final Logger logger = LogManager.getLogger(); public static final String AUTHENTICATION_SCHEME = Constraint.__BASIC_AUTH; - public static ScheduledExecutorService GLOBAL_EXECUTOR = new ScheduledThreadPoolExecutor(3, new ThreadFactory() { - private AtomicInteger threads = new AtomicInteger(); - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, "PrintJobProcessorThread-" + threads.incrementAndGet()); - thread.setDaemon(true); - return thread; - } - }); + public static ScheduledExecutorService GLOBAL_EXECUTOR = new ScheduledThreadPoolExecutor(20, new ThreadFactoryBuilder().setNameFormat("PrintJobProcessorThread-%d").setDaemon(true).build()); + private static Server server; public static void setupAuthentication(ServletContextHandler context, UserManagementFeature loginService) { //All below is user based security @@ -88,7 +88,7 @@ public static void setupAuthentication(ServletContextHandler context, UserManage context.setSecurityHandler(csh); } - public static void main(String[] args) throws Exception { + public static void startServer() { logger.info("================================================================="); logger.info("================================================================="); logger.info("Photonic3D started"); @@ -105,20 +105,17 @@ public static void main(String[] args) throws Exception { * Start server */ - final Server server = new Server(port); - - /* - * Setup ResourceHandler for html files - */ - - // Create the ResourceHandler. It is the object that will handle the request for a given file. It is - // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples. - ResourceHandler resource_handler = new ResourceHandler(); - // Configure the ResourceHandler. Setting the resource base indicates where the files should be served out of. - // In this example it is the current directory but it can be configured to anything that the jvm has access to. - resource_handler.setDirectoriesListed(true); - resource_handler.setWelcomeFiles(new String[]{ "index.html" }); - resource_handler.setResourceBase(HostProperties.Instance().getHostGUIDir()); + server = new Server(port); + List resourceHandlers = new ArrayList(); + for (Skin skin : HostProperties.Instance().getSkins()) { + if (skin.isActive()) { + ResourceHandler resource_handler = new ResourceHandler(); + resource_handler.setDirectoriesListed(true); + resource_handler.setWelcomeFiles(skin.getWelcomeFiles()); + resource_handler.setResourceBase(skin.getResourceBase()); + resourceHandlers.add(resource_handler); + } + } //Angular is pretty messed up when it comes to link rewriting: https://github.com/angular/angular.js/issues/4608 //I can't believe we need to server side changes to fix this!!! https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode @@ -145,34 +142,50 @@ public static void main(String[] args) throws Exception { // Add the ResourceHandler to the server. HandlerList handlers = new HandlerList(); - handlers.setHandlers(new Handler[] { videoContext, serviceContext, resource_handler, rewritePagesHandler, new DefaultHandler() }); + Handler[] allHandlers = new Handler[4 + resourceHandlers.size()]; + allHandlers[0] = videoContext; + allHandlers[1] = serviceContext; + System.arraycopy(resourceHandlers.toArray(), 0, allHandlers, 2, resourceHandlers.size()); + allHandlers[2 + resourceHandlers.size()] = rewritePagesHandler; + allHandlers[3 + resourceHandlers.size()] = new DefaultHandler(); + handlers.setHandlers(allHandlers); server.setHandler(handlers); String externallyAccessableIP = HostProperties.Instance().getExternallyAccessableName(); if (externallyAccessableIP == null) { - //Don't do this: 127.0.0.1 on Linux! + //Don't do this below, it turns out to be 127.0.0.1 on Linux! //getSetup().externallyAccessableIP = InetAddress.getLocalHost().getHostAddress(); - Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); - while (interfaces.hasMoreElements()) { - NetworkInterface currentInterface = interfaces.nextElement(); - if (currentInterface.isLoopback() || currentInterface.isVirtual()) { - continue; - } - - Enumeration addresses = currentInterface.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement(); - if (address.getAddress().length == 4) {//Make sure it's ipv4 - externallyAccessableIP = address.getHostAddress(); + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface currentInterface = interfaces.nextElement(); + if (currentInterface.isLoopback() || currentInterface.isVirtual()) { + continue; + } + + Enumeration addresses = currentInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (address.getAddress().length == 4) {//Make sure it's ipv4 + externallyAccessableIP = address.getHostAddress(); + } } } + } catch (SocketException e) { + logger.error("Couldn't enumerate network interfaces, which means there isn't a network available. This is fatal!", e); + System.exit(1); } } //Determine if we are going to use SSL if (HostProperties.Instance().isUseSSL()) { - JettySecurityUtils.secureContext(externallyAccessableIP, serviceContext, server); + try { + JettySecurityUtils.secureContext(externallyAccessableIP, serviceContext, server); + } catch (InvalidNameException | IOException | GeneralSecurityException e) { + logger.error("Couldn't setup a secure HTTP context. This is fatal!", e); + System.exit(2); + } } //Determine if we are going to use user authentication @@ -180,9 +193,18 @@ public static void main(String[] args) throws Exception { setupAuthentication(serviceContext, FeatureManager.getUserManagementFeature()); } - URI startURI = new URI("http" + (HostProperties.Instance().isUseSSL()?"s://":"://") + externallyAccessableIP + ":" + port); - ServerContainer container = WebSocketServerContainerInitializer.configureContext(serviceContext); - NotificationManager.start(startURI, container); + URI startURI = null; + try { + startURI = new URI("http" + (HostProperties.Instance().isUseSSL()?"s://":"://") + externallyAccessableIP + ":" + port); + ServerContainer container = WebSocketServerContainerInitializer.configureContext(serviceContext); + NotificationManager.start(startURI, container); + } catch (URISyntaxException e) { + logger.error("The IP address(possibly setup in your config.properties) called:" + externallyAccessableIP + " is malformed!", e); + System.exit(3); + } catch (ServletException e) { + logger.error("Couldn't initialize the servlet engine. This is fatal!", e); + System.exit(4); + } //Start server before we start broadcasting! try { @@ -197,24 +219,7 @@ public static void main(String[] args) throws Exception { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { - try { - FeatureManager.shutdown(); - } catch (Exception e) { - logger.error("Error shutting down BroadcastManager", e); - } - try { - NotificationManager.shutdown(); - } catch (Exception e) { - logger.error("Error shutting down NotificationManager", e); - } - try { - server.stop(); - } catch (Exception e) { - logger.error("Error stopping Photonic3D http server", e); - } finally { - server.destroy(); - logger.info("Shutdown Complete"); - } + Main.stopServer(); } }); @@ -228,6 +233,45 @@ public void run() { //At this point we can safely say that a startup is officially complete. HostProperties.Instance().hostStartupComplete(); + } + + public static void stopServer() { + try { + FeatureManager.shutdown(); + } catch (Exception e) { + logger.error("Error shutting down BroadcastManager", e); + } + try { + NotificationManager.shutdown(); + } catch (Exception e) { + logger.error("Error shutting down NotificationManager", e); + } + try { + server.stop(); + } catch (Exception e) { + logger.error("Error stopping Photonic3D http server", e); + } finally { + server.destroy(); + logger.info("================================================================="); + logger.info("================================================================="); + logger.info("Shutdown Complete"); + logger.info("================================================================="); + logger.info("================================================================="); + } + } + + public static void restartServer() { + stopServer(); + logger.info("================================================================="); + logger.info("================================================================="); + logger.info("Photonic3D will now restart"); + logger.info("================================================================="); + logger.info("================================================================="); + startServer(); + } + + public static void main(String[] args) throws Exception { + startServer(); //Wait in the Main method until we are shutdown by the OS try { diff --git a/host/src/main/java/org/area515/resinprinter/server/Skin.java b/host/src/main/java/org/area515/resinprinter/server/Skin.java new file mode 100644 index 000000000..48f3fdb5b --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/server/Skin.java @@ -0,0 +1,33 @@ +package org.area515.resinprinter.server; + +public class Skin { + private String name; + private String[] welcomeFiles; + private String resourceBase; + private boolean active; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String[] getWelcomeFiles() { + return welcomeFiles; + } + public void setWelcomeFiles(String[] welcomeFiles) { + this.welcomeFiles = welcomeFiles; + } + public String getResourceBase() { + return resourceBase; + } + public void setResourceBase(String resourceBase) { + this.resourceBase = resourceBase; + } + public boolean isActive() { + return active; + } + public void setActive(boolean active) { + this.active = active; + } +} diff --git a/host/src/main/java/org/area515/resinprinter/services/CustomizerService.java b/host/src/main/java/org/area515/resinprinter/services/CustomizerService.java index edcee30d0..b89d0db0e 100644 --- a/host/src/main/java/org/area515/resinprinter/services/CustomizerService.java +++ b/host/src/main/java/org/area515/resinprinter/services/CustomizerService.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import javax.annotation.security.RolesAllowed; import javax.imageio.ImageIO; @@ -30,16 +32,23 @@ import org.area515.resinprinter.exception.NoPrinterFoundException; import org.area515.resinprinter.exception.SliceHandlingException; import org.area515.resinprinter.job.AbstractPrintFileProcessor; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.Customizer; +import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.Previewable; import org.area515.resinprinter.job.PrintFileProcessor; +import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.printer.Printer; import org.area515.resinprinter.server.HostProperties; +import org.area515.resinprinter.server.Main; import org.area515.resinprinter.util.security.PhotonicUser; import org.area515.util.PrintFileFilter; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; @Api(value="customizers") @RolesAllowed(PhotonicUser.FULL_RIGHTS) @@ -47,8 +56,63 @@ public class CustomizerService { private static final Logger logger = LogManager.getLogger(); public static CustomizerService INSTANCE = new CustomizerService(); + + //TODO: Should this be a cache? Aren't these really small objects now without the original BufferedImage embedded in them? private Cache customizersByName = CacheBuilder.newBuilder().softValues().build(); + /* + * This cache is only for previewing purposes: + * 1. It allows the web to store BufferedImage cache + * 2. It also allows us to use a more realistic way of previewing images. + * 3. We don't have to create a DataAid for every preview of an image now... + */ + private LoadingCache dataAidsByCustomizer = CacheBuilder.newBuilder().softValues().build(new CacheLoader() { + public DataAid load(Customizer customizer) throws JobManagerException, NoPrinterFoundException, InappropriateDeviceException { + //find the first activePrinter + Printer activePrinter = null; + String printerName = customizer.getPrinterName(); + if (printerName == null || printerName.isEmpty()) { + //if customizer doesn't have a printer stored, set first active printer as printer + try { + activePrinter = PrinterService.INSTANCE.getFirstAvailablePrinter(); + } catch (NoPrinterFoundException e) { + throw new NoPrinterFoundException("No printers found for slice preview. You must have a started printer or specify a valid printer in the Customizer."); + } + + } else { + try { + activePrinter = PrinterService.INSTANCE.getPrinter(printerName); + } catch (InappropriateDeviceException e) { + logger.warn("Could not locate printer {}", printerName, e); + } + } + + File file = new File(HostProperties.Instance().getUploadDir(), customizer.getPrintableName() + "." + customizer.getPrintableExtension()); + PrintFileProcessor processor = PrintFileFilter.INSTANCE.findAssociatedPrintProcessor(file); + if (!(processor instanceof Previewable)) { + if (processor == null) { + throw new IllegalArgumentException("Couldn't find file processor for file:" + file); + } + + throw new IllegalArgumentException(processor.getFriendlyName() + " files don't support image preview."); + } + + if (!(processor instanceof AbstractPrintFileProcessor)) { + throw new IllegalArgumentException("Processor:" + processor.getFriendlyName() + " needs to extend AbstractPrintFileProcessor"); + } + + //instantiate a new print job based on the jobFile and set its printer to activePrinter + PrintJob printJob = new PrintJob(new File(HostProperties.Instance().getUploadDir(), customizer.getPrintableName() + "." + customizer.getPrintableExtension())); + printJob.setPrinter(activePrinter); + printJob.setCustomizer(customizer); + printJob.setPrintFileProcessor(processor); + //printJob.setCurrentSlice(customizer.getNextSlice()); No need to do this since it just sets itself + + return ((AbstractPrintFileProcessor)processor).initializeJobCacheWithDataAid(printJob); + } + }); + + //TODO: Some day this needs to load from disk. Otherwise we will overwrite customizers on normal prints @ApiOperation(value="Retrieves all Customizers") @GET @Path("list") @@ -64,16 +128,40 @@ public Map getCustomizers() { @GET @Path("get/{customizerName}") @Produces(MediaType.APPLICATION_JSON) - public Customizer getCustomizer(@PathParam("customizerName")String customizerName, @QueryParam("externalState") String externalState) { - Customizer customizer = customizersByName.getIfPresent(customizerName); - if (customizer != null) { - if (customizer.getExternalImageAffectingState() == null || !customizer.getExternalImageAffectingState().equals(externalState)) { - customizer.setOrigSliceCache(null); - //TODO: start building image in a background task before the client asks for it! + public Customizer getCustomizer(final @PathParam("customizerName")String customizerName, @QueryParam("externalState") String externalState) { + Customizer customizer; + try { + customizer = customizersByName.get(customizerName, + new Callable() { + @Override + public Customizer call() throws Exception { + Customizer loadedCustomizer = HostProperties.Instance().loadCustomizer(customizerName); + if (loadedCustomizer == null) { + throw new IllegalArgumentException("Couldn't find customizer"); + } + + return loadedCustomizer; + } + }); + } catch (UncheckedExecutionException e) { + if (e.getCause() instanceof IllegalArgumentException) { + logger.info("Couldn't find customizer", customizerName);//Probably normal + return null; } - customizer.setExternalImageAffectingState(externalState); + + logger.error("Couldn't load customizer(unchecked):" + customizerName, e); + return null; + } catch (ExecutionException e) { + logger.error("Couldn't load customizer:" + customizerName, e); + return null; } + //our cache loader ensures that customizer will be set at this position + if (customizer.getExternalImageAffectingState() == null || !customizer.getExternalImageAffectingState().equals(externalState)) { + dataAidsByCustomizer.invalidate(customizer); + //TODO: start building image in a background task before the client asks for it! + } + customizer.setExternalImageAffectingState(externalState); return customizer; } @@ -81,18 +169,39 @@ public Customizer getCustomizer(@PathParam("customizerName")String customizerNam @POST @Path("upsert") @Produces(MediaType.APPLICATION_JSON) - public Customizer addOrUpdateCustomizer(Customizer customizer) { + public Customizer addOrUpdateCustomizer(final Customizer customizer) { Customizer oldCustomizer = customizersByName.getIfPresent(customizer.getName()); if (oldCustomizer != null) { //If the image was affected by some external state other than the customizer(e.g. calibration), trash the cache. if (customizer.getExternalImageAffectingState() != null && customizer.getExternalImageAffectingState().equals(oldCustomizer.getExternalImageAffectingState())) { - customizer.setOrigSliceCache(oldCustomizer.getOrigSliceCache()); + try { + DataAid oldAid = dataAidsByCustomizer.get(oldCustomizer); + dataAidsByCustomizer.put(customizer, oldAid); + oldAid.customizer = customizer; + oldAid.clearAffineTransformCache(); + + //TODO: do we have to do this? Shouldn't this be done naturally through the soft references? This needs to be tested! + dataAidsByCustomizer.invalidate(oldCustomizer); + } catch (ExecutionException e) { + logger.error("Couldn't create dataAid:", e); + } } //else TODO: start building image in a background task before the client asks for it! } customizersByName.put(customizer.getName(), customizer); + //Don't wait for us to save the file to disk + Main.GLOBAL_EXECUTOR.submit(new Runnable(){ + @Override + public void run() { + try { + HostProperties.Instance().saveCustomizer(customizer); + } catch (InappropriateDeviceException e) { + logger.error("Couldn't save customizer:" + customizer, e); + } + } + }); return customizer; } @@ -129,16 +238,15 @@ public void projectImage(@PathParam("customizerName") String customizerName) thr throw new IllegalArgumentException("Printer can't preview while print is active!"); } - File file = new File(HostProperties.Instance().getUploadDir(), customizer.getPrintableName() + "." + customizer.getPrintableExtension()); - PrintFileProcessor processor = PrintFileFilter.INSTANCE.findAssociatedPrintProcessor(file); - if (!(processor instanceof Previewable)) { - throw new IllegalArgumentException(processor.getFriendlyName() + " files don't support image preview."); + try { + DataAid aid = dataAidsByCustomizer.get(customizer); + AbstractPrintFileProcessor previewableProcessor = (AbstractPrintFileProcessor)aid.printJob.getPrintFileProcessor(); + BufferedImage img = previewableProcessor.buildPreviewSlice(customizer, dataAidsByCustomizer.get(customizer)); + printer.setStatus(printer.getStatus());//This is to make sure the slicenumber is reset. + printer.showImage(img, true); + } catch (ExecutionException e) { + throw new IllegalArgumentException("Couldn't build data aid", e); } - - AbstractPrintFileProcessor previewableProcessor = (AbstractPrintFileProcessor) processor; - BufferedImage img = previewableProcessor.buildPreviewSlice(customizer, file, (Previewable)processor); - printer.setStatus(printer.getStatus());//This is to make sure the slicenumber is reset. - printer.showImage(img); } @ApiOperation(value="Renders a slice of a printable based on the customizer") @@ -153,20 +261,11 @@ public StreamingOutput renderFirstSliceImage(@PathParam("customizerName") String if (customizer == null) { throw new IllegalArgumentException("Customizer is missing for:" + customizerName); } - - File file = new File(HostProperties.Instance().getUploadDir(), customizer.getPrintableName() + "." + customizer.getPrintableExtension()); - PrintFileProcessor processor = PrintFileFilter.INSTANCE.findAssociatedPrintProcessor(file); - if (!(processor instanceof Previewable)) { - if (processor == null) { - throw new IllegalArgumentException("Couldn't find file processor for file:" + file); - } - - throw new IllegalArgumentException(processor.getFriendlyName() + " files don't support image preview."); - } - - AbstractPrintFileProcessor previewableProcessor = (AbstractPrintFileProcessor) processor; + try { - BufferedImage img = previewableProcessor.buildPreviewSlice(customizer, file, (Previewable)processor); + DataAid aid = dataAidsByCustomizer.get(customizer); + AbstractPrintFileProcessor previewableProcessor = (AbstractPrintFileProcessor)aid.printJob.getPrintFileProcessor(); + BufferedImage img = previewableProcessor.buildPreviewSlice(customizer, aid); logger.debug("just got the bufferedimg from previewSlice"); @@ -183,6 +282,8 @@ public void write(OutputStream output) throws IOException, WebApplicationExcepti } }; return stream; + } catch (ExecutionException e) { + throw new SliceHandlingException(e.getCause()); } catch (NoPrinterFoundException|SliceHandlingException |IllegalArgumentException e) { throw e; } diff --git a/host/src/main/java/org/area515/resinprinter/services/MachineService.java b/host/src/main/java/org/area515/resinprinter/services/MachineService.java index 3b093a06f..dc9833870 100644 --- a/host/src/main/java/org/area515/resinprinter/services/MachineService.java +++ b/host/src/main/java/org/area515/resinprinter/services/MachineService.java @@ -215,6 +215,18 @@ public Boolean call() throws Exception { } } + @ApiOperation(value="Restarts the Photonic3D print Host software, but now the operating system itself. " + + "This is helpful for when the the HostSettings need to be reloaded from disk.") + @ApiResponses(value = { + @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), + @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) + @GET + @Path("restartPhotonic") + @Produces(MediaType.APPLICATION_JSON) + public void restartPhotonicServer() { + Main.restartServer(); + } + //TODO: getWirelessStrength @ApiOperation(value="Retrieves all of the supported file types that are returned from the each of the org.area515.resinprinter.job.PrintFileProcessor.getFileExtensions()." + SwaggerMetadata.PRINT_FILE_PROCESSOR) @@ -495,12 +507,17 @@ public void saveMachineConfiguration(MachineConfig machineConfig) throws JAXBExc @ApiOperation(value = "Deletes a machine configuration(by name) from the machine config directory of Photonic 3D host.") @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), + @ApiResponse(code = 404, message = SwaggerMetadata.RESOURCE_NOT_FOUND), @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) @DELETE @Path("machineConfigurations/{machineConfigurationName}") - public void deleteMachineConfiguration(@PathParam("machineConfigurationName") String machineConfig) throws JAXBException { + public Response deleteMachineConfiguration(@PathParam("machineConfigurationName") String machineConfig) throws JAXBException { File machineFile = new File(HostProperties.Instance().MACHINE_DIR, machineConfig + HostProperties.MACHINE_EXTENSION); - machineFile.delete(); + if (!machineFile.delete()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok().build(); } @@ -533,11 +550,16 @@ public void saveSlicingProfile(SlicingProfile slicingProfile) throws JAXBExcepti @ApiOperation(value = "Deletes a slicing profile from the slicing profile directory of Photonic 3D host.") @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), + @ApiResponse(code = 404, message = SwaggerMetadata.RESOURCE_NOT_FOUND), @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) @DELETE @Path("slicingProfiles/{slicingProfileName}") - public void deleteSlicingProfile(@PathParam("slicingProfileName") String profile) throws JAXBException { + public Response deleteSlicingProfile(@PathParam("slicingProfileName") String profile) throws JAXBException { File profileFile = new File(HostProperties.Instance().PROFILES_DIR, profile + HostProperties.PROFILES_EXTENSION); - profileFile.delete(); + if (!profileFile.delete()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return Response.ok().build(); } } diff --git a/host/src/main/java/org/area515/resinprinter/services/MediaService.java b/host/src/main/java/org/area515/resinprinter/services/MediaService.java index 527e60571..630ff85cf 100644 --- a/host/src/main/java/org/area515/resinprinter/services/MediaService.java +++ b/host/src/main/java/org/area515/resinprinter/services/MediaService.java @@ -1,5 +1,10 @@ package org.area515.resinprinter.services; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -36,7 +41,7 @@ import org.area515.resinprinter.server.HostProperties; import org.area515.resinprinter.server.Main; import org.area515.resinprinter.util.security.PhotonicUser; -import org.area515.util.Log4jTimer; +import org.area515.util.Log4jUtil; import com.coremedia.iso.boxes.Container; import com.google.common.io.ByteStreams; @@ -45,11 +50,6 @@ import com.googlecode.mp4parser.authoring.builder.FragmentedMp4Builder; import com.googlecode.mp4parser.authoring.tracks.H264TrackImpl; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; - @Api(value="media") @RolesAllowed(PhotonicUser.FULL_RIGHTS) @Path("media") @@ -104,7 +104,7 @@ public byte[] call() throws Exception { ByteArrayOutputStream stream = new ByteArrayOutputStream(); taker.write(stream); return stream.toByteArray(); - } catch (IOException e) { + } catch (IOException | WebApplicationException e) { logger.error("Problem occurred while taking snapshot (recovering)", e); throw e; } finally { @@ -134,7 +134,7 @@ public ImageSnapshotCapture(int x, int y) { @Override public void write(OutputStream output) throws IOException, WebApplicationException { - logger.debug("Image snapshot start", ()->Log4jTimer.startTimer("PictureTimer")); + logger.debug("Image snapshot start", ()->Log4jUtil.startTimer("PictureTimer")); String[] streamingCommand = HostProperties.Instance().getImagingCommand(); String[] replacedCommands = new String[streamingCommand.length]; for (int t = 0; t < streamingCommand.length; t++) { @@ -143,10 +143,11 @@ public void write(OutputStream output) throws IOException, WebApplicationExcepti InputStream inputStream = null; processLock.lock(); + Process imagingProcess = null; try { - Process imagingProcess = Runtime.getRuntime().exec(replacedCommands); + imagingProcess = Runtime.getRuntime().exec(replacedCommands); ByteStreams.copy(imagingProcess.getInputStream(), output); - logger.debug("Image snapshot complete {}ms", ()-> Log4jTimer.completeTimer("PictureTimer")); + logger.debug("Image snapshot complete {}ms", ()-> Log4jUtil.completeTimer("PictureTimer")); } finally { if (inputStream != null) { try { @@ -160,6 +161,13 @@ public void write(OutputStream output) throws IOException, WebApplicationExcepti } catch (IOException e) { } } + if (imagingProcess != null && imagingProcess.exitValue() != 0) { + ByteArrayOutputStream errorOutput = new ByteArrayOutputStream(); + ByteStreams.copy(imagingProcess.getErrorStream(), errorOutput); + String error = new String(errorOutput.toByteArray()); + logger.error(error); + throw new WebApplicationException(error, 400); + } processLock.unlock(); } } @@ -182,7 +190,7 @@ public ClientStream(String clientId) { public void write(OutputStream outputStream) throws IOException, WebApplicationException { while (true) {//Stream forever until they tell us to quit. try { - logger.debug("Client asking to stream", ()->Log4jTimer.startTimer("ClientStreamTimer")); + logger.debug("Client asking to stream", ()->Log4jUtil.startTimer("ClientStreamTimer")); byte[] imageData = nextLiveStreamImage.get(); Future run = liveStreamingThrottlingService.submit(new Callable() { @@ -197,9 +205,9 @@ public Object call() throws IOException { return null; } }); - logger.debug("Client waiting to stream image {}ms", ()->Log4jTimer.splitTimer("ClientStreamTimer")); + logger.debug("Client waiting to stream image {}ms", ()->Log4jUtil.splitTimer("ClientStreamTimer")); run.get();//It may seem strange to setup a future and then run get, but this limits the concurrency level in the event we have a crazy amount of clients - logger.debug("Client streamed image {}ms", ()-> Log4jTimer.completeTimer("ClientStreamTimer")); + logger.debug("Client streamed image {}ms", ()-> Log4jUtil.completeTimer("ClientStreamTimer")); //We've been asked to close nicely from the browser instead of the user just closing the page. if (closeNow != null) { diff --git a/host/src/main/java/org/area515/resinprinter/services/Photonic3dSTLErrorSerializer.java b/host/src/main/java/org/area515/resinprinter/services/Photonic3dSTLErrorSerializer.java index 71e1f5444..2ae8625b0 100644 --- a/host/src/main/java/org/area515/resinprinter/services/Photonic3dSTLErrorSerializer.java +++ b/host/src/main/java/org/area515/resinprinter/services/Photonic3dSTLErrorSerializer.java @@ -4,6 +4,8 @@ import org.area515.resinprinter.slice.StlError; import org.area515.resinprinter.slice.StlError.ErrorType; +import org.area515.resinprinter.stl.MultiTriangleFace; +import org.area515.resinprinter.stl.Triangle3d; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; @@ -14,9 +16,17 @@ public class Photonic3dSTLErrorSerializer extends JsonSerializer { @Override public void serialize(StlError value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { if (value.getType() == ErrorType.NonManifold) { - jgen.writeStartObject(); - jgen.writeNumberField("i", value.getTriangle().getOriginalIndex()); - jgen.writeEndObject(); + if (value.getFace() instanceof Triangle3d) { + jgen.writeStartObject(); + jgen.writeNumberField("i", ((Triangle3d)value.getFace()).getOriginalIndex()); + jgen.writeEndObject(); + } else if (value.getFace() instanceof MultiTriangleFace) { + for (Triangle3d tri : ((MultiTriangleFace)value.getFace()).getFaces()) { + jgen.writeStartObject(); + jgen.writeNumberField("i", tri.getOriginalIndex()); + jgen.writeEndObject(); + } + } } } } diff --git a/host/src/main/java/org/area515/resinprinter/services/PrintableService.java b/host/src/main/java/org/area515/resinprinter/services/PrintableService.java index 63b265490..62a165969 100644 --- a/host/src/main/java/org/area515/resinprinter/services/PrintableService.java +++ b/host/src/main/java/org/area515/resinprinter/services/PrintableService.java @@ -198,7 +198,7 @@ public Response uploadPrintableFile(InputStream istream, @PathParam("filename")S @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), @ApiResponse(code = 400, message = SwaggerMetadata.USER_UNDERSTANDABLE_ERROR), @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) - @POST + @GET @Path("/downloadPrintableFile/{filename}") @Produces("application/octet-stream") public StreamingOutput downloadPrintableFile(@PathParam("filename")String fileName) { diff --git a/host/src/main/java/org/area515/resinprinter/services/PrinterService.java b/host/src/main/java/org/area515/resinprinter/services/PrinterService.java index 143c11da4..272cd1211 100644 --- a/host/src/main/java/org/area515/resinprinter/services/PrinterService.java +++ b/host/src/main/java/org/area515/resinprinter/services/PrinterService.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,6 +44,7 @@ import org.area515.resinprinter.job.Customizer; import org.area515.resinprinter.job.InkDetector; import org.area515.resinprinter.job.JobManagerException; +import org.area515.resinprinter.job.JobStatus; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.job.PrintJobManager; import org.area515.resinprinter.job.render.StubPrintFileProcessor; @@ -544,7 +546,8 @@ public MachineResponse calibrate(@PathParam("printername") String printerName, @ GraphicsOutputInterface device = null; if (printer.isStarted()) { device = DisplayManager.Instance().getDisplayDevice(printer.getDisplayDeviceID()); - } else { + } + if (!printer.isStarted() || device == null) { device = DisplayManager.Instance().getDisplayDevice(currentConfiguration.getMachineConfig().getOSMonitorID()); } currentConfiguration.getSlicingProfile().setxResolution(device.getBoundary().width); @@ -877,6 +880,7 @@ private static PrintJob buildStubJob(Printer printer) { StubPrintFileProcessor processor = new StubPrintFileProcessor<>(); job.setPrintFileProcessor(processor); job.setPrinter(printer); + job.initializePrintJob(CompletableFuture.completedFuture(JobStatus.Ready)); return job; } /*Fix the two places where we assign icons to all of the image types in javascript diff --git a/host/src/main/java/org/area515/resinprinter/services/RemoteService.java b/host/src/main/java/org/area515/resinprinter/services/RemoteService.java index a6ec6a8d8..30f0db47a 100644 --- a/host/src/main/java/org/area515/resinprinter/services/RemoteService.java +++ b/host/src/main/java/org/area515/resinprinter/services/RemoteService.java @@ -74,8 +74,8 @@ public class RemoteService { + "
The remote user must have been given the login & userAdmin roles.
" + "
The local user must send a friend request using the \"" + PhotonicCrypto.FEATURE_NAME + "\" feature to the remote user.
" + "
The remote user must accept the friend request from the local user.
" - + "
The remote user must must give the login role to their new friend(the local user).
" - + "
The remote user must must give any other role to their new friend(the local user) that coresponds with the restful function they would like to grant access.
" + + "
The remote user must give the login role to their new friend(the local user).
" + + "
The remote user must give any other role to their new friend(the local user) that coresponds with the restful function they would like to grant access.
" + "
The local user must be logged in to their own instance of Photonic 3D to sign and encrypt the message(this restful function).
" + "
The remote user must be logged in to their own instance of Photonic 3D in order to verify and decrypt all incoming messages(restful functions) from the local user.
" + ""; diff --git a/host/src/main/java/org/area515/resinprinter/services/SettingsService.java b/host/src/main/java/org/area515/resinprinter/services/SettingsService.java index 047911fac..592fd90df 100644 --- a/host/src/main/java/org/area515/resinprinter/services/SettingsService.java +++ b/host/src/main/java/org/area515/resinprinter/services/SettingsService.java @@ -1,5 +1,10 @@ package org.area515.resinprinter.services; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; + import java.util.List; import javax.annotation.security.RolesAllowed; @@ -9,19 +14,14 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import com.fasterxml.jackson.core.io.JsonStringEncoder; import org.area515.resinprinter.server.CwhEmailSettings; import org.area515.resinprinter.server.HostInformation; import org.area515.resinprinter.server.HostProperties; +import org.area515.resinprinter.server.Skin; import org.area515.resinprinter.util.security.PhotonicUser; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import com.fasterxml.jackson.core.io.JsonStringEncoder; @Api(value="settings") @RolesAllowed(PhotonicUser.FULL_RIGHTS) @@ -121,4 +121,14 @@ public HostInformation getHostInformation() { public void setHostInformation(HostInformation info) { HostProperties.Instance().saveHostInformation(info); } + + @ApiOperation(value="This method returns all of the GUI skins available on the machine") + @ApiResponses(value = { + @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), + @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) + @PUT + @Path("skins/list") + public List getSkins() { + return HostProperties.Instance().getSkins(); + } } \ No newline at end of file diff --git a/host/src/main/java/org/area515/resinprinter/services/SwaggerMetadata.java b/host/src/main/java/org/area515/resinprinter/services/SwaggerMetadata.java index fee92d96d..3bc624b22 100644 --- a/host/src/main/java/org/area515/resinprinter/services/SwaggerMetadata.java +++ b/host/src/main/java/org/area515/resinprinter/services/SwaggerMetadata.java @@ -46,5 +46,5 @@ public class SwaggerMetadata { public static final String NOTIFICATION_MANAGER_SUFFIX = ", a notification will be sent to the NotificationManager. The NotificationManager will then notify all registered implementations of org.area515.resinprinter.notification.Notifier. Implemenations of Notifier are currently Email and WebSockets."; public static final String DIAGNOSTIC_DUMP_PREFIX = "Perform a diagnostic dump that contains system properties, log4j properties, startup logs, cwh logs, configuration properties, stacktrace, xml representation of printers and photonic3d log"; public static final String PRINT_FILE_PROCESSOR = "PrintFileProcessors are registered in the config.properties under the notify.[classname.of.PrintFileProcessor.implementation]=true"; - + public static final String RESOURCE_NOT_FOUND = "The reqest was unsuccessful because the resource was not found"; } diff --git a/host/src/main/java/org/area515/resinprinter/services/UserService.java b/host/src/main/java/org/area515/resinprinter/services/UserService.java index 8f46803f8..e88bbe2bf 100644 --- a/host/src/main/java/org/area515/resinprinter/services/UserService.java +++ b/host/src/main/java/org/area515/resinprinter/services/UserService.java @@ -18,6 +18,7 @@ import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -43,15 +44,31 @@ public class UserService { public static UserService INSTANCE = new UserService(); private ConcurrentHashMap> transientMessages = new ConcurrentHashMap<>(); - public class Message { + public static class Message { private PhotonicUser fromUser; private PhotonicUser toUser; + private UUID id; private String message; - public Message(PhotonicUser fromUser, PhotonicUser toUser, String message) { + private Message() { + } + + private Message(UUID id) { + this.id = id; + } + + public Message(PhotonicUser fromUser, PhotonicUser toUser, UUID id, String message) { this.fromUser = fromUser; this.toUser = toUser; this.message = message; + this.id = id; + } + + public UUID getId() { + return id; + } + public void setId(UUID id) { + this.id = id; } public PhotonicUser getFromUser() { @@ -79,10 +96,7 @@ public void setMessage(String message) { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + getOuterType().hashCode(); - result = prime * result + ((fromUser == null) ? 0 : fromUser.hashCode()); - result = prime * result + ((message == null) ? 0 : message.hashCode()); - result = prime * result + ((toUser == null) ? 0 : toUser.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @@ -95,29 +109,13 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; Message other = (Message) obj; - if (!getOuterType().equals(other.getOuterType())) - return false; - if (fromUser == null) { - if (other.fromUser != null) - return false; - } else if (!fromUser.equals(other.fromUser)) - return false; - if (message == null) { - if (other.message != null) + if (id == null) { + if (other.id != null) return false; - } else if (!message.equals(other.message)) - return false; - if (toUser == null) { - if (other.toUser != null) - return false; - } else if (!toUser.equals(other.toUser)) + } else if (!id.equals(other.id)) return false; return true; } - - private UserService getOuterType() { - return UserService.this; - } } @ApiOperation(value = "Gets all local users and trusted remote users(friends) of this Photonic3d installation.") @@ -131,6 +129,22 @@ public Set getKnownUsers() { return FeatureManager.getUserManagementFeature().getUsers(); } + @ApiOperation(value = "Returns the user that you are logged in as.") + @ApiResponses(value = { + @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), + @ApiResponse(code = 500, message = SwaggerMetadata.UNEXPECTED_ERROR)}) + @GET + @Path("users/whoAmI") + @Produces(MediaType.APPLICATION_JSON) + public PhotonicUser whoAmI(@Context HttpServletRequest request) throws UserManagementException { + PhotonicUser user = (PhotonicUser)request.getUserPrincipal(); + if (user == null) { + throw new UserManagementException("You have to be logged in to know who you are."); + } + + return user; + } + @ApiOperation(value = "Creates a new local Photonic 3d user.") @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), @@ -166,6 +180,7 @@ public Response deleteUser(@PathParam("userId")String userId) throws UserManagem return Response.status(Status.OK).build(); } + @ApiOperation(value = "Trusts a new remote Photonic 3d user as a new friend. " + "A 'Friend' in Photonic3d is nothing more than a remote user that has been given rights to perform actions on your printer.") @ApiResponses(value = { @@ -233,22 +248,28 @@ public List getPotentialFriends() throws UserManagementException { @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), @ApiResponse(code = 400, message = SwaggerMetadata.USER_UNDERSTANDABLE_ERROR)}) - @GET - @Path("messages/create/{toUserId}") + @PUT + @Path("messages") @Consumes(MediaType.APPLICATION_JSON) - public void createMessage( - String messageString, - @PathParam("toUserId") - String toUserId, + public Message createMessage( + Message message, @Context HttpServletRequest request) throws UserManagementException { PhotonicUser fromUser = (PhotonicUser)request.getUserPrincipal(); if (fromUser == null) { throw new UserManagementException("You have to be logged in to chat."); } - PhotonicUser toUser = FeatureManager.getUserManagementFeature().getUser(UUID.fromString(toUserId)); + if (message.getToUser() == null) { + throw new UserManagementException("You must specifiy a user to send the message to."); + } + + if (message.getToUser().getUserId() == null) { + throw new UserManagementException("You must specifiy a userId to send the message to."); + } + + PhotonicUser toUser = FeatureManager.getUserManagementFeature().getUser(message.getToUser().getUserId()); if (toUser.isRemote()) { - throw new UserManagementException("To send this message to a remote user, append 'services/remote/execute/" + toUserId + "/' to the front of your restful request."); + throw new UserManagementException("To send this message to a remote user, append 'services/remote/execute/" + message.getToUser().getUserId() + "/' to the front of your restful request."); } List messages = new ArrayList(); @@ -257,41 +278,43 @@ public void createMessage( messages = oldMessages; } - Message message = new Message(fromUser, toUser, messageString); - messages.add(message); + Message newMessage = new Message(fromUser, toUser, UUID.randomUUID(), message.getMessage()); + messages.add(newMessage); + return newMessage; } @RolesAllowed({PhotonicUser.FULL_RIGHTS, PhotonicUser.USER_ADMIN, PhotonicUser.CHAT}) - @ApiOperation(value = "Removes a P2P message from the transient message store, making sure the caller was the original sender.") + @ApiOperation(value = "Removes a P2P message from the transient message store, making sure the caller was the recipiant of the message.") @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), @ApiResponse(code = 400, message = SwaggerMetadata.USER_UNDERSTANDABLE_ERROR)}) - @GET - @Path("messages/remove") + @DELETE + @Path("messages/{messageId}") @Consumes(MediaType.APPLICATION_JSON) - public void removeMessage( - Message messageToRemove, + public Response removeMessage( + @PathParam("messageId") + UUID messageId, @Context HttpServletRequest request) throws UserManagementException { - PhotonicUser fromUser = (PhotonicUser)request.getUserPrincipal(); - if (fromUser == null) { + PhotonicUser callerUser = (PhotonicUser)request.getUserPrincipal(); + if (callerUser == null) { throw new UserManagementException("You have to be logged in to chat."); } - if (!fromUser.equals(messageToRemove.getFromUser())) { - throw new UserManagementException("You can't remove a message that wasn't from you"); + List messages = transientMessages.get(callerUser.getUserId()); + if (messages == null) { + return Response.status(Response.Status.NOT_FOUND).build(); } - List messages = transientMessages.get(messageToRemove.getToUser().getUserId()); - if (messages == null) { - return; + if (!messages.remove(new Message(messageId))) { + return Response.status(Response.Status.NOT_FOUND).build(); } - messages.remove(messageToRemove); + return Response.ok().build(); } @RolesAllowed({PhotonicUser.FULL_RIGHTS, PhotonicUser.USER_ADMIN, PhotonicUser.CHAT}) - @ApiOperation(value = "Removes a P2P message from the transient message store, making sure the caller was the original sender.") + @ApiOperation(value = "Retrieves all P2P message from the transient message store that belong to the requesting user.") @ApiResponses(value = { @ApiResponse(code = 200, message = SwaggerMetadata.SUCCESS), @ApiResponse(code = 400, message = SwaggerMetadata.USER_UNDERSTANDABLE_ERROR)}) @@ -299,7 +322,6 @@ public void removeMessage( @Path("messages/list") @Consumes(MediaType.APPLICATION_JSON) public List getMessages(@Context HttpServletRequest request) throws UserManagementException { - PhotonicUser user = (PhotonicUser)request.getUserPrincipal(); if (user == null) { throw new UserManagementException("You have to be logged in to chat."); diff --git a/host/src/main/java/org/area515/resinprinter/slice/CloseOffMend.java b/host/src/main/java/org/area515/resinprinter/slice/CloseOffMend.java index 4f5fb2356..def6877ef 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/CloseOffMend.java +++ b/host/src/main/java/org/area515/resinprinter/slice/CloseOffMend.java @@ -15,10 +15,11 @@ public void mendPolygon(ZSlicer slicer, List> brokenLoops, List { private static final Logger logger = LogManager.getLogger(); @@ -22,20 +23,22 @@ public class ScanlineFillPolygonWork extends RecursiveTask potentialLinesInRange; private List scanLines = new ArrayList(); private Set insideOutPolygons = new HashSet(); - private List watchedTriangles; + private List watchedTriangles; private int buildArea; private int start; private int stop; private int z; private List watchedYs; + private ZSlicer slicer; - public ScanlineFillPolygonWork(List potentialLinesInRange, List watchedTriangles, List watchedYs, int start, int stop, int z) { + public ScanlineFillPolygonWork(ZSlicer slicer, List potentialLinesInRange, List watchedTriangles, List watchedYs, int start, int stop, int z) { this.potentialLinesInRange = potentialLinesInRange; this.watchedTriangles = watchedTriangles; this.watchedYs = watchedYs; this.start = start; this.stop = stop; this.z = z; + this.slicer = slicer; } public Set getInsideOutPolygons() { @@ -54,12 +57,29 @@ public int getBuildArea() { protected ScanlineFillPolygonWork compute() { boolean watch = false; for (int y = start; y <= stop; y++) { - Set intersectedPoints = new TreeSet(new XYComparatord(Triangle3d.EQUAL_TOLERANCE)); + + Set intersectedPoints = new TreeSet(new XYComparatorNormalImportantd(Triangle3d.EQUAL_TOLERANCE)); for (Line3d currentLine : potentialLinesInRange) { - if (watchedTriangles != null) { + if (watchedTriangles != null && watchedYs != null) { Face3d face = currentLine.getOriginatingFace(); - if (watchedTriangles.contains(face)) { - logger.debug("Watch triangle:{}", face); + if (watchedTriangles.contains(face) && watchedYs.contains(y)) { + logger.debug("Watch triangle:{}", ()->{ + if (face instanceof Triangle3d) { + return slicer.translateTriangle((Triangle3d)face); + } + if (face instanceof MultiTriangleFace) { + StringBuilder builder = new StringBuilder(); + int t = 0; + for (Triangle3d tri : ((MultiTriangleFace)face).getFaces()) { + builder.append("tri" + t + ":" + slicer.translateTriangle(tri) + " "); + } + return builder.toString(); + } + if (face == null) { + return "No originating face:" + face; + } + return "Unknown Face type" + face.getClass(); + });//translateTriangle watch = true; } } @@ -73,7 +93,18 @@ protected ScanlineFillPolygonWork compute() { } double x = currentLine.getXIntersectionPoint(y); - if (x >= currentLine.getMinX() && x <= currentLine.getMaxX()) {//TODO: ceil/floor here? + if (Double.isNaN(x) && currentLine.getPointOne().y == currentLine.getPointTwo().y && Math.abs(currentLine.getPointOne().y - y) < Triangle3d.EQUAL_TOLERANCE) { + //Pick the lower x value from the two points on the currentLine + if (currentLine.getPointOne().x < currentLine.getPointTwo().x) { + intersectedPoints.add(new Point3d(currentLine.getPointOne().x, currentLine.getPointOne().y, z, new Point3d(Double.NaN, Double.NaN, Double.NaN), currentLine.getOriginatingFace())); + } else { + intersectedPoints.add(new Point3d(currentLine.getPointTwo().x, currentLine.getPointTwo().y, z, new Point3d(Double.NaN, Double.NaN, Double.NaN), currentLine.getOriginatingFace())); + } + if (watch) { + logger.debug("Watch triangle intersections found on straight line:{} and {}", currentLine.getPointOne(), currentLine.getPointTwo()); + watch = false; + } + } else if (x + Triangle3d.EQUAL_TOLERANCE >= currentLine.getMinX() && x - Triangle3d.EQUAL_TOLERANCE <= currentLine.getMaxX()) { Point3d intersection = new Point3d(x, y, z, currentLine.getNormal(), currentLine.getOriginatingFace()); intersectedPoints.add(intersection); if (watch) { @@ -90,16 +121,37 @@ protected ScanlineFillPolygonWork compute() { logger.debug("Watch y:{} intersection:{}", y, intersectedPoints); } + + /* + * A few descriptions + * -1, infinite slope, -1, 1 -> infinite slope == 1 + * -1, 1, infinite slope, 1 -> infinite slope == -1 + * -1, 1, infinite slope, -1, 1 -> infinite slope == continue for + * -1, infinite slope, 1 -> infinite slope == continue for + */ int drawingValue = 0; + boolean lastPointWasHorizontal = false; + int beforeNormal = 0; Point3d firstPoint = null; for (Point3d intersectedPoint : intersectedPoints) { Point3d normal = intersectedPoint.getNormal(); + if (normal.isInfiniteInverseSlopeOnIntegerBoundry()) { + lastPointWasHorizontal = true; + continue; + } if (normal.x > 0) { - drawingValue += 1; + if (!lastPointWasHorizontal || beforeNormal == -1) { + drawingValue += 1; + } + beforeNormal = 1; } else if (normal.x < 0) { - drawingValue -= 1; + if (!lastPointWasHorizontal || beforeNormal == 1) { + drawingValue -= 1; + } + beforeNormal = -1; } - + lastPointWasHorizontal = false; + if (firstPoint == null) { if (drawingValue > 0) { insideOutPolygons.add(intersectedPoint.getOriginatingShape()); diff --git a/host/src/main/java/org/area515/resinprinter/slice/SelfContainedScanlineFillPolygonWork.java b/host/src/main/java/org/area515/resinprinter/slice/SelfContainedScanlineFillPolygonWork.java index e420289b2..d5638f35a 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/SelfContainedScanlineFillPolygonWork.java +++ b/host/src/main/java/org/area515/resinprinter/slice/SelfContainedScanlineFillPolygonWork.java @@ -11,7 +11,7 @@ import org.area515.resinprinter.stl.Line3d; import org.area515.resinprinter.stl.Point3d; import org.area515.resinprinter.stl.Triangle3d; -import org.area515.resinprinter.stl.XYComparatord; +import org.area515.resinprinter.stl.XYComparatorNormalImportantd; public class SelfContainedScanlineFillPolygonWork extends RecursiveAction { private static final long serialVersionUID = 217859858236513212L; @@ -61,7 +61,7 @@ protected void compute() { List tempScanLines = new ArrayList(); List tempInsideOutPolygons = new ArrayList(); for (int y = start; y <= stop; y++) { - Set intersectedPoints = new TreeSet(new XYComparatord(Triangle3d.EQUAL_TOLERANCE)); + Set intersectedPoints = new TreeSet(new XYComparatorNormalImportantd(Triangle3d.EQUAL_TOLERANCE)); for (Line3d currentLine : potentialLinesInRange) { double x = currentLine.getXIntersectionPoint(y); if (x >= currentLine.getMinX() && x <= currentLine.getMaxX()) { diff --git a/host/src/main/java/org/area515/resinprinter/slice/SliceBrowser.java b/host/src/main/java/org/area515/resinprinter/slice/SliceBrowser.java index 596616e35..7f4c3d880 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/SliceBrowser.java +++ b/host/src/main/java/org/area515/resinprinter/slice/SliceBrowser.java @@ -46,13 +46,16 @@ import org.area515.resinprinter.slice.StlError.ErrorType; import org.area515.resinprinter.stl.Face3d; import org.area515.resinprinter.stl.Line3d; +import org.area515.resinprinter.stl.MultiTriangleFace; import org.area515.resinprinter.stl.Shape3d; import org.area515.resinprinter.stl.Triangle3d; public class SliceBrowser extends JSplitPane { + private static final long serialVersionUID = 7919446439764262367L; + private PrinterTools tools; - private int firstSlice = 1; + private int firstSlice = 125; //95 CornerBracket_2.stl //C:\\Users\\wgilster\\Desktop\\fdhgg.stl //78;//"C:\\Users\\wgilster\\Documents\\ArduinoMegaEnclosure.stl"; @@ -61,7 +64,7 @@ public class SliceBrowser extends JSplitPane { //"C:\\Users\\wgilster\\git\\Creation-Workshop-Host\\host\\src\\test\\resources\\org\\area515\\resinprinter\\slice\\CornerBracket_2.stl" //private String firstFile = "C:\\Users\\wgilster\\Documents\\fdhgg.stl";//1,200,670 - private String firstFile = "C:\\Users\\wgilster\\uploaddir\\johnny-test pieces 1.stl"; + private String firstFile = "C:\\Users\\wgilster\\git\\Photonic3DWes\\host\\src\\test\\resources\\org\\area515\\resinprinter\\slice\\YHD-101 Corners 121 that crashes the JVM.stl"; // private String firstFile = "C:\\Users\\wgilster\\Documents\\NonManifoldBox.stl";//-19 // private String firstFile = "C:\\Users\\wgilster\\Documents\\Fat_Guy_Statue.stl"; // private String firstFile = "C:\\Users\\wgilster\\AppData\\Local\\Temp\\uploaddir\\CornerBracket_2.stl";//95 @@ -132,10 +135,10 @@ public void clearChildren() { getSliceTree().clearSelection(); } - public List getSelectedTriangles() { - List triangles = new ArrayList(); + public List getSelectedTriangles() { + List triangles = new ArrayList(); for (Line3d line : selectedLines) { - triangles.add((Triangle3d)line.getOriginatingFace()); + triangles.add(line.getOriginatingFace()); } return triangles; @@ -215,6 +218,12 @@ public void refreshGui(JLabel mouseLabel, boolean refreshTreeNodes) { if (face3d instanceof Triangle3d) { lineNode.add(new SliceBrowserTreeNode(slicer.translateTriangle((Triangle3d)face3d))); } + + if (face3d instanceof MultiTriangleFace) { + for (Face3d face : ((MultiTriangleFace)face3d).getFaces()) { + lineNode.add(new SliceBrowserTreeNode(slicer.translateTriangle((Triangle3d)face))); + } + } } } nodeChanged(root); @@ -388,6 +397,7 @@ public void mouseClicked(MouseEvent e) { newPoint.setX(e.getX()); joinFile.getPoints().add(newPoint); SlicePointUtils.savePoints(points); + JOptionPane.showInputDialog("Points file saved to:", new File(SlicePointUtils.class.getResource("points.json").toURI()).getAbsolutePath()); } catch (IOException | URISyntaxException e1) { e1.printStackTrace(); } @@ -510,7 +520,7 @@ public void actionPerformed(ActionEvent e) { testTriangleEqualButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - List triangles = sliceBrowserListener.getSelectedTriangles(); + List triangles = sliceBrowserListener.getSelectedTriangles(); for (int t = 0; t < triangles.size() - 1; t++) { System.out.print("Triangle:" + t + " = " + (t+1)); if (triangles.get(t).equals(triangles.get(t+1))) { diff --git a/host/src/main/java/org/area515/resinprinter/slice/SlicePointUtils.java b/host/src/main/java/org/area515/resinprinter/slice/SlicePointUtils.java index 51920b796..4306ad6dc 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/SlicePointUtils.java +++ b/host/src/main/java/org/area515/resinprinter/slice/SlicePointUtils.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; +import javax.swing.JOptionPane; + import org.junit.Test; import com.fasterxml.jackson.core.JsonFactory; @@ -29,7 +31,6 @@ public static void savePoints(Map testPoints) throws IOExcep ObjectMapper mapper = new ObjectMapper(new JsonFactory()); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.writeValue(new File(unzippedFileAccess), new ArrayList(testPoints.values())); - System.out.println("Saved to:" + new File(unzippedFileAccess).getAbsolutePath()); } public static Map loadPoints() throws IOException { diff --git a/host/src/main/java/org/area515/resinprinter/slice/StlError.java b/host/src/main/java/org/area515/resinprinter/slice/StlError.java index 334754979..371873551 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/StlError.java +++ b/host/src/main/java/org/area515/resinprinter/slice/StlError.java @@ -1,10 +1,10 @@ package org.area515.resinprinter.slice; +import org.area515.resinprinter.stl.Face3d; import org.area515.resinprinter.stl.Line3d; -import org.area515.resinprinter.stl.Triangle3d; public class StlError { - private Triangle3d triangle; + private Face3d face; private Line3d nonManifoldEdge; private ErrorType type; @@ -13,22 +13,22 @@ public static enum ErrorType { Insideout } - public StlError(Triangle3d triangle, Line3d nonManifoldEdge) { - this.triangle = triangle; + public StlError(Face3d face, Line3d nonManifoldEdge) { + this.face = face; this.nonManifoldEdge = nonManifoldEdge; this.type = ErrorType.NonManifold; } - public StlError(Triangle3d triangle, ErrorType type) { - this.triangle = triangle; + public StlError(Face3d face, ErrorType type) { + this.face = face; this.type = type; } - public Triangle3d getTriangle() { - return triangle; + public Face3d getFace() { + return face; } - public void setTriangle(Triangle3d triangle) { - this.triangle = triangle; + public void setFace(Face3d triangle) { + this.face = triangle; } public Line3d getNonManifoldEdge() { @@ -46,6 +46,6 @@ public void setType(ErrorType type) { } public String toString() { - return type + " triangle:" + triangle + (nonManifoldEdge != null?(" edge:" + nonManifoldEdge):""); + return type + " triangle:" + face + (nonManifoldEdge != null?(" edge:" + nonManifoldEdge):""); } } diff --git a/host/src/main/java/org/area515/resinprinter/slice/ZSlicer.java b/host/src/main/java/org/area515/resinprinter/slice/ZSlicer.java index 17a968efd..5b687829c 100644 --- a/host/src/main/java/org/area515/resinprinter/slice/ZSlicer.java +++ b/host/src/main/java/org/area515/resinprinter/slice/ZSlicer.java @@ -20,17 +20,19 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; +import javax.lang.model.type.ErrorType; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.area515.resinprinter.slice.StlError.ErrorType; import org.area515.resinprinter.stl.BrokenFace3d; import org.area515.resinprinter.stl.Face3d; import org.area515.resinprinter.stl.Line3d; +import org.area515.resinprinter.stl.MultiTriangleFace; import org.area515.resinprinter.stl.Point3d; import org.area515.resinprinter.stl.Shape3d; import org.area515.resinprinter.stl.Triangle3d; import org.area515.resinprinter.stl.XYComparatord; -import org.area515.util.Log4jTimer; +import org.area515.util.Log4jUtil; public class ZSlicer { private static final Logger logger = LogManager.getLogger(); @@ -194,9 +196,9 @@ private LinkageDiscovery findLinkage(List currentWorkingLoop, Line3d cur LinkageDiscovery completedLinkage = LinkageDiscovery.NoLinkFound; Line3d firstInCurrentWorkingLoop = currentWorkingLoop.get(0); Line3d lastInCurrentWorkingLoop = currentWorkingLoop.get(currentWorkingLoop.size() - 1); - if (currentLine.getPointTwo().pointEquals(firstInCurrentWorkingLoop.getPointOne())) { + if (currentLine.getPointTwo().pointCompare(firstInCurrentWorkingLoop.getPointOne()) == 0) { //Check to determine if this loop is closed - if (currentLine.getPointOne().pointEquals(lastInCurrentWorkingLoop.getPointTwo())) { + if (currentLine.getPointOne().pointCompare(lastInCurrentWorkingLoop.getPointTwo()) == 0) { completedLinkage = LinkageDiscovery.FoundCompletion; logger.debug("Completed Link: 1 with [{}] links (Link line)", currentWorkingLoop.size() + 1); } else { @@ -204,9 +206,9 @@ private LinkageDiscovery findLinkage(List currentWorkingLoop, Line3d cur } currentWorkingLoop.add(0, currentLine); - } else if (lastInCurrentWorkingLoop.getPointTwo().pointEquals(currentLine.getPointOne())) { + } else if (lastInCurrentWorkingLoop.getPointTwo().pointCompare(currentLine.getPointOne()) == 0) { //Check to determine if this loop is closed - if (firstInCurrentWorkingLoop.getPointOne().pointEquals(currentLine.getPointTwo())) { + if (firstInCurrentWorkingLoop.getPointOne().pointCompare(currentLine.getPointTwo()) == 0) { completedLinkage = LinkageDiscovery.FoundCompletion; logger.debug("Completed Link: 2 with [{}] links (Link line)", currentWorkingLoop.size() + 1); } else { @@ -214,9 +216,9 @@ private LinkageDiscovery findLinkage(List currentWorkingLoop, Line3d cur } currentWorkingLoop.add(currentLine); - } else if (lastInCurrentWorkingLoop.getPointTwo().pointEquals(currentLine.getPointTwo())) { + } else if (lastInCurrentWorkingLoop.getPointTwo().pointCompare(currentLine.getPointTwo()) == 0) { //Check to determine if this loop is closed - if (firstInCurrentWorkingLoop.getPointOne().pointEquals(currentLine.getPointOne())) { + if (firstInCurrentWorkingLoop.getPointOne().pointCompare(currentLine.getPointOne()) == 0) { completedLinkage = LinkageDiscovery.FoundCompletion; logger.debug("Completed Link: 3 with [{}] links (Link line)", currentWorkingLoop.size() + 1); } else { @@ -225,9 +227,9 @@ private LinkageDiscovery findLinkage(List currentWorkingLoop, Line3d cur currentLine.swap(); currentWorkingLoop.add(currentLine); - } else if (currentLine.getPointOne().pointEquals(firstInCurrentWorkingLoop.getPointOne())) { + } else if (currentLine.getPointOne().pointCompare(firstInCurrentWorkingLoop.getPointOne()) == 0) { //Check to determine if this loop is closed - if (firstInCurrentWorkingLoop.getPointTwo().pointEquals(currentLine.getPointTwo())) { + if (firstInCurrentWorkingLoop.getPointTwo().pointCompare(currentLine.getPointTwo()) == 0) { completedLinkage = LinkageDiscovery.FoundCompletion; logger.debug("Completed Link: 4 with [{}] links (Link line)", currentWorkingLoop.size() + 1); } else { @@ -247,10 +249,10 @@ private LinkageDiscovery findLinkage(List currentWorkingLoop, List currentWorkingLoop, List currentWorkingLoop, List currentWorkingLoop, List compilePolygons(List> completedFillInLoops) { //These are a double check for situations that should never happen other than if a single line(from a broken loop) was placed into the completedFillInLoops if (lines.size() > 1) { - if (!lines.get(t).getPointTwo().pointEquals(lines.get(nextPoint).getPointOne())) { + if (lines.get(t).getPointTwo().pointCompare(lines.get(nextPoint).getPointOne()) != 0) { logger.warn("Compare second point[{}]:{} to first point[{}]:{}", t, lines.get(t), nextPoint, lines.get(nextPoint)); } - if (!lines.get(t).getPointOne().pointEquals(lines.get(prevPoint).getPointTwo())) { + if (lines.get(t).getPointOne().pointCompare(lines.get(prevPoint).getPointTwo()) != 0) { logger.warn("Compare first point[{}]:{} to second point[{}]:{}", t, lines.get(t), prevPoint, lines.get(prevPoint)); } } @@ -559,15 +561,15 @@ private List findPathThroughTrianglesAndBrokenLoops(Point3d beginning, P Point3d checkPoint = brokenEnds[t]; Point3d previousPoint = brokenEnds[t == 0?brokenEnds.length - 1:t-1]; Point3d nextPoint = brokenEnds[t == brokenEnds.length - 1?0:t+1]; - if (checkPoint.pointEquals(beginning)) { + if (checkPoint.pointCompare(beginning) == 0) { usedFaces.add(currentTriangleIndex); //First check if we can end this fiasco... - if (nextPoint.pointEquals(ending)) { + if (nextPoint.pointCompare(ending) == 0) { Line3d line = new Line3d(checkPoint, ending, null, currentBrokenFace, false);//TODO: Use proper normal path.add(line); return path; - } else if (previousPoint.pointEquals(ending)) { + } else if (previousPoint.pointCompare(ending) == 0) { Line3d line = new Line3d(checkPoint, ending, null, currentBrokenFace, false);//TODO: Use proper normal path.add(line); return path; @@ -592,7 +594,7 @@ private List findPathThroughTrianglesAndBrokenLoops(Point3d beginning, P } //used in org.area515.resinprinter.job.STLImageRenderer.STLImageRenderer - public List> colorizePolygons(List watchedTriangles, List watchedYs) { + public List> colorizePolygons(List watchedTriangles, List watchedYs) { sliceMaxX = -Integer.MAX_VALUE; sliceMaxY = -Integer.MAX_VALUE; @@ -609,7 +611,8 @@ public List> colorizePolygons(List watchedTriangles, Li //Effectively, this loop is log n due to the sort into XYComparator logger.info("==================="); - logger.info("ZSlice started", ()->Log4jTimer.startTimer("sliceTime")); + logger.info("ZSlice started", ()->Log4jUtil.startTimer("sliceTime")); + //TODO: This set assumes that normals should distingish separate points. Is that ok? Should we use: org.area515.resinprinter.stl.XYComparatorNormalImportantd Set zIntersectionsBySortedX = new TreeSet(new XYComparatord(Triangle3d.EQUAL_TOLERANCE)); for (Triangle3d triangle : stlFile.getTriangles()) { if (watchedTriangles != null && watchedTriangles.contains(triangle)) { @@ -641,7 +644,7 @@ public List> colorizePolygons(List watchedTriangles, Li }//*/ } - logger.info("IntersectionTime:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("IntersectionTime:{}", ()->Log4jUtil.splitTimer("sliceTime")); logger.debug("==================="); logger.debug("zIntersectionsBySortedX:{}", zIntersectionsBySortedX.size()); logger.debug("completedFillInLoops:{}", completedFillInLoops.size()); @@ -673,7 +676,7 @@ public List> colorizePolygons(List watchedTriangles, Li workingLoops.add(newLoop); } - logger.info("Primary linkage search:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Primary linkage search:{}", ()->Log4jUtil.splitTimer("sliceTime")); if (logger.isDebugEnabled()) { logger.debug("==================="); @@ -691,7 +694,7 @@ public List> colorizePolygons(List watchedTriangles, Li } logger.debug("workingLoops lines:{}", value); logger.debug("===================");//*/ - logger.debug("Debug print time:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.debug("Debug print time:{}", ()->Log4jUtil.splitTimer("sliceTime")); } //Empirically I've found that about half of all loops need to be joined with this method @@ -720,7 +723,7 @@ public List> colorizePolygons(List watchedTriangles, Li workingLoops.remove(0); } - logger.info("Secondary linkage search:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Secondary linkage search:{}", ()->Log4jUtil.splitTimer("sliceTime")); if (logger.isDebugEnabled()) { logger.debug("==================="); @@ -744,7 +747,7 @@ public List> colorizePolygons(List watchedTriangles, Li } logger.debug("brokenLoops lines:{}", value); logger.debug("==================="); - logger.debug("Print broken loops:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.debug("Print broken loops:{}", ()->Log4jUtil.splitTimer("sliceTime")); } //empirically I've found that this block of code will only execute 1 in 100 times. @@ -759,7 +762,7 @@ public List> colorizePolygons(List watchedTriangles, Li //TODO: Somehow we are allowing a lost single point to find it's way to this code. That is indicitive of a bug earlier in the above sections if (currentLoop.size() == 1 && - currentLoop.get(0).getPointOne().pointEquals(currentLoop.get(0).getPointTwo())) { + currentLoop.get(0).getPointOne().pointCompare(currentLoop.get(0).getPointTwo()) == 0) { continue; } @@ -836,16 +839,43 @@ public List> colorizePolygons(List watchedTriangles, Li } } - logger.info("Stl error capturing:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Stl error capturing:{}", ()->Log4jUtil.splitTimer("sliceTime")); } - + + //Combine perfectly horizontal lines into a single line + for (List currentPolygon : completedFillInLoops) { + int polySize = currentPolygon.size(); + if (polySize < 2) { + continue; + } + for (int lineSegmentIndex = 0; lineSegmentIndex < polySize; lineSegmentIndex++) { + Line3d current = currentPolygon.get(lineSegmentIndex); + int prevIndex = lineSegmentIndex == 0?polySize - 1:lineSegmentIndex - 1; + Line3d previous = currentPolygon.get(prevIndex); + if (current.isInfiniteInverseSlope() && + previous.isInfiniteInverseSlope() && + current.getPointOne().y == current.getPointTwo().y && + previous.getPointOne().y == previous.getPointTwo().y && + current.getPointOne().y == previous.getPointTwo().y) { + if (current.getPointOne().pointCompare(previous.getPointTwo()) == 0) { + currentPolygon.set(prevIndex, new Line3d(previous.getPointOne(), current.getPointTwo(), current.getNormal(), new MultiTriangleFace(current.getOriginatingFace(), previous.getOriginatingFace()), false)); + } else { + logger.error("Point ordering is not what we expect in these lines previous:{}, current:{}", previous, current); + } + currentPolygon.remove(lineSegmentIndex); + polySize--; + lineSegmentIndex--; + } + } + } + //close loops manually since we couldn't find a solution for these broken loops if (fixBrokenLoops != null && brokenLoops.size() > 0) { fixBrokenLoops.mendPolygon(this, brokenLoops, completedFillInLoops); - logger.info("Broken loop mending:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Broken loop mending:{}", ()->Log4jUtil.splitTimer("sliceTime")); } - //Preperation work for the Scanline algorithm + //Preparation work for the Scanline algorithm Map> inRangeLines = new HashMap>(); int breakupSize = (sliceMaxY - sliceMinY) / ScanlineFillPolygonWork.SMALLEST_UNIT_OF_WORK; if (completedFillInLoops.size() % ScanlineFillPolygonWork.SMALLEST_UNIT_OF_WORK > 0) { @@ -869,7 +899,7 @@ public List> colorizePolygons(List watchedTriangles, Li } } } - logger.info("Break scanline up into pieces:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Break scanline up into pieces:{}", ()->Log4jUtil.splitTimer("sliceTime")); List> completedWork = new ArrayList>(); for (int y = 0; y < breakupSize; y++) { @@ -879,6 +909,7 @@ public List> colorizePolygons(List watchedTriangles, Li } ScanlineFillPolygonWork work = new ScanlineFillPolygonWork( + this, inRange, watchedTriangles, watchedYs, @@ -887,7 +918,7 @@ public List> colorizePolygons(List watchedTriangles, Li z); completedWork.add(pool.submit(work)); } - logger.info("Submit scanline work:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Submit scanline work:{}", ()->Log4jUtil.splitTimer("sliceTime")); fillInScanLines = new ArrayList(); buildArea = 0; @@ -897,7 +928,7 @@ public List> colorizePolygons(List watchedTriangles, Li work = currentWork.get(); if (keepTrackOfErrors) { for (Face3d currentInsideOutPolygon : work.getInsideOutPolygons()) { - errors.add(new StlError((Triangle3d)currentInsideOutPolygon, ErrorType.Insideout)); + errors.add(new StlError(currentInsideOutPolygon, StlError.ErrorType.Insideout)); } } @@ -907,13 +938,13 @@ public List> colorizePolygons(List watchedTriangles, Li logger.error("Error in executing polygon work", e); } } - logger.info("Wait for scanline work:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Wait for scanline work:{}", ()->Log4jUtil.splitTimer("sliceTime")); //I'm not sure I want to do this. It just traces the polygon but doesn't provide much value other than an edge blur. logger.debug("Polygons"); logger.debug("======"); fillInPolygons = compilePolygons(completedFillInLoops); - logger.info("Compile polygons:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.info("Compile polygons:{}", ()->Log4jUtil.splitTimer("sliceTime")); if (logger.isDebugEnabled()) { logger.debug("TOTALS"); @@ -928,15 +959,15 @@ public List> colorizePolygons(List watchedTriangles, Li } logger.debug("Working Loops({}):{}",workingLoops.size(), workingLoops); logger.debug("======");//*/ - logger.debug("Print working loops:{}", ()->Log4jTimer.splitTimer("sliceTime")); + logger.debug("Print working loops:{}", ()->Log4jUtil.splitTimer("sliceTime")); } pool.shutdown(); - logger.info("ZSlice complete:{}", ()->Log4jTimer.completeTimer("sliceTime")); + logger.info("ZSlice complete:{}", ()->Log4jUtil.completeTimer("sliceTime")); return completedFillInLoops; } public void loadFile(InputStream stream, Double buildPlatformXPixels, Double buildPlatformYPixels) throws IOException { - logger.info("Load file start", ()->Log4jTimer.startTimer("fileLoadTime")); + logger.info("Load file start", ()->Log4jUtil.startTimer("fileLoadTime")); stlFile.load(stream, rewriteNormalsWithRightHandRule); if (imageOffsetX == null) { @@ -957,7 +988,7 @@ public void loadFile(InputStream stream, Double buildPlatformXPixels, Double bui imageOffsetY = -stlFile.getYmin() / precisionScaler * pixelsPerMMY; } } - logger.info("Load file stop:{}", ()->Log4jTimer.completeTimer("fileLoadTime")); + logger.info("Load file stop:{}", ()->Log4jUtil.completeTimer("fileLoadTime")); } public int getZIndex() { diff --git a/host/src/main/java/org/area515/resinprinter/stl/Line3d.java b/host/src/main/java/org/area515/resinprinter/stl/Line3d.java index 2a1d3d7ec..3ed384ff1 100644 --- a/host/src/main/java/org/area515/resinprinter/stl/Line3d.java +++ b/host/src/main/java/org/area515/resinprinter/stl/Line3d.java @@ -8,7 +8,7 @@ public class Line3d implements Shape3d { private Point3d two; private Point3d normal; private Face3d originatingFace;//This is usually a Triangle3d - private double slope; + private double inverseSlope; private double xintercept; public Line3d(Point3d one, Point3d two, Point3d normal, Face3d originatingFace, boolean swapIfNecessary) { @@ -22,8 +22,12 @@ public Line3d(Point3d one, Point3d two, Point3d normal, Face3d originatingFace, this.originatingFace = originatingFace; this.normal = normal != null?normal:new Point3d(this.one.y - this.two.y, this.one.x - this.two.x, this.two.z - this.one.z);//Uses counterclockwise rule in coop with Triangle3d creation in ZSlicer constructor - this.slope = (this.one.x - this.two.x) / (this.one.y - this.two.y); - this.xintercept = -(slope * this.one.y - this.one.x); + this.inverseSlope = (this.one.x - this.two.x) / (this.one.y - this.two.y); + this.xintercept = -(inverseSlope * this.one.y - this.one.x); + } + + public boolean isInfiniteInverseSlope() { + return Double.isInfinite(inverseSlope); } public boolean intersects(double x1, double y1, double x2, double y2) { @@ -31,7 +35,7 @@ public boolean intersects(double x1, double y1, double x2, double y2) { } public double getXIntersectionPoint(double y) { - return slope * y + xintercept; + return inverseSlope * y + xintercept; } public double getMinX() { @@ -92,12 +96,12 @@ public boolean pointsEqual(Object obj) { if (getClass() != obj.getClass()) return false; Line3d other = (Line3d) obj; - if ((one == other.one || (one != null && one.pointEquals(other.one))) && - (two == other.two || (two != null && two.pointEquals(other.two)))) { + if ((one == other.one || (one != null && one.pointCompare(other.one) == 0)) && + (two == other.two || (two != null && two.pointCompare(other.two) == 0))) { return true; } - if ((one == other.two || (two != null && two.pointEquals(other.one))) && - (two == other.one || (one != null && one.pointEquals(other.two)))) { + if ((one == other.two || (two != null && two.pointCompare(other.one) == 0)) && + (two == other.one || (one != null && one.pointCompare(other.two) == 0))) { return true; } return false; diff --git a/host/src/main/java/org/area515/resinprinter/stl/MultiTriangleFace.java b/host/src/main/java/org/area515/resinprinter/stl/MultiTriangleFace.java new file mode 100644 index 000000000..0d664ddc6 --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/stl/MultiTriangleFace.java @@ -0,0 +1,60 @@ +package org.area515.resinprinter.stl; + +import java.util.ArrayList; +import java.util.List; + +public class MultiTriangleFace implements Face3d { + private List faces = new ArrayList<>(); + + public MultiTriangleFace(Face3d face1, Face3d face2) { + if (face1 instanceof MultiTriangleFace) { + faces.addAll(((MultiTriangleFace)face1).faces); + } else if (face1 instanceof Triangle3d){ + faces.add((Triangle3d)face1); + } + if (face2 instanceof MultiTriangleFace) { + faces.addAll(((MultiTriangleFace)face2).faces); + } else if (face2 instanceof Triangle3d){ + faces.add((Triangle3d)face2); + } + } + + @Override + public Point3d[] getBrokenEnds() { + return null; + } + + @Override + public Point3d getNormal() { + return faces.get(0).getNormal(); + } + + public List getFaces() { + return faces; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((faces == null) ? 0 : faces.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MultiTriangleFace other = (MultiTriangleFace) obj; + if (faces == null) { + if (other.faces != null) + return false; + } else if (!faces.equals(other.faces)) + return false; + return true; + } +} diff --git a/host/src/main/java/org/area515/resinprinter/stl/Point3d.java b/host/src/main/java/org/area515/resinprinter/stl/Point3d.java index 4d5ec1ce5..ed86b6009 100644 --- a/host/src/main/java/org/area515/resinprinter/stl/Point3d.java +++ b/host/src/main/java/org/area515/resinprinter/stl/Point3d.java @@ -67,14 +67,47 @@ public boolean ceilingEquals(Point3d otherPoint) { Math.ceil(z) == Math.ceil(otherPoint.z); } - public boolean pointEquals(Point3d other) { + //Does this point belong to a horizontal line? + public boolean isInfiniteInverseSlopeOnIntegerBoundry() { + return Double.isNaN(this.x) && Double.isNaN(this.y) && Double.isNaN(this.z); + } + + public int pointCompare(Point3d other) { + boolean thisInfiniteSlopeOnIntegerBoundry = isInfiniteInverseSlopeOnIntegerBoundry(); + boolean otherInfiniteSlopeOnIntegerBoundry = other.isInfiniteInverseSlopeOnIntegerBoundry(); + if (thisInfiniteSlopeOnIntegerBoundry) { + if (otherInfiniteSlopeOnIntegerBoundry) { + return 0; + } + + return 1; + } else if (otherInfiniteSlopeOnIntegerBoundry){ + return -1; + } + double xdiff = this.x - other.x; double ydiff = this.y - other.y; double zdiff = this.z - other.z; - return xdiff <= Triangle3d.EQUAL_TOLERANCE && xdiff >= -Triangle3d.EQUAL_TOLERANCE && - ydiff <= Triangle3d.EQUAL_TOLERANCE && ydiff >= -Triangle3d.EQUAL_TOLERANCE && - zdiff <= Triangle3d.EQUAL_TOLERANCE && zdiff >= -Triangle3d.EQUAL_TOLERANCE; + if (xdiff > Triangle3d.EQUAL_TOLERANCE) { + return -1; + } + if (xdiff < -Triangle3d.EQUAL_TOLERANCE) { + return 1; + } + if (ydiff > Triangle3d.EQUAL_TOLERANCE) { + return -1; + } + if (ydiff < -Triangle3d.EQUAL_TOLERANCE) { + return 1; + } + if (zdiff > Triangle3d.EQUAL_TOLERANCE) { + return -1; + } + if (zdiff < -Triangle3d.EQUAL_TOLERANCE) { + return 1; + } + return 0; } @Override diff --git a/host/src/main/java/org/area515/resinprinter/stl/Triangle3d.java b/host/src/main/java/org/area515/resinprinter/stl/Triangle3d.java index 6b687cd8d..1cc9cf281 100644 --- a/host/src/main/java/org/area515/resinprinter/stl/Triangle3d.java +++ b/host/src/main/java/org/area515/resinprinter/stl/Triangle3d.java @@ -159,7 +159,7 @@ public Shape3d getZIntersection(double z) { public int compareTo(Triangle3d o) { boolean equals = true; for (int t = 0; t < 3; t++) { - if (!points[t].pointEquals(o.points[t])) { + if (points[t].pointCompare(o.points[t]) != 0) { equals = false; } } diff --git a/host/src/main/java/org/area515/resinprinter/stl/XYComparatorNormalImportantd.java b/host/src/main/java/org/area515/resinprinter/stl/XYComparatorNormalImportantd.java new file mode 100644 index 000000000..ab4fa66dd --- /dev/null +++ b/host/src/main/java/org/area515/resinprinter/stl/XYComparatorNormalImportantd.java @@ -0,0 +1,100 @@ +package org.area515.resinprinter.stl; + +import java.util.Comparator; + +public class XYComparatorNormalImportantd implements Comparator { + private double tolerance; + + public XYComparatorNormalImportantd(double tolerance) { + this.tolerance = tolerance; + } + + private int compareTo(double first, double second) { + double value = first - second; + if (value > 0) { + return 1; + } else if (value < 1) { + return -1; + } + + return 0; + } + + @Override + public int compare(Shape3d first, Shape3d second) { + if (first instanceof Line3d && ((Line3d) first).pointsEqual(second)) { + return 0; + } + + if (first instanceof Point3d && + second instanceof Point3d && + ((Point3d) first).pointCompare((Point3d)second) == 0) { + Point3d normal1 = ((Point3d) first).getNormal(); + Point3d normal2 = ((Point3d)second).getNormal(); + if (normal1 == null) { + if (normal2 == null) { + return 0; + } else { + return -1; + } + } else if (normal2 != null) { + return normal1.pointCompare(normal2); + } else { + return 1;//first.hashCode() - second.hashCode(); + } + } + + if (first instanceof Line3d && second instanceof Line3d) { + int comp = compareTo(((Line3d)first).getPointOne().x, ((Line3d)second).getPointOne().x); + if (comp != 0) { + return comp; + } + comp = compareTo(((Line3d)first).getPointTwo().x, ((Line3d)second).getPointTwo().x); + if (comp != 0) { + return comp; + } + + comp = compareTo(((Line3d)first).getPointOne().y, ((Line3d)second).getPointOne().y); + if (comp != 0) { + return comp; + } + comp = compareTo(((Line3d)first).getPointTwo().y, ((Line3d)second).getPointTwo().y); + if (comp != 0) { + return comp; + } + + comp = compareTo(((Line3d)first).getPointOne().z, ((Line3d)second).getPointOne().z); + if (comp != 0) { + return comp; + } + comp = compareTo(((Line3d)first).getPointTwo().z, ((Line3d)second).getPointTwo().z); + if (comp != 0) { + return comp; + } + + return compare(((Line3d)first).getNormal(), ((Line3d)second).getNormal()); + } + + double value = first.getMinX() - second.getMinX(); + if (value > tolerance) { + return 1; + } + if (value < -tolerance) { + return -1; + } + + value = first.getMinY() - second.getMinY(); + if (value > tolerance) { + return 1; + } + if (value < -tolerance) { + return -1; + } + + if (first.equals(second)) { + return 0; + } + + return first.hashCode() - second.hashCode(); + } +} diff --git a/host/src/main/java/org/area515/resinprinter/stl/XYComparatord.java b/host/src/main/java/org/area515/resinprinter/stl/XYComparatord.java index 564c7ee7e..a757795fc 100644 --- a/host/src/main/java/org/area515/resinprinter/stl/XYComparatord.java +++ b/host/src/main/java/org/area515/resinprinter/stl/XYComparatord.java @@ -26,7 +26,7 @@ public int compare(Shape3d first, Shape3d second) { return 0; } - if (first instanceof Point3d && ((Point3d) first).pointEquals((Point3d)second)) { + if (first instanceof Point3d && ((Point3d) first).pointCompare((Point3d)second) == 0) { return 0; } diff --git a/host/src/main/java/org/area515/resinprinter/text/TextFilePrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/text/TextFilePrintFileProcessor.java index 65dd4ce02..af1b2d194 100644 --- a/host/src/main/java/org/area515/resinprinter/text/TextFilePrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/text/TextFilePrintFileProcessor.java @@ -1,13 +1,9 @@ package org.area515.resinprinter.text; -import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; -import javax.script.ScriptException; - -import org.area515.resinprinter.exception.SliceHandlingException; import org.area515.resinprinter.job.AbstractPrintFileProcessor; +import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.twodim.TwoDimensionalImageRenderer; @@ -50,7 +46,7 @@ public boolean isThreeDimensionalGeometryAvailable() { } @Override - public TwoDimensionalImageRenderer createRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild) { + public TwoDimensionalImageRenderer createTwoDimensionalRenderer(DataAid aid, Object imageIndexToBuild) { return new TextImageRenderer(aid, this, imageIndexToBuild); } } diff --git a/host/src/main/java/org/area515/resinprinter/text/TextImageRenderer.java b/host/src/main/java/org/area515/resinprinter/text/TextImageRenderer.java index 0a27dbf02..a66687923 100644 --- a/host/src/main/java/org/area515/resinprinter/text/TextImageRenderer.java +++ b/host/src/main/java/org/area515/resinprinter/text/TextImageRenderer.java @@ -11,7 +11,6 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.util.concurrent.ExecutionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,26 +30,8 @@ public TextImageRenderer(DataAid aid, AbstractPrintFileProcessor processor } @Override - public BufferedImage scaleImageAndDetectEdges(PrintJob printJob) throws InterruptedException, ExecutionException { - return newImage.get(); - } - - public Font buildFont(DataAid data) { - TwoDimensionalSettings cwhTwoDim = data.slicingProfile.getTwoDimensionalSettings(); - org.area515.resinprinter.printer.SlicingProfile.Font cwhFont = cwhTwoDim != null?cwhTwoDim.getFont():new org.area515.resinprinter.printer.SlicingProfile.Font(); - if (cwhFont == null) { - cwhFont = PrinterService.DEFAULT_FONT; - } - - if (cwhFont.getName() == null) { - cwhFont.setName(PrinterService.DEFAULT_FONT.getName()); - } - - if (cwhFont.getSize() == 0) { - cwhFont.setSize(PrinterService.DEFAULT_FONT.getSize()); - } - - return new Font(cwhFont.getName(), Font.PLAIN, cwhFont.getSize()); + public BufferedImage scaleImageAndDetectEdges(PrintJob printJob) throws JobManagerException { + return waitForImage(); } private Object[] readTextDataFromFile(File textFile) throws JobManagerException { @@ -70,7 +51,7 @@ public BufferedImage loadImageFromFile(PrintJob textFile) throws JobManagerExcep chickenEggGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); chickenEggGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - Font font = buildFont(aid); + Font font = textFile.buildFont(); chickenEggGraphics.setFont(font); FontMetrics metrics = chickenEggGraphics.getFontMetrics(); double maxWidth = 0; @@ -84,6 +65,12 @@ public BufferedImage loadImageFromFile(PrintJob textFile) throws JobManagerExcep totalHeight += rect.getHeight(); } + if (maxWidth < 1) { + maxWidth = 1; + } + if (totalHeight < 1) { + totalHeight = 1; + } BufferedImage textImage = new BufferedImage((int)maxWidth, (int)totalHeight, BufferedImage.TYPE_4BYTE_ABGR); Graphics2D textGraphics = (Graphics2D)textImage.getGraphics(); textGraphics.setFont(font); diff --git a/host/src/main/java/org/area515/resinprinter/twodim/PlatformImageRenderer.java b/host/src/main/java/org/area515/resinprinter/twodim/PlatformImageRenderer.java index 64a555158..ea268a7db 100644 --- a/host/src/main/java/org/area515/resinprinter/twodim/PlatformImageRenderer.java +++ b/host/src/main/java/org/area515/resinprinter/twodim/PlatformImageRenderer.java @@ -1,35 +1,56 @@ package org.area515.resinprinter.twodim; +import java.awt.Color; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; import javax.script.ScriptException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.area515.resinprinter.job.AbstractPrintFileProcessor; import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid; import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.render.CurrentImageRenderer; -import org.area515.resinprinter.job.render.RenderedData; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.printer.SlicingProfile.TwoDimensionalSettings; +import org.area515.util.Log4jUtil; import org.area515.util.TemplateEngine; public class PlatformImageRenderer extends CurrentImageRenderer { + private static final Logger logger = LogManager.getLogger(); + protected static final String EXTRUSION_IMAGE = "lastExtrusionImage"; + private int totalPlatformSlices; - private TwoDimensionalImageRenderer extrusionImageRenderer; + private CurrentImageRenderer extrusionImageRenderer; - public PlatformImageRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild, int totalPlatformSlices, TwoDimensionalImageRenderer extrusionImageRenderer) { + public PlatformImageRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild, int totalPlatformSlices, CurrentImageRenderer extrusionImageRenderer) { super(aid, processor, imageIndexToBuild); this.totalPlatformSlices = totalPlatformSlices; this.extrusionImageRenderer = extrusionImageRenderer; } - @Override - public BufferedImage renderImage(BufferedImage imageToDisplay) throws JobManagerException { - if (imageToDisplay == null) { - imageToDisplay = buildImage(aid.xResolution, aid.yResolution); + protected BufferedImage getStarterImage(BufferedImage platformImage) { + if (platformImage == null) { + return buildImage(aid.xResolution, aid.yResolution); } + + Graphics2D g = (Graphics2D)platformImage.getGraphics(); + g.setColor(new Color(0, true)); + g.setBackground(new Color(0, true)); + g.clearRect(0, 0, platformImage.getWidth(), platformImage.getHeight()); + return platformImage; + } + + @Override + public BufferedImage renderImage(BufferedImage platformImage) throws JobManagerException { + final BufferedImage finalPlatformImage = getStarterImage(platformImage); + logger.trace("Writing renderImage1finalPlatformImage" + imageIndexToBuild + ":{}", () -> Log4jUtil.logImage(finalPlatformImage, "renderImage1finalPlatformImage" + imageIndexToBuild + ".png")); + TwoDimensionalSettings settings = aid.slicingProfile.getTwoDimensionalSettings(); if (settings == null) { throw new JobManagerException("This printer doesn't have it's 2D File Settings setup properly"); @@ -40,18 +61,28 @@ public BufferedImage renderImage(BufferedImage imageToDisplay) throws JobManager throw new JobManagerException("This printer doesn't have 2D platform rendering calculator setup"); } - RenderedData data = aid.cache.getOrCreateIfMissing("lastExtrusionImage"); - if (data.getPreTransformedImage() == null) { - data.setPreTransformedImage(extrusionImageRenderer.renderImage(null)); - } - - Map overrides = new HashMap<>(); - overrides.put("totalPlatformSlices", totalPlatformSlices); + RenderingContext platformToRender = aid.cache.getOrCreateIfMissing(imageIndexToBuild); + RenderingContext extrusionImageData = aid.cache.getOrCreateIfMissing(EXTRUSION_IMAGE); + ReentrantLock lock = platformToRender.getLock(); + lock.lock(); try { - TemplateEngine.runScriptInImagingContext(imageToDisplay, data.getPreTransformedImage(), aid.printJob, aid.printer, aid.scriptEngine, overrides, platformScript, "2D Platform rendering script", false); - } catch (ScriptException e) { - throw new JobManagerException("Failed to execute script", e); + if (extrusionImageData.getPreTransformedImage() == null) { + extrusionImageData.setPreTransformedImage(extrusionImageRenderer.renderImage(null)); + } + logger.trace("Writing renderImage2finalPlatformImage" + imageIndexToBuild + ":{}", () -> Log4jUtil.logImage(finalPlatformImage, "renderImage2finalPlatformImage" + imageIndexToBuild + ".png")); + + Map overrides = new HashMap<>(); + overrides.put("totalPlatformSlices", totalPlatformSlices); + try { + TemplateEngine.runScriptInImagingContext(finalPlatformImage, extrusionImageData.getPreTransformedImage(), aid.printJob, aid.printer, platformToRender.getScriptEngine(), overrides, platformScript, "2D Platform rendering script", false); + } catch (ScriptException e) { + throw new JobManagerException("Failed to execute script", e); + } + logger.trace("Writing renderImage3finalPlatformImage" + imageIndexToBuild + ":{}", () -> Log4jUtil.logImage(finalPlatformImage, "renderImage3finalPlatformImage" + imageIndexToBuild + ".png")); + + return finalPlatformImage; + } finally { + lock.unlock(); } - return imageToDisplay; } } diff --git a/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalImageRenderer.java b/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalImageRenderer.java index 4d428e427..b3ea34b8f 100644 --- a/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalImageRenderer.java +++ b/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalImageRenderer.java @@ -13,6 +13,7 @@ import org.area515.resinprinter.job.JobManagerException; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.job.render.CurrentImageRenderer; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.printer.SlicingProfile; import org.area515.resinprinter.server.Main; @@ -23,34 +24,46 @@ public TwoDimensionalImageRenderer(DataAid aid, AbstractPrintFileProcessor super(aid, processor, imageIndexToBuild); newImage = startImageLoad(aid.printJob); } - + + protected BufferedImage waitForImage() throws JobManagerException { + try { + return newImage.get(); + } catch (InterruptedException e) { + throw new JobManagerException("Interrupted while waiting to load image", e); + } catch (ExecutionException e) { + throw new JobManagerException("Failure occurred while loading image", e); + } + } + + //TODO: There is a race condition that causes this method to be called twice. This is awefully wasteful! private Future startImageLoad(final PrintJob printJob) { return Main.GLOBAL_EXECUTOR.submit(new Callable() { @Override public BufferedImage call() throws Exception { + RenderingContext twoDimensionalImage = aid.cache.getOrCreateIfMissing(imageIndexToBuild); + //This is a short circuit to stop from loading the image from file every time a renderer is created. + if (twoDimensionalImage.getPreTransformedImage() != null) { + return null; + } + return loadImageFromFile(printJob); } }); } - public final BufferedImage renderImage(BufferedImage imageToDisplay) throws JobManagerException { + public BufferedImage renderImage(BufferedImage imageToDisplay) throws JobManagerException { if (imageToDisplay != null) { return imageToDisplay; } - try { - imageToDisplay = scaleImageAndDetectEdges(aid.printJob); - } catch (InterruptedException |ExecutionException e) { - throw new JobManagerException("Couldn't load image", e); - } - - + imageToDisplay = scaleImageAndDetectEdges(aid.printJob); return imageToDisplay; } - public BufferedImage scaleImageAndDetectEdges(PrintJob printJob) throws InterruptedException, ExecutionException { + public BufferedImage scaleImageAndDetectEdges(PrintJob printJob) throws JobManagerException { SlicingProfile profile = printJob.getPrinter().getConfiguration().getSlicingProfile(); - BufferedImage image = newImage.get(); + BufferedImage image = waitForImage(); + Boolean scaleToFit = printJob.getPrinter().getConfiguration().getSlicingProfile().getTwoDimensionalSettings().isScaleImageToFitPrintArea(); if (scaleToFit != null && scaleToFit) { int actualWidth = profile.getxResolution() - image.getWidth(); diff --git a/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalPlatformPrintFileProcessor.java b/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalPlatformPrintFileProcessor.java index ef4e2e835..c9b743798 100644 --- a/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalPlatformPrintFileProcessor.java +++ b/host/src/main/java/org/area515/resinprinter/twodim/TwoDimensionalPlatformPrintFileProcessor.java @@ -2,12 +2,12 @@ import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; import java.util.concurrent.Future; +import javax.imageio.ImageIO; import javax.xml.bind.annotation.XmlTransient; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.area515.resinprinter.display.InappropriateDeviceException; import org.area515.resinprinter.exception.SliceHandlingException; import org.area515.resinprinter.job.AbstractPrintFileProcessor; @@ -16,98 +16,130 @@ import org.area515.resinprinter.job.Previewable; import org.area515.resinprinter.job.PrintJob; import org.area515.resinprinter.job.render.CurrentImageRenderer; -import org.area515.resinprinter.job.render.RenderedData; +import org.area515.resinprinter.job.render.RenderingContext; import org.area515.resinprinter.job.render.RenderingCache; import org.area515.resinprinter.printer.SlicingProfile.TwoDimensionalSettings; -import org.area515.resinprinter.server.Main; +import org.area515.resinprinter.services.PrintJobService; public abstract class TwoDimensionalPlatformPrintFileProcessor extends AbstractPrintFileProcessor implements Previewable { - private static final Logger logger = LogManager.getLogger(); - - public DataAid createDataAid(PrintJob printJob) throws JobManagerException { - return new DataAid(printJob); - } - + public class TwoDimensionalDataAid extends DataAid { + public int totalPlatformSlices; + public int totalExtrusionSlices; + public int platformSlices; + public int extrusionSlices; + public TwoDimensionalImageRenderer platformSizeInitializer; + + public TwoDimensionalDataAid(PrintJob printJob) throws JobManagerException { + super(printJob); + } + } + @Override public void prepareEnvironment(final File processingFile, final PrintJob printJob) throws JobManagerException { DataAid aid; try { aid = initializeJobCacheWithDataAid(printJob); - createRenderer(aid, this, Boolean.TRUE); + createTwoDimensionalRenderer(aid, Boolean.TRUE); } catch (InappropriateDeviceException e) { throw new JobManagerException("Couldn't create job", e); } } - private PlatformImageRenderer buildPlatformRenderer(DataAid dataAid, Object nextRenderingPointer, int totalPlatformSlices, TwoDimensionalImageRenderer platformSizeInitializer) { + @Override + public DataAid createDataAid(PrintJob printJob) throws JobManagerException { + return new TwoDimensionalDataAid(printJob); + } + + protected CurrentImageRenderer buildPlatformRenderer(DataAid dataAid, Object nextRenderingPointer, int totalPlatformSlices, CurrentImageRenderer platformSizeInitializer) { return new PlatformImageRenderer(dataAid, this, nextRenderingPointer, totalPlatformSlices, platformSizeInitializer); } + public abstract TwoDimensionalImageRenderer createTwoDimensionalRenderer(DataAid aid, Object imageIndexToBuild); + + @Override + public final CurrentImageRenderer createRenderer(DataAid aid, Object imageIndexToBuild) { + TwoDimensionalDataAid dataAid = (TwoDimensionalDataAid)aid; + if (dataAid.platformSlices <= 0 && dataAid.extrusionSlices <= 0) { + return null; + } + + if (dataAid.platformSizeInitializer == null) { + dataAid.platformSizeInitializer = createTwoDimensionalRenderer(dataAid, imageIndexToBuild); + } + + if (dataAid.platformSlices > 0) { + dataAid.platformSlices--; + return buildPlatformRenderer(dataAid, imageIndexToBuild, dataAid.totalPlatformSlices, dataAid.platformSizeInitializer); + } + + //Clear cache so that the text will render for the first time and not use the build platform cache + if (dataAid.extrusionSlices == dataAid.totalExtrusionSlices) { + dataAid.cache.clearCache(Boolean.TRUE); + dataAid.cache.clearCache(Boolean.FALSE); + } + + dataAid.extrusionSlices--; + return createTwoDimensionalRenderer(dataAid, imageIndexToBuild); + } + + protected void setupSlices(PrintJob printJob, TwoDimensionalDataAid dataAid, int suggestedPlatformSlices, int suggestedExtrusionSlices) { + int startingSlice = dataAid.customizer.getNextSlice(); + dataAid.platformSlices = suggestedPlatformSlices; + dataAid.extrusionSlices = suggestedExtrusionSlices; + dataAid.totalPlatformSlices = dataAid.platformSlices; + dataAid.totalExtrusionSlices = dataAid.extrusionSlices; + + if (startingSlice > dataAid.platformSlices) { + startingSlice -= dataAid.platformSlices; + dataAid.platformSlices = 0; + } else { + dataAid.platformSlices -= startingSlice; + startingSlice = 0; + } + + if (startingSlice > dataAid.extrusionSlices) { + startingSlice -= dataAid.extrusionSlices; + dataAid.extrusionSlices = 0; + } else { + dataAid.extrusionSlices -= startingSlice; + startingSlice = 0; + } + + //Total slices must be set to the actual total number of slices that are in the model + printJob.setTotalSlices(dataAid.totalPlatformSlices + dataAid.totalExtrusionSlices); + } + @Override public JobStatus processFile(PrintJob printJob) throws Exception { - DataAid dataAid = getDataAid(printJob); + TwoDimensionalDataAid dataAid = (TwoDimensionalDataAid)getDataAid(printJob); try { performHeader(dataAid); - - int startingSlice = dataAid.customizer.getNextSlice(); - int platformSlices = getSuggestedPlatformLayerCount(dataAid); - int totalPlatformSlices = platformSlices; - int extrusionSlices = getSuggested2DExtrusionLayerCount(dataAid); - - if (startingSlice >= platformSlices) { - platformSlices = 0; - startingSlice -= platformSlices; - } - extrusionSlices -= startingSlice; - - printJob.setTotalSlices(platformSlices + extrusionSlices); + setupSlices(printJob, dataAid, getSuggestedPlatformLayerCount(dataAid), getSuggested2DExtrusionLayerCount(dataAid)); RenderingCache printState = dataAid.cache; Boolean nextRenderingPointer = (Boolean)printState.getCurrentRenderingPointer(); - Future currentImage = null; - TwoDimensionalImageRenderer platformSizeInitializer = null; - if (platformSlices > 0) { - platformSizeInitializer = createRenderer(dataAid, this, nextRenderingPointer); - currentImage = Main.GLOBAL_EXECUTOR.submit(buildPlatformRenderer(dataAid, nextRenderingPointer, totalPlatformSlices, platformSizeInitializer)); - } else { - currentImage = Main.GLOBAL_EXECUTOR.submit(createRenderer(dataAid, this, nextRenderingPointer)); - } - while (platformSlices > 0 || extrusionSlices > 0) { + Future currentImage = startImageRendering(dataAid, nextRenderingPointer); + while (currentImage != null) { //Performs all of the duties that are common to most print files - JobStatus status = performPreSlice(dataAid, null); + JobStatus status = performPreSlice(dataAid, dataAid.currentlyRenderingImage.getScriptEngine(), null); if (status != null) { return status; } //Wait until the image has been properly rendered. Most likely, it's already done though... - BufferedImage image = currentImage.get().getPrintableImage(); + RenderingContext rendered = currentImage.get(); //Now that the image has been rendered, we can make the switch to use the pointer that we were using while we were rendering printState.setCurrentRenderingPointer(nextRenderingPointer); - //Get the next pointer in line to start rendering the image into + //Get the next pointer in line to start rendering the next image into nextRenderingPointer = !nextRenderingPointer; - //Render the next image while we are waiting for the current image to cure - if (platformSlices > 1) { - currentImage = Main.GLOBAL_EXECUTOR.submit(buildPlatformRenderer(dataAid, nextRenderingPointer, totalPlatformSlices, platformSizeInitializer)); - platformSlices--; - } else if (extrusionSlices > 1) { - //Clear cache so that the text will render for the first time and not use the build platform cache - if (platformSlices == 1) { - platformSlices--; - dataAid.cache.clearCache(Boolean.TRUE); - dataAid.cache.clearCache(Boolean.FALSE); - } else { - extrusionSlices--; - } - currentImage = Main.GLOBAL_EXECUTOR.submit(createRenderer(dataAid, this, nextRenderingPointer)); - } else { - extrusionSlices--; - } - + //Start to render the next image while we are waiting for the current image to cure + currentImage = startImageRendering(dataAid, nextRenderingPointer); + //Performs all of the duties that are common to most print files - status = printImageAndPerformPostProcessing(dataAid, image); + status = printImageAndPerformPostProcessing(dataAid, rendered.getScriptEngine(), rendered.getPrintableImage()); if (status != null) { return status; } @@ -149,21 +181,19 @@ public int getSuggested2DExtrusionLayerCount(DataAid aid) { return (int)Math.round(extrusionHeight / aid.inkConfiguration.getSliceHeight()); } - @Override public BufferedImage renderPreviewImage(final DataAid aid) throws SliceHandlingException { try { - int platformSlices = getSuggestedPlatformLayerCount(aid); - TwoDimensionalImageRenderer extrusionRenderer = createRenderer(aid, this, Boolean.TRUE); - CurrentImageRenderer targetRenderer = aid.customizer.getNextSlice() < platformSlices? - buildPlatformRenderer(aid, Boolean.TRUE, platformSlices, extrusionRenderer): - extrusionRenderer; - - return targetRenderer.call().getPrintableImage(); - } catch (JobManagerException e) { + TwoDimensionalDataAid dataAid = (TwoDimensionalDataAid)aid; + setupSlices(aid.printJob, dataAid, getSuggestedPlatformLayerCount(dataAid), getSuggested2DExtrusionLayerCount(dataAid)); + CurrentImageRenderer renderer = createRenderer(dataAid, Boolean.TRUE); + if (renderer == null) { + return ImageIO.read(PrintJobService.class.getResourceAsStream("noimageavailable.png")); + } + + return renderer.call().getPrintableImage(); + } catch (IOException | JobManagerException e) { throw new SliceHandlingException(e); } } - - public abstract TwoDimensionalImageRenderer createRenderer(DataAid aid, AbstractPrintFileProcessor processor, Object imageIndexToBuild); } diff --git a/host/src/main/java/org/area515/resinprinter/usbimport/USBUploader.java b/host/src/main/java/org/area515/resinprinter/usbimport/USBUploader.java index 9b4ebec6f..7664a04a7 100644 --- a/host/src/main/java/org/area515/resinprinter/usbimport/USBUploader.java +++ b/host/src/main/java/org/area515/resinprinter/usbimport/USBUploader.java @@ -61,7 +61,7 @@ private void uploadFromRoot(File root) { } @Override - public void start(URI uri) { + public void start(URI uri, String settings) { masterRoots = new HashMap<>(); for (File root : listRoots()) { masterRoots.put(root.getAbsolutePath(), root); diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/CronFeature.java b/host/src/main/java/org/area515/resinprinter/util/cron/CronFeature.java index 7809fa7ab..a957ff3ea 100644 --- a/host/src/main/java/org/area515/resinprinter/util/cron/CronFeature.java +++ b/host/src/main/java/org/area515/resinprinter/util/cron/CronFeature.java @@ -1,48 +1,124 @@ package org.area515.resinprinter.util.cron; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.apache.commons.beanutils.BeanUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.area515.resinprinter.plugin.Feature; -import org.area515.resinprinter.server.Main; +import org.area515.util.DynamicJSonSettings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.ThreadFactoryBuilder; public class CronFeature implements Feature { private static final Logger logger = LogManager.getLogger(); private List taskList = new ArrayList(); + public static ScheduledExecutorService CRON_EXECUTOR = new ScheduledThreadPoolExecutor(10, new ThreadFactoryBuilder().setNameFormat("CronThread-%d").setDaemon(true).build()); - public static class CronTask implements Callable { + public static class CronTask implements Callable { private String taskName; private Runnable runnable; - private Callable callable; + private Callable callable; private CronPredictor predictor; - private ScheduledFuture future; + private ScheduledFuture future; private boolean canceledTask; - public CronTask(String taskName, Runnable runnable, SchedulingPattern pattern) { - this.taskName = taskName; - this.runnable = runnable; - this.predictor = new CronPredictor(pattern); - } + private String taskClassName; + private String cronString; + private DynamicJSonSettings taskSettings; - public CronTask(String taskName, Callable callable, SchedulingPattern pattern) { + public CronTask() { + } + + @JsonProperty + public DynamicJSonSettings getTaskSettings() { + return taskSettings; + } + public void setTaskSettings(DynamicJSonSettings taskSettings) { + this.taskSettings = taskSettings; + } + + @JsonProperty + public void setCronString(String cronString) { + this.cronString = cronString; + } + public void setTaskName(String taskName) { this.taskName = taskName; - this.callable = callable; - this.predictor = new CronPredictor(pattern); } - + + @JsonProperty + public String getTaskName() { + return taskName; + } + public String getCronString() { + return cronString; + } + + @JsonProperty + public String getTaskClassName() { + return taskClassName; + } + public void setTaskClassName(String taskClassName) { + this.taskClassName = taskClassName; + } + + private void initializeIfNecessary() throws RejectedExecutionException { + //If we are already initialized, let's get out of here. + if (predictor != null) { + return; + } + + predictor = new CronPredictor(cronString); + + if (taskClassName == null) { + throw new RejectedExecutionException("Cron task:" + taskName + " doesn't have a taskClassName set"); + } + + try { + Class runnableOrCallableClass = Class.forName(taskClassName); + Object runnableOrCallable = runnableOrCallableClass.newInstance(); + if (runnableOrCallable instanceof Runnable) { + runnable = (Runnable)runnableOrCallable; + } else if (runnableOrCallable instanceof Callable) { + callable = (Callable)runnableOrCallable; + } else { + throw new RejectedExecutionException(runnableOrCallable + " class:" + runnableOrCallableClass + " not an instance of Runnable or Callable"); + } + BeanUtils.populate(runnableOrCallable, getTaskSettings() == null?null:getTaskSettings().getSettings()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + logger.error("Couldn't create class:" + taskClassName, e); + } + } + + @SuppressWarnings("unchecked") public synchronized void scheduleNextRun() throws RejectedExecutionException { if (canceledTask) { throw new RejectedExecutionException("Task cannot be executed again since it has been cancelled."); } - future = Main.GLOBAL_EXECUTOR.schedule(this, predictor.nextMatchingTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + + initializeIfNecessary(); + + long nextExecution = predictor.nextMatchingTime(); + future = CRON_EXECUTOR.schedule(this, nextExecution - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + + logger.info("Scheduled task:" + taskName + " for:" + new Date(nextExecution)); } public synchronized boolean cancel() { @@ -75,15 +151,40 @@ public Object call() throws Exception { throw t; } } + + public String toString() { + return taskName; + } } @Override - public void start(URI uri) throws Exception { + public void start(URI uri, String cronTasks) throws Exception { + if (cronTasks == null) { + logger.info("No cron tasks were setup"); + return; + } + ObjectMapper mapper = new ObjectMapper(new JsonFactory()); + try { + CronTask[] cronTask = mapper.readValue(cronTasks, new TypeReference(){}); + Collections.addAll(taskList, cronTask); + } catch (IOException e) { + throw new IllegalArgumentException(cronTasks + " didn't parse correctly.", e); + } + + for (CronTask task : taskList) { + try { + task.scheduleNextRun(); + } catch (RejectedExecutionException e) { + logger.error("Couldn't schedule task:" + task.getTaskName(), e); + } + } } @Override public void stop() { - + for (CronTask task : taskList) { + task.cancel(); + } } } diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java b/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java index f1bc42034..938ae8d6a 100644 --- a/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java +++ b/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java @@ -308,28 +308,4 @@ public synchronized long nextMatchingTime() { public synchronized Date nextMatchingDate() { return new Date(nextMatchingTime()); } - - public static void main(String[] args) { - Date currentDate = new Date(); - CronPredictor cron = new CronPredictor(currentDate.getMinutes() + " * * * *", currentDate); - System.out.println(currentDate + "->" + cron.nextMatchingDate()); - - Calendar cal = Calendar.getInstance(); - /*cal.set(2014, 6, 15, 8, 8); - cron = new CronPredictor("* * * 6 *", cal.getTime()); - System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); - - cal.set(2014, 6, 15, 8, 8); - cron = new CronPredictor("* * * 7 *", cal.getTime()); - System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); - - cal.set(2014, 6, 15, 8, 8); - cron = new CronPredictor("* * * 8 *", cal.getTime()); - System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());*/ - - cal.set(2014, 6, 15, 8, 8); - cron = new CronPredictor("* 1 * * *", cal.getTime()); - System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); - - } } diff --git a/host/src/main/java/org/area515/resinprinter/util/security/PhotonicCrypto.java b/host/src/main/java/org/area515/resinprinter/util/security/PhotonicCrypto.java index a3c3214a8..dc917d31c 100644 --- a/host/src/main/java/org/area515/resinprinter/util/security/PhotonicCrypto.java +++ b/host/src/main/java/org/area515/resinprinter/util/security/PhotonicCrypto.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -151,7 +150,7 @@ public void setRemoteCrypto(PhotonicCrypto remoteCrypto) { } public boolean isAsymetricEncryption(String algorithm) { - return algorithm != null && algorithm.equalsIgnoreCase("rsa"); + return algorithm != null && (algorithm.equalsIgnoreCase("rsa") || algorithm.equalsIgnoreCase("EC")); } public Message buildCertificateTrustMessage(UUID toUser) throws CertificateEncodingException { @@ -292,20 +291,19 @@ public Message buildKeyExchange() throws InvalidNameException, CertificateExpire keyMessage.setTo(UUID.fromString(userIdAndName[0])); userIdAndName = LdapUtils.getUserIdAndName(((X509Certificate)signer.getCertificate()).getSubjectDN().getName()); keyMessage.setFrom(UUID.fromString(userIdAndName[0])); - keyMessage.setEncryptionAlgorithm("RSA"); + keyMessage.setEncryptionAlgorithm(signer.getPrivateKey().getAlgorithm());//RSA only right now remoteCrypto.encryptor.checkValidity(new Date()); KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(AES_KEY_SIZE * 8); byte[] keyCreatorSeed = keyGen.generateKey().getEncoded(); byte[] keyBytes = keyGen.generateKey().getEncoded(); - Cipher encrypt=Cipher.getInstance("RSA"); + Cipher encrypt=Cipher.getInstance(remoteCrypto.encryptor.getPublicKey().getAlgorithm());//RSA only right now encrypt.init(Cipher.ENCRYPT_MODE, remoteCrypto.encryptor.getPublicKey()); encrypt.update(Base64.getEncoder().encode(keyCreatorSeed)); encrypt.update(new byte[]{10}); byte[] ivAndKey = encrypt.doFinal(Base64.getEncoder().encode(keyBytes)); keyMessage.setData(ivAndKey); - verifier.checkValidity(new Date()); Signature sig = Signature.getInstance(verifier.getSigAlgName()); sig.initSign(signer.getPrivateKey()); diff --git a/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetectorSettings.java b/host/src/main/java/org/area515/util/DynamicJSonSettings.java similarity index 60% rename from host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetectorSettings.java rename to host/src/main/java/org/area515/util/DynamicJSonSettings.java index e7682b47c..357ca2e4c 100644 --- a/host/src/main/java/org/area515/resinprinter/inkdetection/PrintMaterialDetectorSettings.java +++ b/host/src/main/java/org/area515/util/DynamicJSonSettings.java @@ -1,4 +1,4 @@ -package org.area515.resinprinter.inkdetection; +package org.area515.util; import java.util.HashMap; @@ -9,24 +9,28 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -public class PrintMaterialDetectorSettings { - private HashMap settings; +public class DynamicJSonSettings { + private HashMap settings; @JsonAnyGetter - public HashMap getSettings() { + public HashMap getSettings() { return settings; } - public void setSettings(HashMap settings) { + public void setSettings(HashMap settings) { this.settings = settings; } @JsonAnySetter - public void putSettings(String key, String value) { + public void putSettings(String key, Object value) { if (settings == null) { settings = new HashMap<>(); } settings.put(key, value); } + + public String toString() { + return settings + ""; + } } diff --git a/host/src/main/java/org/area515/util/ExceptionMarshaller.java b/host/src/main/java/org/area515/util/ExceptionMarshaller.java index d5b455bec..acbf9efc3 100644 --- a/host/src/main/java/org/area515/util/ExceptionMarshaller.java +++ b/host/src/main/java/org/area515/util/ExceptionMarshaller.java @@ -15,6 +15,17 @@ public class ExceptionMarshaller implements ExceptionMapper { public Response toResponse(Exception e) { logger.error("Error caught by exception marshaller and relayed to browser", e); - return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN).entity(e.getMessage()).build(); + + Throwable t = e; + //TODO: Adding this in causes popups to occur when a file is clicked in the printables area that doesn't support image preview. + /*while (t.getCause() != null) { + t = t.getCause(); + }*/ + + if (t.getMessage() == null) { + return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN).entity("Internal server error").build(); + } + + return Response.status(Status.BAD_REQUEST).type(MediaType.TEXT_PLAIN).entity(t.getMessage()).build(); } } \ No newline at end of file diff --git a/host/src/main/java/org/area515/util/Log4jTimer.java b/host/src/main/java/org/area515/util/Log4jUtil.java similarity index 79% rename from host/src/main/java/org/area515/util/Log4jTimer.java rename to host/src/main/java/org/area515/util/Log4jUtil.java index ba5530d8d..1f42fbfc8 100644 --- a/host/src/main/java/org/area515/util/Log4jTimer.java +++ b/host/src/main/java/org/area515/util/Log4jUtil.java @@ -1,13 +1,30 @@ package org.area515.util; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import javax.imageio.ImageIO; + import org.apache.logging.log4j.ThreadContext; -public class Log4jTimer { +public class Log4jUtil { public static Map GLOBAL = new HashMap<>(); + public static boolean logImage(final BufferedImage img, final String fileName) { + if (img == null) { + return false; + } + try { + ImageIO.write(img, "png", new File(fileName.replaceAll(":", "").replaceAll("/", "").replaceAll("\\\\", ""))); + return true; + } catch (IOException e) { + return false; + } + } + public static long startTimer(String timerName) { long newTime = System.currentTimeMillis(); ThreadContext.put(timerName, newTime + ""); diff --git a/host/src/main/java/org/area515/util/TemplateEngine.java b/host/src/main/java/org/area515/util/TemplateEngine.java index 57957e2fa..6895d8cc9 100644 --- a/host/src/main/java/org/area515/util/TemplateEngine.java +++ b/host/src/main/java/org/area515/util/TemplateEngine.java @@ -2,10 +2,13 @@ import java.awt.Color; import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -13,6 +16,7 @@ import javax.script.Bindings; import javax.script.CompiledScript; +import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptException; @@ -94,16 +98,22 @@ public static String buildData(PrintJob job, Printer printer, String templateStr root.put("now", new Date()); root.put("shutterOpen", printer.isShutterOpen()); root.put("bulbHours", printer.getCachedBulbHours()); - root.put("CURSLICE", job.getCurrentSlice()); + root.put("CURSLICE", job.getRenderingSlice()); root.put("LayerThickness", printer.getConfiguration().getSlicingProfile().getSelectedInkConfig().getSliceHeight()); root.put("ZDir", printer.getConfiguration().getSlicingProfile().getDirection().getVector()); - root.put("ZLiftRate", job.getZLiftSpeed()); - root.put("ZLiftDist", job.getZLiftDistance()); - Double buildArea = job.getPrintFileProcessor().getBuildAreaMM(job); +//TODO: Create a retract calculator +//TODO: Create a computed ZLiftSpeed +//TODO: Create a computed ZLiftDistance +//TODO: What about race conditions on these varaibles? +root.put("ZLiftRate", job.getZLiftSpeed()); +root.put("ZLiftDist", job.getZLiftDistance()); +Double buildArea = job.getPrintFileProcessor().getBuildAreaMM(job); root.put("buildAreaMM", buildArea == null || buildArea < 0?null:buildArea); root.put("LayerTime", printer.getConfiguration().getSlicingProfile().getSelectedInkConfig().getExposureTime()); root.put("FirstLayerTime", printer.getConfiguration().getSlicingProfile().getSelectedInkConfig().getFirstLayerExposureTime()); root.put("NumFirstLayers", printer.getConfiguration().getSlicingProfile().getSelectedInkConfig().getNumberOfFirstLayers()); +//TODO: Computed Exposure Time +//TODO: get all other computed values root.put("SlideTiltVal", printer.getConfiguration().getSlicingProfile().getSlideTiltValue()); root.put("buildPlatformXPixels", printer.getConfiguration().getSlicingProfile().getxResolution()); root.put("buildPlatformYPixels", printer.getConfiguration().getSlicingProfile().getyResolution()); @@ -146,8 +156,8 @@ public static String buildData(PrintJob job, Printer printer, String templateStr } public static Object runScriptInImagingContext( - BufferedImage imageToDisplay, - BufferedImage targetImage, + BufferedImage platformImage, + BufferedImage printImage, PrintJob printJob, Printer printer, ScriptEngine scriptEngine, @@ -156,35 +166,46 @@ public static Object runScriptInImagingContext( String scriptName, boolean clearMasterImage) throws ScriptException { - Graphics graphics = imageToDisplay.getGraphics(); + Graphics graphics = platformImage.getGraphics(); if (clearMasterImage) { graphics.setColor(Color.black); - graphics.fillRect(0, 0, imageToDisplay.getWidth(), imageToDisplay.getHeight()); + graphics.fillRect(0, 0, platformImage.getWidth(), platformImage.getHeight()); } graphics.setColor(Color.white); if (overrides == null) { overrides = new HashMap<>(); } - overrides.put("buildPlatformImage", imageToDisplay); + overrides.put("buildPlatformImage", platformImage); overrides.put("buildPlatformGraphics", graphics); - overrides.put("buildPlatformRaster", imageToDisplay.getRaster()); - overrides.put("printImage", targetImage); - overrides.put("printGraphics", targetImage.getGraphics()); - overrides.put("printRaster", targetImage.getRaster()); - overrides.put("centerX", imageToDisplay.getWidth() / 2);//int centerX = aid.xResolution / 2; - overrides.put("centerY", imageToDisplay.getHeight() / 2);//int centerY = aid.yResolution / 2; + overrides.put("buildPlatformRaster", platformImage.getRaster()); + overrides.put("printImage", printImage); + overrides.put("printGraphics", printImage.getGraphics()); + overrides.put("printRaster", printImage.getRaster()); + overrides.put("centerX", platformImage.getWidth() / 2);//int centerX = aid.xResolution / 2; + overrides.put("centerY", platformImage.getHeight() / 2);//int centerY = aid.yResolution / 2; + + Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); + if (!bindings.containsKey("exposureTimers")) { + ArrayList timers = new ArrayList<>(); + bindings.put("exposureTimers", timers); + } - return TemplateEngine.runScript(printJob, printer, scriptEngine, calculatorScript, scriptName, overrides); + if (!bindings.containsKey("printableShape")) { + bindings.put("printableShape", new Rectangle(0, 0, printImage.getWidth(), printImage.getHeight())); + } + + Object returnValue = TemplateEngine.runScript(printJob, printer, scriptEngine, calculatorScript, scriptName, overrides); + return returnValue; } public static Object runScript(PrintJob job, Printer printer, ScriptEngine engine, String script, String scriptName, Map overrides) throws ScriptException { - Bindings bindings = engine.createBindings(); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); bindings.put("now", new Date()); bindings.put("$shutterOpen", printer.isShutterOpen()); Integer bulbHours = printer.getCachedBulbHours(); bindings.put("$bulbHours", bulbHours == null || bulbHours < 0?Double.NaN:new Double(bulbHours)); - bindings.put("$CURSLICE", job.getCurrentSlice()); + bindings.put("$CURSLICE", job.getRenderingSlice()); bindings.put("$LayerThickness", printer.getConfiguration().getSlicingProfile().getSelectedInkConfig().getSliceHeight()); bindings.put("$ZDir", printer.getConfiguration().getSlicingProfile().getDirection().getVector()); bindings.put("$ZLiftRate", job.getZLiftSpeed()); @@ -203,7 +224,7 @@ public static Object runScript(PrintJob job, Printer printer, ScriptEngine engin bindings.put("job", job); bindings.put("printer", printer); bindings.put(ScriptEngine.FILENAME, scriptName); - + if (overrides != null) { Iterator> entries = overrides.entrySet().iterator(); while (entries.hasNext()) { diff --git a/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java b/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java index a3fd1abba..dcbfdf1b3 100644 --- a/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java +++ b/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java @@ -4,6 +4,7 @@ import java.awt.image.BufferedImage; import java.io.File; +import javax.script.ScriptEngine; import javax.script.ScriptException; import org.area515.resinprinter.display.InappropriateDeviceException; @@ -18,6 +19,7 @@ import org.area515.resinprinter.printer.SlicingProfile; import org.area515.resinprinter.printer.SlicingProfile.InkConfig; import org.area515.resinprinter.serial.SerialCommunicationsPort; +import org.area515.resinprinter.server.HostProperties; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,11 +29,19 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.modules.junit4.PowerMockRunner; -@PowerMockIgnore("javax.management.*") +@PowerMockIgnore({ + "javax.management.*", + "com.sun.xml.bind.v2.*", + "com.sun.xml.bind.v2.model.impl.*", + "javax.xml.bind.*", + "javax.xml.datatype.*", + "javax.xml.namespace.*", + "javax.xml.transform.*"}) @RunWith(PowerMockRunner.class) public class AbstractPrintFileProcessorTest { private BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_4BYTE_ABGR); - + private ScriptEngine scriptEngine = HostProperties.Instance().buildScriptEngine(); + public static AbstractPrintFileProcessor createNewPrintFileProcessor() { return Mockito.mock(AbstractPrintFileProcessor.class, Mockito.CALLS_REAL_METHODS); } @@ -72,13 +82,13 @@ public void EnsureMethodsThrowExceptionIfNotInitialized() throws Exception { DataAid aid = null; try { //applyimagetransform - processor.applyBulbMask(aid, null, 0 ,0); + processor.applyBulbMask(aid, scriptEngine, null, 0 ,0); Assert.fail("Failed to throw IllegalStateException."); } catch (IllegalStateException e) { } try { //applyimagetransform - processor.applyImageTransforms(aid, null); + processor.applyImageTransforms(aid, scriptEngine, null); Assert.fail("Failed to throw IllegalStateException."); } catch (IllegalStateException e) { } @@ -93,12 +103,12 @@ public void EnsureMethodsThrowExceptionIfNotInitialized() throws Exception { } catch (IllegalStateException e) { } try { - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Assert.fail("Failed to throw IllegalStateException."); } catch (IllegalStateException e) { } try { - processor.performPreSlice(aid, null); + processor.performPreSlice(aid, scriptEngine, null); Assert.fail("Failed to throw IllegalStateException."); } catch (IllegalStateException e) { } @@ -113,7 +123,7 @@ public void unsupportedBuildAreaDoesntBreakProjectorGradient() throws Inappropri Mockito.when(printJob.getPrintFileProcessor().getBuildAreaMM(Mockito.any(PrintJob.class))).thenReturn(null); DataAid aid = processor.initializeJobCacheWithDataAid(printJob); //apply image transform - processor.applyBulbMask(aid, graphics, 0, 0); + processor.applyBulbMask(aid, scriptEngine, graphics, 0, 0); // processor.applyImageTransforms(aid, graphics, 0, 0); //processor.applyImageTransforms(aid, null, 0, 0); } @@ -126,7 +136,7 @@ public void unsupportedBuildAreaDoesntBreakLiftDistanceCalculator() throws Excep Mockito.when(printJob.getPrintFileProcessor().getBuildAreaMM(Mockito.any(PrintJob.class))).thenReturn(null); DataAid aid = processor.initializeJobCacheWithDataAid(printJob); aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean()); } @@ -139,7 +149,7 @@ public void getExceptionWhenWeReturnGarbageForLiftDistanceCalculator() throws Ex DataAid aid = processor.initializeJobCacheWithDataAid(printJob); try { aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), true); } catch (IllegalArgumentException e) { Assert.assertEquals("The result of your lift distance script needs to evaluate to an instance of java.lang.Number", e.getMessage()); @@ -155,7 +165,7 @@ public void noNullPointerWhenWeReturnNull() throws Exception { DataAid aid = processor.initializeJobCacheWithDataAid(printJob); try { aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean()); } catch (IllegalArgumentException e) { Assert.assertEquals("The result of your lift distance script needs to evaluate to an instance of java.lang.Number", e.getMessage()); @@ -173,7 +183,7 @@ public void usingUnsupportedBuildAreaWithLiftDistance() throws Exception { DataAid aid = processor.initializeJobCacheWithDataAid(printJob); try { aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Assert.fail("Must throw InappropriateDeviceException"); } catch (InappropriateDeviceException e) { Mockito.verify(printJob.getPrintFileProcessor(), Mockito.times(2)).getBuildAreaMM(Mockito.any(PrintJob.class)); @@ -181,7 +191,7 @@ public void usingUnsupportedBuildAreaWithLiftDistance() throws Exception { Mockito.when(printJob.getPrinter().getConfiguration().getSlicingProfile().getZLiftDistanceGCode()).thenReturn("G99 ${1 + buildAreaMM * 2} ;dependent on buildArea"); try { aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Mockito.verify(printJob.getPrintFileProcessor(), Mockito.times(5)).getBuildAreaMM(Mockito.any(PrintJob.class)); } catch (InappropriateDeviceException e) { Assert.fail("Should not throw InappropriateDeviceException"); @@ -198,7 +208,7 @@ public void syntaxErrorInTemplate() throws Exception { DataAid aid = processor.initializeJobCacheWithDataAid(printJob); try { aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); Assert.fail("Must throw InappropriateDeviceException"); } catch (InappropriateDeviceException e) { Mockito.verify(printJob.getPrintFileProcessor(), Mockito.times(2)).getBuildAreaMM(Mockito.any(PrintJob.class)); @@ -232,7 +242,7 @@ public String answer(InvocationOnMock invocation) throws Throwable { } }); aid.customizer.setNextStep(PrinterStep.PerformExposure); - processor.printImageAndPerformPostProcessing(aid, image); + processor.printImageAndPerformPostProcessing(aid, scriptEngine, image); //The two executes are for getZLiftDistanceGCode and the life gcode itself Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(2)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean()); } diff --git a/host/src/test/java/org/area515/resinprinter/security/KeystoreSecurityTest.java b/host/src/test/java/org/area515/resinprinter/security/KeystoreSecurityTest.java index 0057b6068..87b1189a4 100644 --- a/host/src/test/java/org/area515/resinprinter/security/KeystoreSecurityTest.java +++ b/host/src/test/java/org/area515/resinprinter/security/KeystoreSecurityTest.java @@ -101,7 +101,7 @@ public void createKeystoreAndAddUser() throws Exception { String testUsername = "<>!@#$%^&*()~` ,.;'[]-=?:\"{}|\\frank,cn=Wes";//Ensure they can't perform a name spoof - service1.start(null); + service1.start(null, null); Assert.assertNull(service1.login(testUsername, testUsername, null)); PhotonicUser insertUser = new PhotonicUser(testUsername, testUsername, null, testUsername, new String[]{PhotonicUser.FULL_RIGHTS}, false); PhotonicUser user = service1.update(insertUser); diff --git a/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java b/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java index 2facc6aa2..c2c1be8f1 100644 --- a/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java +++ b/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java @@ -8,6 +8,7 @@ import java.util.UUID; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; @@ -66,8 +67,9 @@ public void createListDeleteNewMachineConfig() throws JAXBException { config.setName(configName); MachineService.INSTANCE.saveMachineConfiguration(config); Assert.assertTrue(isFound(MachineService.INSTANCE.getMachineConfigurations(), configName)); - MachineService.INSTANCE.deleteMachineConfiguration(configName); + Assert.assertEquals(Response.Status.OK.getStatusCode(), MachineService.INSTANCE.deleteMachineConfiguration(configName).getStatus()); Assert.assertFalse(isFound(MachineService.INSTANCE.getMachineConfigurations(), configName)); + Assert.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), MachineService.INSTANCE.deleteMachineConfiguration(configName).getStatus()); } @Test @@ -77,8 +79,9 @@ public void createListDeleteNewSlicingProfileConfig() throws JAXBException { config.setName(configName); MachineService.INSTANCE.saveSlicingProfile(config); Assert.assertTrue(isFound(MachineService.INSTANCE.getSlicingProfiles(), configName)); - MachineService.INSTANCE.deleteSlicingProfile(configName); + Assert.assertEquals(Response.Status.OK.getStatusCode(), MachineService.INSTANCE.deleteSlicingProfile(configName).getStatus()); Assert.assertFalse(isFound(MachineService.INSTANCE.getSlicingProfiles(), configName)); + Assert.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), MachineService.INSTANCE.deleteSlicingProfile(configName).getStatus()); } @Test diff --git a/host/src/test/java/org/area515/resinprinter/services/PrinterServiceTest.java b/host/src/test/java/org/area515/resinprinter/services/PrinterServiceTest.java new file mode 100644 index 000000000..88c1ee1f6 --- /dev/null +++ b/host/src/test/java/org/area515/resinprinter/services/PrinterServiceTest.java @@ -0,0 +1,20 @@ +package org.area515.resinprinter.services; + +import java.util.UUID; + +import org.area515.resinprinter.display.InappropriateDeviceException; +import org.area515.resinprinter.printer.Printer; +import org.junit.Assert; +import org.junit.Test; + +public class PrinterServiceTest { + @Test + public void deletePrinterTest() throws InappropriateDeviceException { + Printer printer = PrinterService.INSTANCE.createTemplatePrinter(); + String printerName = UUID.randomUUID().toString(); + printer.getConfiguration().setName(printerName); + PrinterService.INSTANCE.savePrinter(printer); + PrinterService.INSTANCE.deletePrinter(printerName); + Assert.assertTrue(!MachineServiceTest.isFound(PrinterService.INSTANCE.getPrinters(), printerName)); + } +} diff --git a/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java b/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java index 11e458b46..4fd20edea 100644 --- a/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java +++ b/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java @@ -16,9 +16,11 @@ import org.area515.resinprinter.security.SerializeMessageAsJson; import org.area515.resinprinter.security.keystore.RendezvousExchange; import org.area515.resinprinter.services.MachineServiceTest; +import org.area515.resinprinter.services.PrinterServiceTest; import org.area515.resinprinter.services.TestScriptAndTemplating; import org.area515.resinprinter.slice.CheckSlicePoints; import org.area515.resinprinter.stl.ZSlicingGeometry; +import org.area515.resinprinter.util.cron.RunCronPredictor; import org.area515.util.IOUtilitiesTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -45,6 +47,8 @@ TestByteSession.class, TestCustomizer.class, MachineServiceTest.class, + RunCronPredictor.class, + PrinterServiceTest.class }) public class FullTestSuite { diff --git a/host/src/test/java/org/area515/resinprinter/util/cron/RunCronPredictor.java b/host/src/test/java/org/area515/resinprinter/util/cron/RunCronPredictor.java new file mode 100644 index 000000000..6b92b3095 --- /dev/null +++ b/host/src/test/java/org/area515/resinprinter/util/cron/RunCronPredictor.java @@ -0,0 +1,33 @@ +package org.area515.resinprinter.util.cron; + +import java.util.Calendar; +import java.util.Date; + +import org.junit.Test; + +public class RunCronPredictor { + @Test + public void main() { + Date currentDate = new Date(); + CronPredictor cron = new CronPredictor(currentDate.getMinutes() + " * * * *", currentDate); + System.out.println(currentDate + "->" + cron.nextMatchingDate()); + + Calendar cal = Calendar.getInstance(); + /*cal.set(2014, 6, 15, 8, 8); + cron = new CronPredictor("* * * 6 *", cal.getTime()); + System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); + + cal.set(2014, 6, 15, 8, 8); + cron = new CronPredictor("* * * 7 *", cal.getTime()); + System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); + + cal.set(2014, 6, 15, 8, 8); + cron = new CronPredictor("* * * 8 *", cal.getTime()); + System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());*/ + + cal.set(2014, 6, 15, 8, 8); + cron = new CronPredictor("* 1 * * *", cal.getTime()); + System.out.println(cal.getTime() + "->" + cron.nextMatchingDate()); + } +} + diff --git a/host/src/test/resources/org/area515/resinprinter/slice/50x100a.stl b/host/src/test/resources/org/area515/resinprinter/slice/50x100a.stl new file mode 100644 index 000000000..bd298dd75 Binary files /dev/null and b/host/src/test/resources/org/area515/resinprinter/slice/50x100a.stl differ diff --git a/host/src/test/resources/org/area515/resinprinter/slice/points.json b/host/src/test/resources/org/area515/resinprinter/slice/points.json index 3c92efccb..26a2818b2 100644 --- a/host/src/test/resources/org/area515/resinprinter/slice/points.json +++ b/host/src/test/resources/org/area515/resinprinter/slice/points.json @@ -30,6 +30,58 @@ "pixelsPerMMY" : 5.0, "zSliceResolution" : 0.1, "zSliceOffset" : 0.05 +}, { + "points" : [ { + "x" : 502, + "y" : 230, + "sliceNumber" : 1 + }, { + "x" : 603, + "y" : 230, + "sliceNumber" : 1 + }, { + "x" : 596, + "y" : 235, + "sliceNumber" : 1 + }, { + "x" : 750, + "y" : 235, + "sliceNumber" : 1 + }, { + "x" : 749, + "y" : 240, + "sliceNumber" : 1 + }, { + "x" : 593, + "y" : 240, + "sliceNumber" : 1 + }, { + "x" : 508, + "y" : 135, + "sliceNumber" : 1 + }, { + "x" : 751, + "y" : 135, + "sliceNumber" : 1 + }, { + "x" : 749, + "y" : 370, + "sliceNumber" : 4 + }, { + "x" : 750, + "y" : 135, + "sliceNumber" : 14 + }, { + "x" : 751, + "y" : 135, + "sliceNumber" : 19 + } ], + "fileName" : "50x100a.stl", + "stlScale" : 1.0, + "pixelsPerMMX" : 5.0, + "pixelsPerMMY" : 5.0, + "zSliceResolution" : 0.1, + "zSliceOffset" : 0.0 }, { "points" : [ { "x" : 511, diff --git a/host/web.keystore b/host/web.keystore deleted file mode 100644 index 236f345e6..000000000 Binary files a/host/web.keystore and /dev/null differ