6 Commits

Author SHA1 Message Date
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
14 changed files with 658 additions and 29 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 4
versionName "0.1.1"
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"
@@ -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;
@@ -121,13 +122,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 +147,6 @@ public abstract class UnfoldMenu {
rootView.setTranslationX(fromTranslationX);
rootView.setTranslationY(fromTranslationY);
onCreate();
dimmView = new View(ctx);
dimmView.setBackgroundColor(0x40000000);
dimmView.setTranslationX(toTranslationX);
@@ -207,6 +215,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 +227,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);
@@ -5,11 +5,13 @@ import static ru.ytkab0bp.slicebeam.utils.DebugUtils.assertTrue;
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 +24,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;
@@ -51,10 +54,13 @@ import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
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 +77,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 +183,305 @@ 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 GCodeViewer getViewer() {
return fragment.getGlView().getRenderer().getViewer();
}
@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();
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, 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;
}
private void updateValues() {
GCodeViewer viewer = getViewer();
if (viewer == null) return;
float[] values = new float[2 + GCodeViewer.EXTRUSION_ROLES_COUNT];
values[0] = 0;
values[values.length - 1] = 1;
float prev = 0;
int lastVisible = 0;
float total = getTotalEstimatedTime();
for (int i = 0; i < GCodeViewer.EXTRUSION_ROLES_COUNT; i++) {
if (viewer.isExtrusionRoleVisible(i)) {
float percent = viewer.getEstimatedTime(i) / total;
values[i + 1] = prev + percent;
lastVisible = i;
prev = values[i + 1];
} else {
values[i + 1] = prev;
}
}
values[lastVisible] = 1;
segmentsView.setValues(values);
totalView.setText(formatTime(total));
}
private static String formatTime(float time) {
int secondsTotal = Math.round(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) {
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 = rightMargin = ViewUtils.dp(12);
}});
timeView = new TextView(context);
timeView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
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, @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(formatTime(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 +515,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 +569,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 +588,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) {
@@ -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);
@@ -84,6 +84,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();
}
}
@@ -1139,4 +1139,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));
}
}
+5 -2
View File
@@ -68,8 +68,11 @@
<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="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"/>
+5 -2
View File
@@ -69,8 +69,11 @@
<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="MenuSliceLayers">Layers</string>
<string name="MenuSliceInfoLayers">Layers %d - %d</string>
<string name="MenuSliceExportToFile">Export to file</string>
<string name="MenuSliceShare">Share</string>