11 Commits

Author SHA1 Message Date
YTKAB0BP fb16805739 Bump version 2024-11-18 20:22:26 +03:00
YTKAB0BP a8c108aa85 Weight/Length filament info in stats 2024-11-18 20:20:39 +03:00
YTKAB0BP 1451fe274c Calibration models library 2024-11-18 19:42:18 +03:00
YTKAB0BP 4246b6ff8c I forgor 💀 (To add check for WebView debugging) 2024-11-17 23:48:32 +03:00
YTKAB0BP 26b4ce849a Null-check findPrint in filament selection 2024-11-17 23:23:56 +03:00
YTKAB0BP 89f960173c Fix gap fill color 2024-11-15 18:39:20 +03:00
YTKAB0BP da8974dbf1 Bump version 2024-11-15 10:55:44 +03:00
YTKAB0BP c4da71792b Notify subs view in changelog 2024-11-15 10:52:27 +03:00
YTKAB0BP 53e20fdaae Time estimation menu 2024-11-15 03:26:33 +03:00
YTKAB0BP 7f56cba79d Update info about printer profiles 2024-11-13 21:30:30 +03:00
YTKAB0BP 596f0f6dda Fix unfold menus soft-lock after tab is switched to another 2024-11-13 21:26:37 +03:00
39 changed files with 960 additions and 40 deletions
+6
View File
@@ -12,6 +12,12 @@ It is based on PrusaSlicer's core and well optimized for Android touchscreen int
# Quick Start
Just download APK from [Releases tab](https://github.com/utkabobr/SliceBeam/releases/latest) and follow setup instructions. Google Play builds will be available later.
# Where to get printer profiles?
It is recommended to use PrusaSlicer's profiles, Slice Beam profiles are using Prusa's format. You can import PrusaSlicer's profiles and vice versa.
But, there is an experimental OrcaSlicer profile support, please open Issue tickets with your .orca_printer files, so I can check what wents wrong on your specific case.
# Some screenshots
![Screenshot 1](/.github/img/screen1.png) ![Screenshot 2](/.github/img/screen2.png) ![Screenshot 3](/.github/img/screen3.png) ![Screenshot 4](/.github/img/screen4.png)
+7 -3
View File
@@ -12,8 +12,8 @@ android {
applicationId "ru.ytkab0bp.slicebeam"
minSdk 21
targetSdk 34
versionCode 2
versionName "0.1.0"
versionCode 5
versionName "0.1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -34,8 +34,12 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "boolean", "IS_GOOGLE_PLAY", "true"
buildConfigField "boolean", "IS_GOOGLE_PLAY", "false"
buildConfigField "String", "COMMIT", "\"" + commit + "\""
ndk {
//noinspection ChromeOsAbiSupport
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
debug {
buildConfigField "boolean", "IS_GOOGLE_PLAY", "false"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -66,7 +66,7 @@ public class SliceBeam extends Application {
throw new RuntimeException(e);
}
}
WebView.setWebContentsDebuggingEnabled(true);
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG);
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
StringWriter sw = new StringWriter();
@@ -45,6 +45,7 @@ import ru.ytkab0bp.slicebeam.view.BeamButton;
import ru.ytkab0bp.slicebeam.view.BoostySubsView;
public class ChangeLogBottomSheet extends BottomSheetDialog {
private BoostySubsView subsView;
private ScrollView scrollView;
private ViewPager pager;
@@ -154,7 +155,7 @@ public class ChangeLogBottomSheet extends BottomSheetDialog {
subtitle.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
ll.addView(subtitle);
BoostySubsView subsView = new BoostySubsView(context);
subsView = new BoostySubsView(context);
if (SliceBeam.SERVER_DATA != null) {
List<String> list = new ArrayList<>(SliceBeam.SERVER_DATA.boostySubscribers);
Collections.shuffle(list);
@@ -238,6 +239,11 @@ public class ChangeLogBottomSheet extends BottomSheetDialog {
@EventHandler(runOnMainThread = true)
public void onDataUpdated(BeamServerDataUpdatedEvent e) {
if (SliceBeam.SERVER_DATA != null) {
List<String> list = new ArrayList<>(SliceBeam.SERVER_DATA.boostySubscribers);
Collections.shuffle(list);
subsView.setStrings(list);
}
pager.getAdapter().notifyDataSetChanged();
}
@@ -27,6 +27,7 @@ public abstract class UnfoldMenu {
protected BedFragment fragment;
private boolean isVisible;
private boolean isDismissing;
private SpringAnimation spring;
private DynamicAnimation.OnAnimationUpdateListener updateListener;
private FrameLayout containerLayout;
@@ -73,7 +74,9 @@ public abstract class UnfoldMenu {
private void show(View from, BedFragment fragment, FrameLayout into) {
if (isVisible) return;
this.fragment = fragment;
if (fragment != null) {
this.fragment = fragment;
}
this.isVisible = true;
this.containerLayout = into;
@@ -95,7 +98,7 @@ public abstract class UnfoldMenu {
fromTranslationX = pos[0] - intoPos[0];
fromTranslationY = pos[1] - intoPos[1];
toTranslationX = 0;
toTranslationY = portrait ? into.getHeight() - side : 0;
toTranslationY = portrait ? into.getHeight() - side - into.getPaddingTop() - into.getPaddingBottom() : 0;
rootView = new FrameLayout(ctx) {
{
setWillNotDraw(false);
@@ -121,13 +124,21 @@ public abstract class UnfoldMenu {
return true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (isVisible) {
onCreate();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (isVisible) {
onDestroy();
isVisible = false;
}
}
};
@@ -138,7 +149,6 @@ public abstract class UnfoldMenu {
rootView.setTranslationX(fromTranslationX);
rootView.setTranslationY(fromTranslationY);
onCreate();
dimmView = new View(ctx);
dimmView.setBackgroundColor(0x40000000);
dimmView.setTranslationX(toTranslationX);
@@ -207,6 +217,10 @@ public abstract class UnfoldMenu {
updateListener.onAnimationUpdate(spring, 1f, 0f);
}
public boolean isAttached() {
return rootView.getParent() != null && !isDismissing;
}
public void dismiss() {
dismiss(false);
}
@@ -215,7 +229,9 @@ public abstract class UnfoldMenu {
if (!isVisible) return;
this.isVisible = false;
isDismissing = true;
onDestroy();
isDismissing = false;
if (alphaOnly) {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1).setDuration(150);
@@ -124,7 +124,7 @@ public class WebViewMenu extends UnfoldMenu {
@Override
public int getRequestedSize(FrameLayout into, boolean portrait) {
return portrait ? into.getHeight() : into.getWidth();
return portrait ? into.getHeight() - into.getPaddingTop() - into.getPaddingBottom() : into.getWidth();
}
private final class Bridge {
@@ -18,6 +18,8 @@ import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -36,13 +38,16 @@ import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
import ru.ytkab0bp.slicebeam.components.WebViewMenu;
import ru.ytkab0bp.slicebeam.config.ConfigObject;
import ru.ytkab0bp.slicebeam.events.NeedDismissCalibrationsMenu;
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
@@ -214,7 +219,7 @@ public class FileMenu extends ListBedMenu {
public final class CalibrationsMenu extends UnfoldMenu {
public int getRequestedSize(FrameLayout into, boolean portrait) {
return (int) (portrait ? into.getHeight() * 0.3f : into.getWidth() * 0.6f);
return (int) (portrait ? into.getHeight() * 0.35f : into.getWidth() * 0.6f);
}
private String loadJSLoader(String key) {
@@ -309,6 +314,11 @@ public class FileMenu extends ListBedMenu {
if (ctx instanceof MainActivity) {
((MainActivity) ctx).showUnfoldMenu(new WebViewMenu(Uri.parse("https://k3d.tech/calibrations/retractions/calibrator/").buildUpon().appendQueryParameter("lang", getK3DLanguage()).build(), loadJSLoader("k3d_rct")).setFragment(fragment), v);
}
}),
new PreferenceItem().setIcon(R.drawable.deployed_code_24).setTitle(ctx.getString(R.string.MenuFileCalibrationsModels)).setSubtitle(ctx.getString(R.string.MenuFileCalibrationsModelsDescription)).setOnClickListener(v -> {
if (ctx instanceof MainActivity) {
((MainActivity) ctx).showUnfoldMenu(new CalibrationModelsMenu().setFragment(fragment), v);
}
})
));
rv.setAdapter(adapter);
@@ -359,4 +369,111 @@ public class FileMenu extends ListBedMenu {
SliceBeam.EVENT_BUS.unregisterListener(this);
}
}
public final static class CalibrationModelsMenu extends UnfoldMenu {
private void loadModel(String key) {
BedFragment fragment = this.fragment;
ViewUtils.postOnMainThread(() -> {
File f = new File(SliceBeam.getModelCacheDir(), "calibration_" + key + ".stl");
new Thread(()->{
try {
InputStream in = SliceBeam.INSTANCE.getAssets().open("models/" + key + ".stl");
FileOutputStream fos = new FileOutputStream(f);
byte[] buffer = new byte[10240]; int c;
while ((c = in.read(buffer)) != -1) {
fos.write(buffer, 0, c);
}
fos.close();
in.close();
ViewUtils.postOnMainThread(() -> {
try {
if (f.getName().endsWith(".gcode")) {
fragment.loadGCode(f);
} else {
fragment.loadModel(f);
SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent());
}
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded));
} catch (Slic3rRuntimeError e) {
f.delete();
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(fragment.getContext())
.setTitle(R.string.MenuFileOpenFileFailed)
.setMessage(e.toString())
.setPositiveButton(android.R.string.ok, null)
.show());
}
});
} catch (Exception e) {
f.delete();
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(fragment.getContext())
.setTitle(R.string.MenuFileOpenFileFailed)
.setMessage(e.toString())
.setPositiveButton(android.R.string.ok, null)
.show());
}
}).start();
}, 200);
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissCalibrationsMenu());
dismiss(true);
}
@Override
protected View onCreateView(Context ctx, boolean portrait) {
LinearLayout ll = new LinearLayout(ctx);
ll.setOrientation(LinearLayout.VERTICAL);
LinearLayout toolbar = new LinearLayout(ctx);
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
toolbar.setOrientation(LinearLayout.HORIZONTAL);
toolbar.setGravity(Gravity.CENTER_VERTICAL);
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
toolbar.setOnClickListener(v -> dismiss());
ImageView icon = new ImageView(ctx);
icon.setImageResource(R.drawable.arrow_left_outline_28);
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
TextView title = new TextView(ctx);
title.setText(R.string.MenuOrientationPositionBack);
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
leftMargin = ViewUtils.dp(12);
}});
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
RecyclerView rv = new FadeRecyclerView(ctx);
SimpleRecyclerAdapter adapter = new SimpleRecyclerAdapter();
adapter.setItems(Arrays.asList(
new PreferenceItem().setIcon(R.drawable.model_3dbenchy).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModels3DBenchy)).setOnClickListener(v -> loadModel("3dbenchy")),
new PreferenceItem().setIcon(R.drawable.model_xyz_cube).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsXYZCube)).setOnClickListener(v -> loadModel("xyz_cube")),
new PreferenceItem().setIcon(R.drawable.model_bunny).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsBunny)).setOnClickListener(v -> loadModel("bunny")),
new PreferenceItem().setIcon(R.drawable.model_fox).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsFox)).setOnClickListener(v -> loadModel("fox")),
new PreferenceItem().setIcon(R.drawable.model_box).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsBox)).setOnClickListener(v -> loadModel("box")),
new PreferenceItem().setIcon(R.drawable.model_cone).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsCone)).setOnClickListener(v -> loadModel("cone")),
new PreferenceItem().setIcon(R.drawable.model_cylinder).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsCylinder)).setOnClickListener(v -> loadModel("cylinder")),
new PreferenceItem().setIcon(R.drawable.model_pyramid).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsPyramid)).setOnClickListener(v -> loadModel("pyramid")),
new PreferenceItem().setIcon(R.drawable.model_sphere).setNoTint(true).setRoundRadius(ViewUtils.dp(8)).setTitle(ctx.getString(R.string.MenuFileCalibrationsModelsSphere)).setOnClickListener(v -> loadModel("sphere"))
));
rv.setAdapter(adapter);
ll.addView(rv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
return ll;
}
@Override
public int getRequestedSize(FrameLayout into, boolean portrait) {
return portrait ? into.getHeight() - into.getPaddingTop() - into.getPaddingBottom() : into.getWidth();
}
public CalibrationModelsMenu setFragment(BedFragment fragment) {
this.fragment = fragment;
return this;
}
}
}
@@ -2,14 +2,17 @@ package ru.ytkab0bp.slicebeam.components.bed_menu;
import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -22,6 +25,7 @@ import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.core.util.Pair;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
@@ -32,6 +36,7 @@ import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -50,11 +55,15 @@ import ru.ytkab0bp.slicebeam.config.ConfigObject;
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
import ru.ytkab0bp.slicebeam.slic3r.GCodeProcessorResult;
import ru.ytkab0bp.slicebeam.slic3r.GCodeViewer;
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
import ru.ytkab0bp.slicebeam.theme.IThemeView;
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
import ru.ytkab0bp.slicebeam.view.DividerView;
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
import ru.ytkab0bp.slicebeam.view.SegmentsView;
public class SliceMenu extends ListBedMenu {
private AsyncHttpClient client = new AsyncHttpClient();
@@ -71,7 +80,8 @@ public class SliceMenu extends ListBedMenu {
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
lastUid = SliceBeam.CONFIG_UID;
List<SimpleRecyclerItem> items = new ArrayList<>(Arrays.asList(
new BedMenuItem(R.string.MenuSliceInfo, R.drawable.square_stack_up_outline_28).onClick(v -> fragment.showUnfoldMenu(new PrintInfoMenu(), v)),
new BedMenuItem(R.string.MenuSliceInfo, R.drawable.clock_circle_dashed_outline_24).onClick(v -> fragment.showUnfoldMenu(new InfoMenu(), v)),
new BedMenuItem(R.string.MenuSliceLayers, R.drawable.square_stack_up_outline_28).onClick(v -> fragment.showUnfoldMenu(new LayersMenu(), v)),
new BedMenuItem(R.string.MenuSliceExportToFile, R.drawable.folder_simple_arrow_right_outline_28).onClick(v -> {
if (fragment.getContext() instanceof Activity) {
Activity act = (Activity) fragment.getContext();
@@ -176,15 +186,328 @@ public class SliceMenu extends ListBedMenu {
});
}
private final static class PrintInfoMenu extends UnfoldMenu {
private final static class InfoMenu extends UnfoldMenu implements IThemeView {
private TextView totalView;
private SegmentsView segmentsView;
private ExtrusionRoleView[] roleViews = new ExtrusionRoleView[GCodeViewer.EXTRUSION_ROLES_COUNT];
private static DecimalFormat format = new DecimalFormat("0.##");
private GCodeViewer getViewer() {
return fragment.getGlView().getRenderer().getViewer();
}
private GCodeProcessorResult getResult() {
return fragment.getGlView().getRenderer().getGcodeResult();
}
@Override
protected View onCreateView(Context ctx, boolean portrait) {
LinearLayout ll = new LinearLayout(ctx);
ll.setOrientation(LinearLayout.VERTICAL);
ll.addView(new Space(ctx), new LinearLayout.LayoutParams(0, 0, 1f));
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
ll.addView(roleViews[i] = new ExtrusionRoleView(ctx));
}
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
totalView = new TextView(ctx);
totalView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
totalView.setGravity(Gravity.CENTER);
ll.addView(totalView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(18)) {{
topMargin = ViewUtils.dp(8);
}});
segmentsView = new SegmentsView(ctx);
ll.addView(segmentsView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(12)) {{
leftMargin = rightMargin = ViewUtils.dp(12);
topMargin = bottomMargin = ViewUtils.dp(8);
}});
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
LinearLayout toolbar = new LinearLayout(ctx);
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
toolbar.setOrientation(LinearLayout.HORIZONTAL);
toolbar.setGravity(Gravity.CENTER_VERTICAL);
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
toolbar.setOnClickListener(v -> dismiss());
ImageView icon = new ImageView(ctx);
icon.setImageResource(R.drawable.arrow_left_outline_28);
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
TextView title = new TextView(ctx);
title.setText(R.string.MenuOrientationPositionBack);
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
leftMargin = ViewUtils.dp(12);
}});
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
onApplyTheme();
return ll;
}
@Override
protected void onCreate() {
super.onCreate();
GCodeViewer viewer = getViewer();
GCodeProcessorResult result = getResult();
if (viewer != null) {
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
boolean visible = viewer.getEstimatedTime(i) != 0;
roleViews[i].setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) {
roleViews[i].bind(viewer, result, i);
}
}
}
updateValues();
ViewUtils.postOnMainThread(() -> segmentsView.startAnimation(), 50);
}
@Override
protected void onDestroy() {
super.onDestroy();
segmentsView.setNotVisible();
}
@Override
public int getRequestedSize(FrameLayout into, boolean portrait) {
if (portrait) {
GCodeViewer viewer = getViewer();
if (viewer != null) {
int visibleCount = 0;
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
if (viewer.getEstimatedTime(i) != 0) {
visibleCount++;
}
}
return ViewUtils.dp(42) * visibleCount + ViewUtils.dp(28) + ViewUtils.dp(52) + ViewUtils.dp(18 + 8);
}
}
return super.getRequestedSize(into, portrait);
}
private float getTotalEstimatedTime() {
GCodeViewer viewer = getViewer();
if (viewer == null) return 0;
float total = 0;
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
if (viewer.isExtrusionRoleVisible(i)) {
total += viewer.getEstimatedTime(i);
}
}
return total;
}
@SuppressLint("SetTextI18n")
private void updateValues() {
GCodeViewer viewer = getViewer();
GCodeProcessorResult result = getResult();
if (viewer == null) return;
float[] values = new float[2 + GCodeViewer.EXTRUSION_ROLES_COUNT];
double totalWeight = 0;
double totalLength = 0;
values[0] = 0;
values[values.length - 1] = 1;
float prev = 0;
int lastVisible = 0;
float totalTime = getTotalEstimatedTime();
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
if (viewer.isExtrusionRoleVisible(i)) {
float percent = viewer.getEstimatedTime(i) / totalTime;
values[i + 1] = prev + percent;
lastVisible = i;
prev = values[i + 1];
totalLength += result.getUsedFilamentMM(i);
totalWeight += result.getUsedFilamentG(i);
} else {
values[i + 1] = prev;
}
}
values[lastVisible] = 1;
segmentsView.setValues(values);
totalView.setText(formatComplex(totalWeight, totalLength, totalTime));
}
private static String formatComplex(double weight, double length, float time) {
StringBuilder sb = new StringBuilder();
if (weight > 0) {
sb.append(format.format(weight)).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoWeight)).append(" | ");
}
sb.append(format.format(length)).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoLength)).append(" | ");
sb.append(formatTime(time));
return sb.toString();
}
private static String formatTime(float time) {
int secondsTotal = (int) Math.round(Math.ceil(time));
int seconds = secondsTotal % 60;
int minutes = ((secondsTotal - seconds) / 60) % 60;
int hours = ((secondsTotal - seconds) / 60) / 60;
StringBuilder sb = new StringBuilder();
if (hours > 0) {
sb.append(hours).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoHour));
}
if (minutes > 0) {
if (sb.length() > 0) sb.append(" ");
sb.append(minutes).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoMinute));
}
if (seconds > 0 || sb.length() == 0) {
if (sb.length() > 0) sb.append(" ");
sb.append(seconds).append(" ").append(SliceBeam.INSTANCE.getString(R.string.MenuSliceInfoSecond));
}
return sb.toString();
}
@Override
public void onApplyTheme() {
totalView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
}
private final class ExtrusionRoleView extends LinearLayout implements IThemeView {
private MaterialCheckBox checkBox;
private TextView titleView;
private TextView timeView;
private Runnable invalidateGl;
public ExtrusionRoleView(Context context) {
super(context);
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(16), 0);
checkBox = new MaterialCheckBox(getContext()) {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}
};
addView(checkBox, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
titleView = new TextView(context);
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
addView(titleView, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
leftMargin = ViewUtils.dp(12);
rightMargin = ViewUtils.dp(8);
}});
timeView = new TextView(context);
timeView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
addView(timeView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(42)));
onApplyTheme();
}
public void bind(GCodeViewer viewer, GCodeProcessorResult result, @GCodeViewer.ExtrusionRole int role) {
switch (role) {
case GCodeViewer.EXTRUSION_ROLE_NONE:
titleView.setText(Slic3rLocalization.getString("Unknown"));
break;
case GCodeViewer.EXTRUSION_ROLE_PERIMETER:
titleView.setText(Slic3rLocalization.getString("Perimeter"));
break;
case GCodeViewer.EXTRUSION_ROLE_EXTERNAL_PERIMETER:
titleView.setText(Slic3rLocalization.getString("External perimeter"));
break;
case GCodeViewer.EXTRUSION_ROLE_OVERHANG_PERIMETER:
titleView.setText(Slic3rLocalization.getString("Overhang perimeter"));
break;
case GCodeViewer.EXTRUSION_ROLE_INTERNAL_INFILL:
titleView.setText(Slic3rLocalization.getString("Internal infill"));
break;
case GCodeViewer.EXTRUSION_ROLE_SOLID_INFILL:
titleView.setText(Slic3rLocalization.getString("Solid infill"));
break;
case GCodeViewer.EXTRUSION_ROLE_TOP_SOLID_INFILL:
titleView.setText(Slic3rLocalization.getString("Top solid infill"));
break;
case GCodeViewer.EXTRUSION_ROLE_IRONING:
titleView.setText(Slic3rLocalization.getString("Ironing"));
break;
case GCodeViewer.EXTRUSION_ROLE_BRIDGE_INFILL:
titleView.setText(Slic3rLocalization.getString("Bridge infill"));
break;
case GCodeViewer.EXTRUSION_ROLE_GAP_FILL:
titleView.setText(Slic3rLocalization.getString("Gap fill"));
break;
case GCodeViewer.EXTRUSION_ROLE_SKIRT:
titleView.setText(Slic3rLocalization.getString("Skirt/Brim"));
break;
case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL:
titleView.setText(Slic3rLocalization.getString("Support material"));
break;
case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE:
titleView.setText(Slic3rLocalization.getString("Support material interface"));
break;
case GCodeViewer.EXTRUSION_ROLE_WIPE_TOWER:
titleView.setText(Slic3rLocalization.getString("Wipe tower"));
break;
case GCodeViewer.EXTRUSION_ROLE_CUSTOM:
titleView.setText(Slic3rLocalization.getString("Custom"));
break;
}
timeView.setText(formatComplex(result.getUsedFilamentG(role), result.getUsedFilamentMM(role), viewer.getEstimatedTime(role)));
checkBox.setChecked(viewer.isExtrusionRoleVisible(role));
checkBox.setButtonTintList(ColorStateList.valueOf(SegmentsView.mapColor(role)));
setOnClickListener(v -> {
if (getTotalEstimatedTime() == viewer.getEstimatedTime(role)) {
return;
}
viewer.toggleExtrusionRoleVisible(role);
checkBox.setChecked(!checkBox.isChecked());
updateValues();
if (invalidateGl != null) ViewUtils.removeCallbacks(invalidateGl);
ViewUtils.postOnMainThread(invalidateGl = () -> {
Pair<Long, Long> p = viewer.getLayersViewRange();
viewer.setLayersViewRange(p.first, p.second);
fragment.getGlView().requestRender();
}, 250);
});
}
@Override
public void onApplyTheme() {
titleView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
timeView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 12));
}
}
}
private final static class LayersMenu extends UnfoldMenu {
private PositionScrollView fromTrack, toTrack;
private TextView title;
private Runnable applyCallback;
private GCodeViewer getViewer() {
return fragment.getGlView().getRenderer().getViewer();
}
private void applyView(int from, int to) {
if (applyCallback != null) ViewUtils.removeCallbacks(applyCallback);
GCodeViewer viewer = getViewer();
if (viewer == null) {
return;
@@ -218,15 +541,20 @@ public class SliceMenu extends ListBedMenu {
if (toTrack.getCurrentPosition() < integer) {
toTrack.setCurrentPosition(integer);
}
applyView(integer, toTrack.getCurrentPosition());
title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, fromTrack.getCurrentPosition(), integer));
ViewUtils.removeCallbacks(applyCallback);
ViewUtils.postOnMainThread(applyCallback = ()-> applyView(integer, toTrack.getCurrentPosition()), 50);
});
fromTrack.setListener(integer -> applyView(integer, toTrack.getCurrentPosition()));
ll.addView(fromTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
toTrack = new PositionScrollView(ctx);
toTrack.setProgressListener(integer -> {
// TODO: apply only visual?
applyView(fromTrack.getCurrentPosition(), integer);
title.setText(fragment.getContext().getString(R.string.MenuSliceInfoLayers, fromTrack.getCurrentPosition(), integer));
ViewUtils.removeCallbacks(applyCallback);
ViewUtils.postOnMainThread(applyCallback = ()-> applyView(fromTrack.getCurrentPosition(), integer), 50);
});
toTrack.setListener(integer -> applyView(fromTrack.getCurrentPosition(), integer));
ll.addView(toTrack, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(80)));
@@ -267,6 +595,7 @@ public class SliceMenu extends ListBedMenu {
super.onCreate();
GCodeViewer viewer = getViewer();
if (viewer == null) return;
long max = viewer.getLayersCount();
fromTrack.setMinMax(1, (int) max);
toTrack.setMinMax(1, (int) max);
@@ -285,9 +614,6 @@ public class SliceMenu extends ListBedMenu {
fromTrack.stopScroll();
toTrack.stopScroll();
if (getViewer() != null) {
applyView(1, (int) getViewer().getLayersCount());
}
}
}
}
@@ -123,7 +123,10 @@ public class BedFragment extends Fragment {
public void showUnfoldMenu(UnfoldMenu menu, View from) {
if (currentUnfoldMenu != null) return;
menu.setOnDismiss(()-> currentUnfoldMenu = null);
menu.setOnDismiss(()-> {
if (menu.isAttached()) return;
currentUnfoldMenu = null;
});
currentUnfoldMenu = menu;
menu.show(from, this);
}
@@ -469,6 +472,11 @@ public class BedFragment extends Fragment {
super.onApplyTheme();
menuView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
for (int i = 0; i < MenuCategory.values().length; i++) {
if (i != currentMenuSlot) {
ThemesRepo.invalidateView(menuMap.get(i).getView());
}
}
}
private void constructMenuView(Context ctx, boolean portrait) {
@@ -39,13 +39,15 @@ public class FilamentConfigFragment extends ProfileListFragment {
List<ConfigObject> nList = new ArrayList<>(list.size());
Slic3rUtils.ConfigChecker checker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrinter(printer).serialize());
Slic3rUtils.ConfigChecker printChecker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrint(print).serialize());
for (ConfigObject obj : list) {
if (checker.checkCompatibility(obj.get("compatible_printers_condition")) && printChecker.checkCompatibility(obj.get("compatible_prints_condition"))) {
nList.add(obj);
if (SliceBeam.CONFIG.findPrint(print) != null) {
Slic3rUtils.ConfigChecker printChecker = new Slic3rUtils.ConfigChecker(SliceBeam.CONFIG.findPrint(print).serialize());
for (ConfigObject obj : list) {
if (checker.checkCompatibility(obj.get("compatible_printers_condition")) && printChecker.checkCompatibility(obj.get("compatible_prints_condition"))) {
nList.add(obj);
}
}
printChecker.release();
}
printChecker.release();
checker.release();
lastPrinter = printer;
lastPrint = print;
@@ -2,6 +2,8 @@ package ru.ytkab0bp.slicebeam.recycler;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -13,6 +15,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -29,6 +33,7 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
private int textColorRes;
private boolean noTint;
private ValueProvider valueProvider;
private float roundRadius;
public PreferenceItem setTitle(CharSequence title) {
mTitle = title;
@@ -70,6 +75,11 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
return this;
}
public PreferenceItem setRoundRadius(float roundRadius) {
this.roundRadius = roundRadius;
return this;
}
public PreferenceItem setTextColorRes(int textColorRes) {
this.textColorRes = textColorRes;
return this;
@@ -94,6 +104,7 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
private TextView title, subtitle;
private ImageView icon;
private TextView value;
private float radius;
public PreferenceHolderView(Context context) {
super(context);
@@ -101,7 +112,23 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
icon = new ImageView(context);
icon = new AppCompatImageView(context) {
private Path path = new Path();
@Override
public void draw(@NonNull Canvas canvas) {
if (radius != 0) {
canvas.save();
path.rewind();
path.addRoundRect(0, 0, getWidth(), getHeight(), radius, radius, Path.Direction.CW);
canvas.clipPath(path);
}
super.draw(canvas);
if (radius != 0) {
canvas.restore();
}
}
};
icon.setLayoutParams(new LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
setMarginStart(ViewUtils.dp(4));
setMarginEnd(ViewUtils.dp(8));
@@ -178,6 +205,11 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
} else {
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(item.textColorRes != 0 ? item.textColorRes : android.R.attr.textColorSecondary)));
}
radius = item.roundRadius;
icon.invalidate();
ViewGroup.LayoutParams params = icon.getLayoutParams();
params.width = params.height = radius != 0 ? ViewUtils.dp(42) : ViewUtils.dp(28);
}
@Override
@@ -13,6 +13,14 @@ public class GCodeProcessorResult {
pointer = ptr;
}
public double getUsedFilamentMM(@GCodeViewer.ExtrusionRole int role) {
return Native.gcoderesult_get_used_filament_mm(pointer, role);
}
public double getUsedFilamentG(@GCodeViewer.ExtrusionRole int role) {
return Native.gcoderesult_get_used_filament_g(pointer, role);
}
public String getRecommendedName() {
return Native.gcoderesult_get_recommended_name(pointer);
}
@@ -4,12 +4,54 @@ import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue;
import android.graphics.Color;
import androidx.annotation.IntDef;
import androidx.core.util.Pair;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import ru.ytkab0bp.slicebeam.R;
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
public class GCodeViewer {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
EXTRUSION_ROLE_NONE,
EXTRUSION_ROLE_PERIMETER,
EXTRUSION_ROLE_EXTERNAL_PERIMETER,
EXTRUSION_ROLE_OVERHANG_PERIMETER,
EXTRUSION_ROLE_INTERNAL_INFILL,
EXTRUSION_ROLE_SOLID_INFILL,
EXTRUSION_ROLE_TOP_SOLID_INFILL,
EXTRUSION_ROLE_IRONING,
EXTRUSION_ROLE_BRIDGE_INFILL,
EXTRUSION_ROLE_GAP_FILL,
EXTRUSION_ROLE_SKIRT,
EXTRUSION_ROLE_SUPPORT_MATERIAL,
EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE,
EXTRUSION_ROLE_WIPE_TOWER,
EXTRUSION_ROLE_CUSTOM
})
public @interface ExtrusionRole{}
public final static int EXTRUSION_ROLE_NONE = 0,
EXTRUSION_ROLE_PERIMETER = 1,
EXTRUSION_ROLE_EXTERNAL_PERIMETER = 2,
EXTRUSION_ROLE_OVERHANG_PERIMETER = 3,
EXTRUSION_ROLE_INTERNAL_INFILL = 4,
EXTRUSION_ROLE_SOLID_INFILL = 5,
EXTRUSION_ROLE_TOP_SOLID_INFILL = 6,
EXTRUSION_ROLE_IRONING = 7,
EXTRUSION_ROLE_BRIDGE_INFILL = 8,
EXTRUSION_ROLE_GAP_FILL = 9,
EXTRUSION_ROLE_SKIRT = 10,
EXTRUSION_ROLE_SUPPORT_MATERIAL = 11,
EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE = 12,
EXTRUSION_ROLE_WIPE_TOWER = 13,
EXTRUSION_ROLE_CUSTOM = 14,
EXTRUSION_ROLES_COUNT = 15;
private ThreadLocal<float[]> viewMatrixBuffer = new ThreadLocal<float[]>() {
@Override
protected float[] initialValue() {
@@ -54,6 +96,22 @@ public class GCodeViewer {
return Native.vgcode_get_layers_count(pointer);
}
public float getEstimatedTime() {
return Native.vgcode_get_estimated_time(pointer);
}
public float getEstimatedTime(@ExtrusionRole int extrusionRole) {
return Native.vgcode_get_estimated_time_role(pointer, extrusionRole);
}
public boolean isExtrusionRoleVisible(@ExtrusionRole int extrusionRole) {
return Native.vgcode_is_extrusion_role_visible(pointer, extrusionRole);
}
public void toggleExtrusionRoleVisible(@ExtrusionRole int extrusionRole) {
Native.vgcode_toggle_extrusion_role_visibility(pointer, extrusionRole);
}
public void render(double[] viewMatrix, double[] projectionMatrix) {
assertTrue(viewMatrix.length == 16);
assertTrue(projectionMatrix.length == 16);
@@ -73,6 +73,8 @@ class Native {
static native long gcoderesult_load_file(String path, String name);
static native String gcoderesult_get_recommended_name(long ptr);
static native double gcoderesult_get_used_filament_mm(long ptr, int role);
static native double gcoderesult_get_used_filament_g(long ptr, int role);
static native void gcoderesult_release(long ptr);
static native long vgcode_create();
@@ -84,6 +86,10 @@ class Native {
static native void vgcode_render(long ptr, float[] viewMatrix, float[] projectionMatrix);
static native void vgcode_set_layers_view_range(long ptr, long min, long max);
static native long[] vgcode_get_layers_view_range(long ptr);
static native float vgcode_get_estimated_time(long ptr);
static native float vgcode_get_estimated_time_role(long ptr, int role);
static native boolean vgcode_is_extrusion_role_visible(long ptr, int role);
static native void vgcode_toggle_extrusion_role_visibility(long ptr, int role);
static native void vgcode_reset(long ptr);
static native void vgcode_release(long ptr);
@@ -27,13 +27,21 @@ public class BeamTheme {
colors.put(R.attr.modelHoverColor, 0xffffffff);
colors.put(R.attr.textColorNegative, 0xffff464a);
colors.put(R.attr.gcodeViewerSkirt, 0x7FFF7F);
colors.put(R.attr.gcodeViewerExternalPerimeter, 0xFFFF00);
colors.put(R.attr.gcodeViewerSupportMaterial, 0x7FFF7F);
colors.put(R.attr.gcodeViewerSupportMaterialInterface, 0x7FFF7F);
colors.put(R.attr.gcodeViewerInternalInfill, 0xFF7F7F);
colors.put(R.attr.gcodeViewerSolidInfill, 0xFF7F7F);
colors.put(R.attr.gcodeViewerWipeTower, 0xF7FF7F);
colors.put(R.attr.gcodeViewerNone, 0xFFE6B3B3);
colors.put(R.attr.gcodeViewerPerimeter, 0xFFFFE64D);
colors.put(R.attr.gcodeViewerExternalPerimeter, 0xFFFF7D38);
colors.put(R.attr.gcodeViewerOverhangPerimeter, 0xFF1F1FFF);
colors.put(R.attr.gcodeViewerInternalInfill, 0xFFB03029);
colors.put(R.attr.gcodeViewerSolidInfill, 0xFF9654CC);
colors.put(R.attr.gcodeViewerTopSolidInfill, 0xFFF04040);
colors.put(R.attr.gcodeViewerIroning, 0xFFFF8C69);
colors.put(R.attr.gcodeViewerBridgeInfill, 0xFF4D80BA);
colors.put(R.attr.gcodeViewerGapFill, 0xFFFFFFFF);
colors.put(R.attr.gcodeViewerSkirt, 0xFF00876E);
colors.put(R.attr.gcodeViewerSupportMaterial, 0xFF00FF00);
colors.put(R.attr.gcodeViewerSupportMaterialInterface, 0xFF008000);
colors.put(R.attr.gcodeViewerWipeTower, 0xFFB3E3AB);
colors.put(R.attr.gcodeViewerCustom, 0xFF5ED194);
colors.put(R.attr.xTrackColor, 0xffbf0000);
colors.put(R.attr.yTrackColor, 0xff00bf00);
@@ -0,0 +1,134 @@
package ru.ytkab0bp.slicebeam.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.FloatValueHolder;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import java.util.Arrays;
import ru.ytkab0bp.slicebeam.R;
import ru.ytkab0bp.slicebeam.slic3r.GCodeViewer;
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
public class SegmentsView extends View {
private Path path = new Path();
private Paint paint = new Paint();
private float showProgress;
private boolean isVisible;
private float[] currentValues;
private SpringAnimation currentChangeAnimation;
public SegmentsView(Context context) {
super(context);
}
public void startAnimation() {
isVisible = true;
new SpringAnimation(new FloatValueHolder(0))
.setMinimumVisibleChange(1 / 256f)
.setSpring(new SpringForce(1f)
.setStiffness(1000f)
.setDampingRatio(1f))
.addUpdateListener((animation, value, velocity) -> {
showProgress = value;
invalidate();
})
.start();
}
public void setNotVisible() {
isVisible = false;
}
public void setValues(float[] values) {
if (!isVisible || currentValues == null || currentValues.length != values.length) {
currentValues = values;
invalidate();
} else {
float[] prevValues = Arrays.copyOf(currentValues, currentValues.length);
currentChangeAnimation = new SpringAnimation(new FloatValueHolder(0))
.setMinimumVisibleChange(1 / 256f)
.setSpring(new SpringForce(1f)
.setStiffness(1000f)
.setDampingRatio(1f))
.addUpdateListener((animation, value, velocity) -> {
for (int i = 0; i < currentValues.length; i++) {
currentValues[i] = ViewUtils.lerp(prevValues[i], values[i], value);
}
invalidate();
})
.addEndListener((animation, canceled, value, velocity) -> {
if (animation == currentChangeAnimation) {
currentChangeAnimation = null;
}
});
currentChangeAnimation.start();
}
}
public static int mapColor(@GCodeViewer.ExtrusionRole int index) {
switch (index) {
default:
case GCodeViewer.EXTRUSION_ROLE_NONE:
return ThemesRepo.getColor(R.attr.gcodeViewerNone);
case GCodeViewer.EXTRUSION_ROLE_PERIMETER:
return ThemesRepo.getColor(R.attr.gcodeViewerPerimeter);
case GCodeViewer.EXTRUSION_ROLE_EXTERNAL_PERIMETER:
return ThemesRepo.getColor(R.attr.gcodeViewerExternalPerimeter);
case GCodeViewer.EXTRUSION_ROLE_OVERHANG_PERIMETER:
return ThemesRepo.getColor(R.attr.gcodeViewerOverhangPerimeter);
case GCodeViewer.EXTRUSION_ROLE_INTERNAL_INFILL:
return ThemesRepo.getColor(R.attr.gcodeViewerInternalInfill);
case GCodeViewer.EXTRUSION_ROLE_SOLID_INFILL:
return ThemesRepo.getColor(R.attr.gcodeViewerSolidInfill);
case GCodeViewer.EXTRUSION_ROLE_TOP_SOLID_INFILL:
return ThemesRepo.getColor(R.attr.gcodeViewerTopSolidInfill);
case GCodeViewer.EXTRUSION_ROLE_IRONING:
return ThemesRepo.getColor(R.attr.gcodeViewerIroning);
case GCodeViewer.EXTRUSION_ROLE_BRIDGE_INFILL:
return ThemesRepo.getColor(R.attr.gcodeViewerBridgeInfill);
case GCodeViewer.EXTRUSION_ROLE_GAP_FILL:
return ThemesRepo.getColor(R.attr.gcodeViewerGapFill);
case GCodeViewer.EXTRUSION_ROLE_SKIRT:
return ThemesRepo.getColor(R.attr.gcodeViewerSkirt);
case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL:
return ThemesRepo.getColor(R.attr.gcodeViewerSupportMaterial);
case GCodeViewer.EXTRUSION_ROLE_SUPPORT_MATERIAL_INTERFACE:
return ThemesRepo.getColor(R.attr.gcodeViewerSupportMaterialInterface);
case GCodeViewer.EXTRUSION_ROLE_WIPE_TOWER:
return ThemesRepo.getColor(R.attr.gcodeViewerWipeTower);
case GCodeViewer.EXTRUSION_ROLE_CUSTOM:
return ThemesRepo.getColor(R.attr.gcodeViewerCustom);
}
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
canvas.save();
path.rewind();
float cX = getWidth() / 2f;
float l = 0, r = ViewUtils.lerp(0, getWidth(), showProgress);
path.addRoundRect(l, 0, r, getHeight(), ViewUtils.dp(12), ViewUtils.dp(12), Path.Direction.CW);
canvas.clipPath(path);
if (currentValues != null) {
float dw = r - l;
for (int i = 1; i < currentValues.length; i++) {
float prev = currentValues[i - 1];
float to = currentValues[i];
paint.setColor(mapColor(i - 1));
canvas.drawRect(l + prev * dw, 0, l + to * dw, getHeight(), paint);
}
}
canvas.restore();
}
}
+140
View File
@@ -637,6 +637,73 @@ extern "C" {
return env->NewStringUTF(ref->name.c_str());
}
GCodeExtrusionRole mapGCodeRole(int index) {
GCodeExtrusionRole gRole;
switch (index) {
default:
case 0:
gRole = GCodeExtrusionRole::None;
break;
case 1:
gRole = GCodeExtrusionRole::Perimeter;
break;
case 2:
gRole = GCodeExtrusionRole::ExternalPerimeter;
break;
case 3:
gRole = GCodeExtrusionRole::OverhangPerimeter;
break;
case 4:
gRole = GCodeExtrusionRole::InternalInfill;
break;
case 5:
gRole = GCodeExtrusionRole::SolidInfill;
break;
case 6:
gRole = GCodeExtrusionRole::TopSolidInfill;
break;
case 7:
gRole = GCodeExtrusionRole::Ironing;
break;
case 8:
gRole = GCodeExtrusionRole::BridgeInfill;
break;
case 9:
gRole = GCodeExtrusionRole::GapFill;
break;
case 10:
gRole = GCodeExtrusionRole::Skirt;
break;
case 11:
gRole = GCodeExtrusionRole::SupportMaterial;
break;
case 12:
gRole = GCodeExtrusionRole::SupportMaterialInterface;
break;
case 13:
gRole = GCodeExtrusionRole::WipeTower;
break;
case 14:
gRole = GCodeExtrusionRole::Custom;
break;
}
return gRole;
}
JNIEXPORT jdouble JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_gcoderesult_1get_1used_1filament_1mm(JNIEnv* env, jclass, jlong ptr, jint role) {
GCodeResultRef* ref = (GCodeResultRef*) (intptr_t) ptr;
std::pair<double, double> info = ref->result.print_statistics.used_filaments_per_role.find(mapGCodeRole(role))->second;
return info.first * 1000.0 / 25.4;
}
JNIEXPORT jdouble JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_gcoderesult_1get_1used_1filament_1g(JNIEnv* env, jclass, jlong ptr, jint role) {
GCodeResultRef* ref = (GCodeResultRef*) (intptr_t) ptr;
std::pair<double, double> info = ref->result.print_statistics.used_filaments_per_role.find(mapGCodeRole(role))->second;
return info.second;
}
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_gcoderesult_1release(JNIEnv* env, jclass, jlong ptr) {
GCodeResultRef* ref = (GCodeResultRef*) (intptr_t) ptr;
delete ref;
@@ -1139,4 +1206,77 @@ extern "C" {
ref->viewer.shutdown();
delete ref;
}
JNIEXPORT jfloat JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1get_1estimated_1time(JNIEnv* env, jclass, jlong ptr) {
GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr;
return ref->viewer.get_estimated_time();
}
libvgcode::EGCodeExtrusionRole mapRole(int index) {
libvgcode::EGCodeExtrusionRole crole;
switch (index) {
default:
case 0:
crole = libvgcode::EGCodeExtrusionRole::None;
break;
case 1:
crole = libvgcode::EGCodeExtrusionRole::Perimeter;
break;
case 2:
crole = libvgcode::EGCodeExtrusionRole::ExternalPerimeter;
break;
case 3:
crole = libvgcode::EGCodeExtrusionRole::OverhangPerimeter;
break;
case 4:
crole = libvgcode::EGCodeExtrusionRole::InternalInfill;
break;
case 5:
crole = libvgcode::EGCodeExtrusionRole::SolidInfill;
break;
case 6:
crole = libvgcode::EGCodeExtrusionRole::TopSolidInfill;
break;
case 7:
crole = libvgcode::EGCodeExtrusionRole::Ironing;
break;
case 8:
crole = libvgcode::EGCodeExtrusionRole::BridgeInfill;
break;
case 9:
crole = libvgcode::EGCodeExtrusionRole::GapFill;
break;
case 10:
crole = libvgcode::EGCodeExtrusionRole::Skirt;
break;
case 11:
crole = libvgcode::EGCodeExtrusionRole::SupportMaterial;
break;
case 12:
crole = libvgcode::EGCodeExtrusionRole::SupportMaterialInterface;
break;
case 13:
crole = libvgcode::EGCodeExtrusionRole::WipeTower;
break;
case 14:
crole = libvgcode::EGCodeExtrusionRole::Custom;
break;
}
return crole;
}
JNIEXPORT jfloat JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1get_1estimated_1time_1role(JNIEnv* env, jclass, jlong ptr, jint role) {
GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr;
return ref->viewer.get_extrusion_role_estimated_time(mapRole(role));
}
JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1is_1extrusion_1role_1visible(JNIEnv* env, jclass, jlong ptr, jint role) {
GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr;
return ref->viewer.is_extrusion_role_visible(mapRole(role));
}
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1toggle_1extrusion_1role_1visibility(JNIEnv* env, jclass, jlong ptr, jint role) {
GCodeViewerRef* ref = (GCodeViewerRef*) (intptr_t) ptr;
ref->viewer.toggle_extrusion_role_visibility(mapRole(role));
}
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#000">
<path
android:fillColor="@android:color/white"
android:pathData="M440,777L440,503L200,364L200,638Q200,638 200,638Q200,638 200,638L440,777ZM520,777L760,638Q760,638 760,638Q760,638 760,638L760,364L520,503L520,777ZM480,434L717,297L480,160Q480,160 480,160Q480,160 480,160L243,297L480,434ZM160,708Q141,697 130.5,679Q120,661 120,639L120,321Q120,299 130.5,281Q141,263 160,252L440,91Q459,80 480,80Q501,80 520,91L800,252Q819,263 829.5,281Q840,299 840,321L840,639Q840,661 829.5,679Q819,697 800,708L520,869Q501,880 480,880Q459,880 440,869L160,708ZM480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+17 -2
View File
@@ -18,6 +18,16 @@
<string name="MenuFileCalibrationsLADescription">Калибровка Linear/Pressure Advance</string>
<string name="MenuFileCalibrationsRetract">K3D Откаты</string>
<string name="MenuFileCalibrationsRetractDescription">Калибровка расстояния и скорости откатов</string>
<string name="MenuFileCalibrationsModels">Калибровочные модели</string>
<string name="MenuFileCalibrationsModelsDescription">Библиотека калибровочных моделей</string>
<string name="MenuFileCalibrationsModelsXYZCube">Калибровочный куб</string>
<string name="MenuFileCalibrationsModelsBunny">Заяц</string>
<string name="MenuFileCalibrationsModelsFox">Лис</string>
<string name="MenuFileCalibrationsModelsBox">Куб</string>
<string name="MenuFileCalibrationsModelsCone">Конус</string>
<string name="MenuFileCalibrationsModelsCylinder">Цилиндр</string>
<string name="MenuFileCalibrationsModelsPyramid">Пирамида</string>
<string name="MenuFileCalibrationsModelsSphere">Сфера</string>
<string name="MenuFileOpenFileLoaded">Файл загружен.</string>
<string name="MenuFileImportProfiles">Импорт. профилей</string>
<string name="MenuFileImportProfilesFailed">Не удалось импортировать</string>
@@ -68,8 +78,13 @@
<string name="MenuTransformMirrorZ">Отразить по Z</string>
<string name="MenuModifiers">Модификаторы</string>
<string name="MenuSlice">Нарезка</string>
<!-- <string name="MenuSliceInfo">Информ.\nо печати</string>-->
<string name="MenuSliceInfo">Слои</string>
<string name="MenuSliceInfo">Информ.\nо печати</string>
<string name="MenuSliceInfoSecond">с.</string>
<string name="MenuSliceInfoMinute">мин.</string>
<string name="MenuSliceInfoHour">ч.</string>
<string name="MenuSliceInfoWeight">г.</string>
<string name="MenuSliceInfoLength">мм.</string>
<string name="MenuSliceLayers">Слои</string>
<string name="MenuSliceInfoLayers">Слои %d - %d</string>
<string name="MenuSliceExportToFile">Экспорт. в файл</string>
<string name="MenuSliceShare">Отправка</string>
+11 -3
View File
@@ -9,13 +9,21 @@
<attr name="dividerColor" format="reference|color"/>
<attr name="dividerContrastColor" format="reference|color"/>
<attr name="dialogBackground" format="reference|color"/>
<attr name="gcodeViewerSkirt" format="reference|color"/>
<attr name="gcodeViewerNone" format="reference|color"/>
<attr name="gcodeViewerPerimeter" format="reference|color"/>
<attr name="gcodeViewerExternalPerimeter" format="reference|color"/>
<attr name="gcodeViewerSupportMaterial" format="reference|color"/>
<attr name="gcodeViewerSupportMaterialInterface" format="reference|color"/>
<attr name="gcodeViewerOverhangPerimeter" format="reference|color"/>
<attr name="gcodeViewerInternalInfill" format="reference|color"/>
<attr name="gcodeViewerSolidInfill" format="reference|color"/>
<attr name="gcodeViewerTopSolidInfill" format="reference|color"/>
<attr name="gcodeViewerIroning" format="reference|color"/>
<attr name="gcodeViewerBridgeInfill" format="reference|color"/>
<attr name="gcodeViewerGapFill" format="reference|color"/>
<attr name="gcodeViewerSkirt" format="reference|color"/>
<attr name="gcodeViewerSupportMaterial" format="reference|color"/>
<attr name="gcodeViewerSupportMaterialInterface" format="reference|color"/>
<attr name="gcodeViewerWipeTower" format="reference|color"/>
<attr name="gcodeViewerCustom" format="reference|color"/>
<attr name="switchThumbUncheckedColor" format="reference|color"/>
<attr name="boostyColorTop" format="reference|color"/>
<attr name="boostyColorBottom" format="reference|color"/>
+18 -2
View File
@@ -20,6 +20,17 @@
<string name="MenuFileCalibrationsLADescription">Linear/Pressure Advance Calibration</string>
<string name="MenuFileCalibrationsRetract">K3D Retractions</string>
<string name="MenuFileCalibrationsRetractDescription">Retraction distance and speed calibration</string>
<string name="MenuFileCalibrationsModels">Calibration models</string>
<string name="MenuFileCalibrationsModelsDescription">Calibration models library</string>
<string name="MenuFileCalibrationsModels3DBenchy" translatable="false">3D Benchy</string>
<string name="MenuFileCalibrationsModelsXYZCube">Calibration cube</string>
<string name="MenuFileCalibrationsModelsBunny">Bunny</string>
<string name="MenuFileCalibrationsModelsFox">Fox</string>
<string name="MenuFileCalibrationsModelsBox">Box</string>
<string name="MenuFileCalibrationsModelsCone">Cone</string>
<string name="MenuFileCalibrationsModelsCylinder">Cylinder</string>
<string name="MenuFileCalibrationsModelsPyramid">Pyramid</string>
<string name="MenuFileCalibrationsModelsSphere">Sphere</string>
<string name="MenuFileImportProfiles">Import profiles</string>
<string name="MenuFileImportProfilesFailed">Import failed</string>
<string name="MenuFileImportProfilesFailedNotIni">Not an .ini file</string>
@@ -69,8 +80,13 @@
<string name="MenuTransformMirrorZ">Mirror Z</string>
<string name="MenuModifiers">Modifiers</string>
<string name="MenuSlice">Slice</string>
<!-- <string name="MenuSliceInfo">Print\ninfo</string>-->
<string name="MenuSliceInfo">Layers</string>
<string name="MenuSliceInfo">Print\ninfo</string>
<string name="MenuSliceInfoSecond">s.</string>
<string name="MenuSliceInfoMinute">min.</string>
<string name="MenuSliceInfoHour">h.</string>
<string name="MenuSliceInfoWeight">g.</string>
<string name="MenuSliceInfoLength">mm.</string>
<string name="MenuSliceLayers">Layers</string>
<string name="MenuSliceInfoLayers">Layers %d - %d</string>
<string name="MenuSliceExportToFile">Export to file</string>
<string name="MenuSliceShare">Share</string>