text:
package com.mrcrayfish.catalogue.client.screen.widget;

import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mrcrayfish.catalogue.Constants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;

/**
 * Author: MrCrayfish
 */
public class CatalogueIconButton extends Button
{
    private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/icons.png");

    private final Component label;
    private final int u, v;

    public CatalogueIconButton(int x, int y, int u, int v, OnPress onPress)
    {
        this(x, y, u, v, 20, CommonComponents.EMPTY, onPress);
    }

    public CatalogueIconButton(int x, int y, int u, int v, int width, Component label, OnPress onPress)
    {
        super(x, y, width, 20, CommonComponents.EMPTY, onPress, Button.DEFAULT_NARRATION);
        this.label = label;
        this.u = u;
        this.v = v;
    }

    @Override
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks)
    {
        super.renderWidget(graphics, mouseX, mouseY, partialTicks);
        Minecraft minecraft = Minecraft.getInstance();
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
        RenderSystem.enableDepthTest();
        RenderSystem.enableBlend();
        RenderSystem.defaultBlendFunc();
        RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
        int contentWidth = 10 + minecraft.font.width(this.label) + (!this.label.getString().isEmpty() ? 4 : 0);
        int iconX = this.getX() + (this.width - contentWidth) / 2;
        int iconY = this.getY() + 5;
        float brightness = this.active ? 1.0F : 0.5F;
        RenderSystem.setShaderColor(brightness, brightness, brightness, this.alpha);
        graphics.blit(TEXTURE, iconX, iconY, this.u, this.v, 10, 10, 64, 64);
        RenderSystem.setShaderColor(brightness, brightness, brightness, this.alpha);
        int textColor = 0xFFFFFF | Mth.ceil(this.alpha * 255.0F) << 24;
        graphics.drawString(minecraft.font, this.label, iconX + 14, iconY + 1, textColor);
    }
}


text:
package com.mrcrayfish.catalogue.client.screen.widget;

import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mrcrayfish.catalogue.Constants;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Checkbox;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.resources.ResourceLocation;

/**
 * Author: MrCrayfish
 */
public class CatalogueCheckBoxButton extends Checkbox
{
    private static final ResourceLocation TEXTURE = new ResourceLocation(Constants.MOD_ID, "textures/gui/checkbox.png");

    private final OnPress onPress;

    public CatalogueCheckBoxButton(int x, int y, OnPress onPress)
    {
        super(x, y, 14, 14, CommonComponents.EMPTY, false);
        this.onPress = onPress;
    }

    @Override
    public void onPress()
    {
        super.onPress();
        this.onPress.onPress(this);
    }

    @Override
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks)
    {
        RenderSystem.enableDepthTest();
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
        RenderSystem.enableBlend();
        RenderSystem.defaultBlendFunc();
        RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
        graphics.blit(TEXTURE, this.getX(), this.getY(), this.isHoveredOrFocused() ? 14 : 0, this.selected() ? 14 : 0, 14, 14, 64, 64);
        //this.renderBg(poseStack, minecraft, mouseX, mouseY);
    }

    public interface OnPress
    {
        void onPress(Checkbox button);
    }
}

repo_name: MrCrayfish/Catalogue

repo_language: Java

mime_type: text/x-java


text:
package com.mrcrayfish.catalogue.platform;

import com.mrcrayfish.catalogue.Constants;
import com.mrcrayfish.catalogue.platform.services.IComponentHelper;
import com.mrcrayfish.catalogue.platform.services.IPlatformHelper;

import java.util.ServiceLoader;

public class ClientServices
{
    public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class);
    public static final IComponentHelper COMPONENT = load(IComponentHelper.class);

    public static <T> T load(Class<T> clazz)
    {
        final T loadedService = ServiceLoader.load(clazz).findFirst().orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName()));
        Constants.LOG.debug("Loaded {} for service {}", loadedService, clazz);
        return loadedService;
    }
}


text:
package com.mrcrayfish.catalogue.platform.services;

import com.mojang.blaze3d.platform.NativeImage;
import com.mrcrayfish.catalogue.client.IModData;

import java.io.File;
import java.util.List;
import java.util.function.Consumer;

public interface IPlatformHelper
{
    List<IModData> getAllModData();

    File getModDirectory();

    default boolean isForge()
    {
        return false;
    }

    void loadNativeImage(String modId, String resource, Consumer<NativeImage> consumer);
}


text:
package com.mrcrayfish.catalogue.platform.services;

import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;

/**
 * Author: MrCrayfish
 */
public interface IComponentHelper
{
    MutableComponent createTitle();

    MutableComponent createVersion(String version);

    MutableComponent createFormatted(String formatKey, String value);

    Component createFilterUpdates();

    String getCreditsKey();
}


text:
package com.mrcrayfish.catalogue;

import com.google.common.collect.ImmutableMap;
import com.mrcrayfish.catalogue.client.screen.CatalogueModListScreen;
import com.mrcrayfish.catalogue.client.screen.widget.CatalogueIconButton;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.fabricmc.fabric.api.client.screen.v1.Screens;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.PauseScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.network.chat.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;

public class Catalogue implements ClientModInitializer
{
    private static Map<String, BiFunction<Screen, ModContainer, Screen>> providers;

    @Override
    public void onInitializeClient()
    {
        Catalogue.providers = this.findConfigFactoryProviders();

        ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) ->
        {
            if(screen instanceof TitleScreen)
            {
                int x = screen.width / 2;
                int y = screen.height / 4 + 48;
                Button modButton = new CatalogueIconButton(x - 124, y + 48, 30, 0, button -> client.setScreen(new CatalogueModListScreen(screen)));
                modButton.setTooltip(Tooltip.create(Component.translatable("catalogue.gui.mod_list")));
                Screens.getButtons(screen).add(modButton);
            }
            else if(screen instanceof PauseScreen)
            {
                int x = screen.width / 2;
                int y = screen.height / 4 + 32;
                Button modButton = new CatalogueIconButton(x - 124, y + 48, 30, 0, button -> client.setScreen(new CatalogueModListScreen(screen)));
                modButton.setTooltip(Tooltip.create(Component.translatable("catalogue.gui.mod_list")));
                Screens.getButtons(screen).add(modButton);
            }
        });
    }

    public static Map<String, BiFunction<Screen, ModContainer, Screen>> getConfigProviders()
    {
        return providers;
    }

    private Map<String, BiFunction<Screen, ModContainer, Screen>> findConfigFactoryProviders()
    {
        Map<String, BiFunction<Screen, ModContainer, Screen>> factories = new HashMap<>();
        Map<String, BiFunction<Screen, ModContainer, Screen>> providers = new HashMap<>();
        FabricLoader.getInstance().getAllMods().forEach(container -> {
            this.getConfigFactoryClass(container).ifPresent(className -> {
                Optional.ofNullable(createConfigFactoryProvider(className)).ifPresent(map -> {
                    map.forEach(providers::putIfAbsent); // Only adds provider if not provided already
                });
                this.createConfigFactory(className).ifPresent(function -> {
                    factories.put(container.getMetadata().getId(), function);
                });
            });
        });
        providers.putAll(factories);
        return ImmutableMap.copyOf(providers);
    }

    private Optional<String> getConfigFactoryClass(ModContainer container)
    {
        ModMetadata metadata = container.getMetadata();
        CustomValue value = metadata.getCustomValue("catalogue");
        if(value == null || value.getType() != CustomValue.CvType.OBJECT)
            return Optional.empty();

        CustomValue.CvObject catalogueObj = value.getAsObject();
        CustomValue configFactoryValue = catalogueObj.get("configFactory");
        if(configFactoryValue == null || configFactoryValue.getType() != CustomValue.CvType.STRING)
            return Optional.empty();

        return Optional.of(configFactoryValue.getAsString());
    }

    @SuppressWarnings("unchecked")
    private static Map<String, BiFunction<Screen, ModContainer, Screen>> createConfigFactoryProvider(String className)
    {
        try
        {
            Class<?> configFactoryClass = Class.forName(className);
            Method createConfigProviderMethod = configFactoryClass.getDeclaredMethod("createConfigProvider");
            int mods = createConfigProviderMethod.getModifiers();
            if(!Modifier.isPublic(mods))
            {
                throw new RuntimeException("createConfigProvider is not accessible for class: " + className);
            }
            if(!Modifier.isStatic(mods))
            {
                throw new RuntimeException("createConfigProvider is not static for class: " + className);
            }
            if(createConfigProviderMethod.getReturnType() != Map.class)
            {
                throw new RuntimeException("createConfigProvider must return a Map<String, BiFunction<Screen, ModContainer, Screen>>");
            }
            return (Map<String, BiFunction<Screen, ModContainer, Screen>>) createConfigProviderMethod.invoke(null);
        }
        catch(ClassNotFoundException e)
        {
            throw new RuntimeException("Unable to locate config factory class: " + className);
        }
        catch(InvocationTargetException | IllegalAccessException e)
        {
            throw new RuntimeException(e);
        }
        catch(NoSuchMethodException e)
        {
            // Method is optional
        }
        return null;
    }

    private Optional<BiFunction<Screen, ModContainer, Screen>> createConfigFactory(String className)
    {
        try
        {
            Class<?> configFactoryClass = Class.forName(className);
            Method createConfigScreenMethod = configFactoryClass.getDeclaredMethod("createConfigScreen", Screen.class, ModContainer.class);
            int mods = createConfigScreenMethod.getModifiers();
            if(!Modifier.isPublic(mods))
            {
                throw new RuntimeException("createConfigScreen is not accessible for class: " + className);
            }
            if(!Modifier.isStatic(mods))
            {
                throw new RuntimeException("createConfigScreen is not static for class: " + className);
            }
            return Optional.of((currentScreen, container) ->
            {
                try
                {
                    return (Screen) createConfigScreenMethod.invoke(null, currentScreen, container);
                }
                catch(InvocationTargetException | IllegalAccessException e)
                {
                    throw new RuntimeException(e);
                }
            });
        }
        catch(ClassNotFoundException e)
        {
            throw new RuntimeException("Unable to locate config factory class: " + className);
        }
        catch(NoSuchMethodException e)
        {
            // Method is optional
        }
        return Optional.empty();
    }
}


text:
package com.mrcrayfish.catalogue.client;

import com.mrcrayfish.catalogue.Catalogue;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.api.metadata.Person;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nullable;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static com.mrcrayfish.catalogue.client.IModData.Type.*;

/**
 * Author: MrCrayfish
 */
public class FabricModData implements IModData
{
    private final ModMetadata metadata;
    private final Type type;
    private final String imageIcon;
    private final String imageBanner;
    private final String imageBackground;
    private final String itemIcon;

    public FabricModData(ModMetadata metadata)
    {
        this.metadata = metadata;
        this.type = analyzeType(metadata);
        String imageIcon = metadata.getIconPath(64).orElse(null);
        String imageBanner = null;
        String imageBackground = null;
        String itemIcon = null;
        CustomValue value = metadata.getCustomValue("catalogue");
        if(value != null && value.getType() == CustomValue.CvType.OBJECT)
        {
            CustomValue.CvObject catalogueObj = value.getAsObject();
            CustomValue iconValue = catalogueObj.get("icon");
            if(iconValue != null && iconValue.getType() == CustomValue.CvType.OBJECT)
            {
                CustomValue.CvObject iconObj = iconValue.getAsObject();
                CustomValue imageValue = iconObj.get("image");
                if(imageValue != null && imageValue.getType() == CustomValue.CvType.STRING)
                {
                    imageIcon = imageValue.getAsString();
                }
                CustomValue itemValue = iconObj.get("item");
                if(itemValue != null && itemValue.getType() == CustomValue.CvType.STRING)
                {
                    itemIcon = itemValue.getAsString();
                }
            }
            CustomValue bannerValue = catalogueObj.get("banner");
            if(bannerValue != null && bannerValue.getType() == CustomValue.CvType.STRING)
            {
                imageBanner = bannerValue.getAsString();
            }
            CustomValue backgroundValue = catalogueObj.get("background");
            if(backgroundValue != null && backgroundValue.getType() == CustomValue.CvType.STRING)
            {
                imageBackground = backgroundValue.getAsString();
            }
        }
        this.imageIcon = imageIcon;
        this.itemIcon = itemIcon;
        this.imageBanner = imageBanner;
        this.imageBackground = imageBackground;
    }

    @Override
    public Type getType()
    {
        return this.type;
    }

    @Override
    public String getModId()
    {
        return this.metadata.getId();
    }

    @Override
    public String getDisplayName()
    {
        return this.metadata.getName();
    }

    @Override
    public String getVersion()
    {
        return this.metadata.getVersion().getFriendlyString();
    }

    @Override
    public String getDescription()
    {
        return this.metadata.getDescription();
    }

    @Nullable
    @Override
    public String getItemIcon()
    {
        return this.itemIcon;
    }

    @Nullable
    @Override
    public String getImageIcon()
    {
        return this.imageIcon;
    }

    @Override
    public String getLicense()
    {
        return StringUtils.join(this.metadata.getLicense(), ", ");
    }

    @Nullable
    @Override
    public String getCredits()
    {
        return StringUtils.join(this.metadata.getContributors().stream().map(Person::getName).collect(Collectors.toList()), ", ");
    }

    @Nullable
    @Override
    public String getAuthors()
    {
        return StringUtils.join(this.metadata.getAuthors().stream().map(Person::getName).collect(Collectors.toList()), ", ");
    }

    @Nullable
    @Override
    public String getHomepage()
    {
        return this.metadata.getContact().get("homepage").orElse(null);
    }

    @Nullable
    @Override
    public String getIssueTracker()
    {
        return this.metadata.getContact().get("issues").orElse(null);
    }

    @Nullable
    @Override
    public String getBanner()
    {
        return this.imageBanner;
    }

    @Nullable
    @Override
    public String getBackground()
    {
        return this.imageBackground;
    }

    @Override
    public Update getUpdate()
    {
        return null;
    }

    @Override
    public boolean hasConfig()
    {
        return Catalogue.getConfigProviders().containsKey(this.metadata.getId());
    }

    @Override
    public boolean isLogoSmooth()
    {
        return false;
    }

    @Override
    public void openConfigScreen(Screen parent)
    {
        BiFunction<Screen, ModContainer, Screen> configFactory = Catalogue.getConfigProviders().get(this.metadata.getId());
        if(configFactory != null)
        {
            FabricLoader.getInstance().getModContainer(this.metadata.getId()).ifPresent(container ->
            {
                Screen configScreen = configFactory.apply(parent, container);
                if(configScreen != null)
                {
                    Minecraft.getInstance().setScreen(configScreen);
                }
            });
        }
    }

    private static Type analyzeType(ModMetadata metadata)
    {
        CustomValue lifecycle = metadata.getCustomValue("fabric-api:module-lifecycle");
        if(lifecycle != null)
        {
            return LIBRARY;
        }

        String modId = metadata.getId();
        if(modId.startsWith("fabric-") || modId.equals("minecraft") || modId.equals("java") || modId.equals("fabricloader"))
        {
            return LIBRARY;
        }

        CustomValue generated = metadata.getCustomValue("fabric-loom:generated");
        if(generated != null && generated.getType() == CustomValue.CvType.BOOLEAN && generated.getAsBoolean())
        {
            return GENERATED;
        }

        return DEFAULT;
    }
}


text:
package com.mrcrayfish.catalogue.platform;

import com.mrcrayfish.catalogue.platform.services.IComponentHelper;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;

/**
 * Author: MrCrayfish
 */
public class FabricComponentHelper implements IComponentHelper
{
    @Override
    public MutableComponent createTitle()
    {
        return Component.translatable("catalogue.gui.mod_list");
    }

    @Override
    public MutableComponent createVersion(String version)
    {
        return Component.translatable("catalogue.gui.version", version);
    }

    @Override
    public MutableComponent createFormatted(String formatKey, String value)
    {
        return Component.translatable(formatKey, value);
    }

    @Override
    public Component createFilterUpdates()
    {
        return Component.translatable("catalogue.gui.internal_libraries");
    }

    @Override
    public String getCreditsKey()
    {
        return "catalogue.gui.contributors";
    }
}


text:
package com.mrcrayfish.catalogue.platform;

import com.mrcrayfish.catalogue.client.FabricModData;
import com.mrcrayfish.catalogue.client.IModData;
import com.mrcrayfish.catalogue.platform.services.IPlatformHelper;
import com.mojang.blaze3d.platform.NativeImage;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.impl.FabricLoaderImpl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class FabricPlatformHelper implements IPlatformHelper
{
    @Override
    public List<IModData> getAllModData()
    {
        return FabricLoader.getInstance().getAllMods().stream().map(ModContainer::getMetadata).map(FabricModData::new).collect(Collectors.toList());
    }

    @Override
    public File getModDirectory()
    {
        return FabricLoaderImpl.INSTANCE.getModsDirectory();
    }

    @Override
    public void loadNativeImage(String modId, String resource, Consumer<NativeImage> consumer)
    {
        FabricLoader.getInstance().getModContainer(modId).flatMap(container -> container.findPath(resource)).ifPresent(path ->
        {
            try(InputStream is = Files.newInputStream(path); NativeImage icon = NativeImage.read(is))
            {
                consumer.accept(icon);
            }
            catch(IOException ignored) {}
        });
    }
}


text:
<opml version="2.0">
	<body>
		<outline text="vnbrs digest" title="vnbrs digest">
			<outline xmlUrl='https://martinfowler.com/feed.atom' />
			<outline xmlUrl='http://feeds.feedburner.com/codinghorror' />
			<outline xmlUrl='https://www.joelonsoftware.com/feed/' />
			<outline xmlUrl='https://feeds.feedburner.com/GiantRobotsSmashingIntoOtherGiantRobots' />
			<outline xmlUrl='https://m.signalvnoise.com/feed/' />
			<outline xmlUrl='https://medium.com/feed/google-developers' />
			<outline xmlUrl='https://www.mikeperham.com/index.xml' />
			<outline xmlUrl='https://medium.com/feed/netflix-techblog' />
			<outline xmlUrl='https://www.smashingmagazine.com/feed/' />
			<outline xmlUrl='https://overreacted.io/rss.xml' />
			<outline xmlUrl='https://davidwalsh.name/feed' />
			<outline xmlUrl='https://technology.riotgames.com/news/feed' />
		</outline>
	</body>
</opml>

repo_name: vinibrsl/digest

mime_type: text/plain


text:
# How to contribute?

First of all, thank you for your interest in contributing.

If you know a great blog or website, open a PR adding your suggestion. Please follow this format:

```markdown
[Your Suggestion](https://blog.com) - posts about foo, bar and baz
```

Please note:

* No full stop at the end
* Search previous suggestions before making a new one, as yours may be a duplicate
* Please add the entry in the correct section (e.g.: back-end, general or front-end)


text:
# Digest
A curated list of blogs, websites, and all kinds of reads about software development.

![](https://i.imgur.com/8q0BYtu.png)

# Usage
You can load this list into your RSS reader, such as Feedly, by importing [this .opml file](https://github.com/vnbrs/digest/blob/master/feed.opml) into your reader.

# Summary

- [General](#general)
- [Back-end](#back-end)
- [Front-end](#front-end)
- [Game Development](#game-development)

## General
* [dev.to](https://dev.to/) - open-source community where programmers share ideas and help each other grow
* [Martian Chronicles](https://evilmartians.com/chronicles) - very detailed posts about back-end, UX, React, Ruby, Rails, front-end
* [Martin Fowler](https://martinfowler.com/) - reliable content about software architecture, design patterns
* [Coding Horror](https://blog.codinghorror.com/) - a blog by Jeff Atwood, Stack Overflow's co-founder, on programming and human factors
* [Joel on Software](https://www.joelonsoftware.com/) - Stack Overflow co-founder's blog. He writes about software development, management, business
* [Giant Robots Smashing Into Other Giant Robots](https://thoughtbot.com/blog) - written by thoughtbot, experienced designers and developers who turn your idea into the right product
* [Signal v. Noise](https://m.signalvnoise.com/) - strong opinions and shared thoughts on design, business, and tech. By the makers (and friends) of Basecamp. Since 1999.
* [Google Developers](https://medium.com/google-developers) - for engineers, by engineers
* [Porter.io](https://porter.io/explore) - Hacker News personalized and delivered

## Back-end
* [Mike Perham](https://www.mikeperham.com/) - Sidekiq's creator, talks about Ruby, OSS
* [Netflix TechBlog](https://medium.com/netflix-techblog) - learn more about the way Netflix does engineering. Posts about scalability, databases, infrastructure

## Front-end
* [Smashing Magazine](https://www.smashingmagazine.com/) - editorial content and professional resources for web developers and designers
* [Overreacted](https://overreacted.io/) - Dan Abramov's personal blog. He writes about React, front-end development, behavior
* [David Walsh Blog](https://davidwalsh.name/) - tutorials and articles about JavaScript, CSS, animations, HTML5
* [Google Chrome Developers](https://www.youtube.com/channel/UCnUYZLuoy1rq1aVMwx4aTzw) - videos about web development, Google Chrome updates, PWA
* [CSS-Tricks](https://css-tricks.com/) - tips, tricks, and techniques on using CSS

## Game Development
* [Riot Games Technology](https://technology.riotgames.com/) - stories about the development of League of Legends. They talk about CI, testing, performance


text:
django-sslify
=============

Do you want to force HTTPs across your Django site? You're in the right place!

.. image:: https://img.shields.io/pypi/v/django-sslify.svg
    :alt: django-sslify Release
    :target: https://pypi.python.org/pypi/django-sslify

.. image:: https://img.shields.io/pypi/dm/django-sslify.svg
    :alt: django-sslify Downloads
    :target: https://pypi.python.org/pypi/django-sslify

.. image:: https://img.shields.io/travis/rdegges/django-sslify.svg
    :alt: django-sslify Build
    :target: https://travis-ci.org/rdegges/django-sslify

.. image:: https://github.com/rdegges/django-sslify/raw/master/assets/guardian-sketch.png
   :alt: Guardian Sketch

Meta
----

- Author: Randall Degges
- Email: r@rdegges.com
- Site: http://www.rdegges.com
- Status: maintained, active

Purpose
-------

Enabling SSL on your Django site should be easy, easy as in *one-line-of-code
easy*.  That's why I wrote ``django-sslify``!

The goal of this project is to make it easy for people to force HTTPS on every
page of their Django site, API, web app, or whatever you're building.  Securing
your site shouldn't be hard.

Using Django 1.8 or later?
--------------------------

This package was written before Django 1.8. If you are using Django 1.8 or later, you do not need this library in order to force HTTPS. Instead, you can just change your ``settings.py`` file to include ``SECURE_SSL_REDIRECT``.

.. code-block:: python

    # in settings.py
    SECURE_SSL_REDIRECT = True

If you are using Heroku, you may need to add ``SECURE_PROXY_SSL_HEADER`` as well.

.. code-block:: python

    # in settings.py
    SECURE_SSL_REDIRECT = True
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Django's documentation includes more details about `security settings for HTTPS <https://docs.djangoproject.com/en/dev/topics/security/#ssl-https>`_.

If you are using an older version of Django (1.7 or earlier), then this package is for you.

Installation
------------

To install ``django-sslify``, simply run:

.. code-block:: console

    $ pip install django-sslify

This will install the latest version of the library automatically.

If you're using `Heroku <https://www.heroku.com/>`_, you should add
``django-sslify>=0.2`` to your ``requirements.txt`` file:

.. code-block:: console

    $ echo 'django-sslify>=0.2.0' >> requirements.txt

Once you've done this, the next time you push your code to Heroku this library
will be installed for you automatically.

Usage
-----

To use this library, and force SSL across your Django site, all you need to do
is modify your ``settings.py`` file, and prepend
``sslify.middleware.SSLifyMiddleware`` to your ``MIDDLEWARE_CLASSES`` setting:

.. code-block:: python

    # settings.py

    MIDDLEWARE_CLASSES = (
        'sslify.middleware.SSLifyMiddleware',
        # ...
    )

.. note::
    Make sure ``sslify.middleware.SSLifyMiddleware`` is the first middleware
    class listed, as this will ensure that if a user makes an insecure request
    (*over HTTP*), they will be redirected to HTTPs before any actual
    processing happens.

If you're using Heroku, you should also add the following settings to your
Django settings file:

.. code-block:: python

    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

This ensures that Django will be able to detect a secure connection properly.

Using a Custom SSL Port
***********************

If your site is running on a non-standard SSL port, you can change
``django-sslify``'s default redirection behavior by setting a special variable
in your ``settings.py`` file:

.. code-block:: python

    SSLIFY_PORT = 999

Disabling SSLify
----------------

If you'd like to disable SSLify in certain environments (*for local development,
or running unit tests*), the best way to do it is to modify your settings file
and add the following:

.. code-block:: python

    SSLIFY_DISABLE = True

You can also disable SSLify for certain requests only (*useful for exposing
HTTP-only web hook URLs, etc*) by adding a callable with a single request
parameter to the ``SSLIFY_DISABLE_FOR_REQUEST`` list.  Returning ``True`` from
your callable will disable SSL redirects.

.. code-block:: python

    SSLIFY_DISABLE_FOR_REQUEST = [
        lambda request: request.get_full_path().startswith('/no_ssl_please')
    ]

Notes
-----

This code was initially taken from
`this StackOverflow thread <http://stackoverflow.com/questions/8436666/how-to-make-python-on-heroku-https-only>`_.

This code has been adopted over the years to work on Heroku, and non-Heroku
platforms.

If you're using Heroku, and have no idea how to setup SSL, read
`this great article <https://devcenter.heroku.com/articles/ssl-endpoint>`_
which talks about using the new SSL endpoint addon (*which totally rocks!*).

NGINX + Infinite Redirect
-------------------------

If you're running your Django app behind an Nginx load balancer, and are seeing
infinite redirects, the solution is to add the following line:

.. code-block:: text

    proxy_set_header X-Forwarded-Proto $scheme;

To your ``nginx.conf`` file, inside of the relevant ``location`` blocks.  This
`Stack Overflow thread
<http://stackoverflow.com/questions/23121800/nginx-redirect-loop-with-ssl>`_
might also be useful.

Contributing
------------

This project is only possible due to the amazing contributors who work on it!

If you'd like to improve this library, please send me a pull request! I'm happy
to review and merge pull requests.

The standard contribution workflow should look something like this:

- Fork this project on Github.
- Make some changes in the master branch (*this project is simple, so no need to
  complicate things*).
- Send a pull request when ready.

Also, if you're making changes, please write tests for your changes -- this
project has a full test suite you can easily modify / test.

To run the test suite, you can use the following commands:

.. code-block:: console

    $ cd django-sslify
    $ python setup.py develop
    $ python manage.py test sslify

Change Log
----------

All library changes, in descending order.

Version 0.2.8
*************

**Released January 15, 2018.**

- Adding Django 1.10 compatibility.
- Fixing markup.
- Updating Travis CI for 1.9.

Version 0.2.5
*************

**Released December 28, 2014.**

- Adding in new ``SSLIFY_DISABLE_FOR_REQUEST`` setting which allows a user to
  specify functions that can choose to reject SSL -- this is useful for
  situations where you might want to force SSL site-wide EXCEPT in a few
  circumstances (*webhooks that don't support SSL, for instance*).

Version 0.2.4
*************

**Released on November 23, 2014.**

- Adding the ability to specify a custom SSL port.
- Totally revamping docs.
- Changing project logo / mascot thingy ^^
- Adding new tests for custom SSL ports.

repo_name: rdegges/django-sslify

repo_language: Python


text:
include README.rst manage.py UNLICENSE
recursive-exclude sslify *.pyc
recursive-exclude test_project *.pyc
recursive-include test_project *.py
recursive-include assets *.png


text:
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)


text:
[tox]
envlist =
    py{27,33,34,35}-django{18},
    py{27,34,35}-django{19,110},
    py{27,34,35,36}-django{111},
    py{34,35,36}-django{20},
    py{35,36,37}-django{21},

[testenv]
commands=python -Wall {toxinidir}/manage.py test sslify --settings=
deps=
    django18: Django>=1.8,<1.9
    django19: Django>=1.9,<1.10
    django110: Django>=1.10,<1.11
    django111: Django>=1.11,<2.0
    django20: Django>=2.0,<2.1
    django21: https://github.com/django/django/archive/master.tar.gz


text:
Django>=1.8
Sphinx==3.0.4


text:
"""Python packaging information."""

from os.path import abspath, dirname, join, normpath

from setuptools import setup

from sslify import __version__

setup(

    # Basic package information:
    name = 'django-sslify',
    version = __version__,

    packages = ['sslify'],

    # Packaging options:
    zip_safe = False,
    include_package_data = True,

    # Package dependencies:
    install_requires = ['Django>=1.0'],

    # Metadata for PyPI:
    author = 'Randall Degges',
    author_email = 'r@rdegges.com',
    license = 'UNLICENSE',
    url = 'https://github.com/rdegges/django-sslify',
    keywords = 'django ssl https middleware',
    description = 'Force SSL on your Django site.',
    classifiers = [
        'Development Status :: 5 - Production/Stable',
        'Environment :: Web Environment',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'License :: Public Domain',
        'Natural Language :: English',
        'Operating System :: OS Independent',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.2',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: Implementation',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: Implementation :: PyPy',
        'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ],
    long_description = open(normpath(join(dirname(abspath(__file__)), 'README.rst'))).read(),

)


text:
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = build

# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source

.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext

help:
	@echo "Please use \`make <target>' where <target> is one of"
	@echo "  html       to make standalone HTML files"
	@echo "  dirhtml    to make HTML files named index.html in directories"
	@echo "  singlehtml to make a single large HTML file"
	@echo "  pickle     to make pickle files"
	@echo "  json       to make JSON files"
	@echo "  htmlhelp   to make HTML files and a HTML help project"
	@echo "  qthelp     to make HTML files and a qthelp project"
	@echo "  devhelp    to make HTML files and a Devhelp project"
	@echo "  epub       to make an epub"
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
	@echo "  text       to make text files"
	@echo "  man        to make manual pages"
	@echo "  texinfo    to make Texinfo files"
	@echo "  info       to make Texinfo files and run them through makeinfo"
	@echo "  gettext    to make PO message catalogs"
	@echo "  changes    to make an overview of all changed/added/deprecated items"
	@echo "  xml        to make Docutils-native XML files"
	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
	@echo "  linkcheck  to check all external links for integrity"
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"

clean:
	rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

singlehtml:
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
	@echo
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-sslify.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-sslify.qhc"

devhelp:
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
	@echo
	@echo "Build finished."
	@echo "To view the help file:"
	@echo "# mkdir -p $$HOME/.local/share/devhelp/django-sslify"
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-sslify"
	@echo "# devhelp"

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
	@echo
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
	      "(use \`make latexpdf' here to do that automatically)."

latexpdf:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through pdflatex..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

latexpdfja:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through platex and dvipdfmx..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

text:
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
	@echo
	@echo "Build finished. The text files are in $(BUILDDIR)/text."

man:
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
	@echo
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

texinfo:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo
	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
	@echo "Run \`make' in that directory to run these through makeinfo" \
	      "(use \`make info' here to do that automatically)."

info:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo "Running Texinfo files through makeinfo..."
	make -C $(BUILDDIR)/texinfo info
	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."

gettext:
	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
	@echo
	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."

xml:
	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
	@echo
	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."

pseudoxml:
	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
	@echo
	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."


text:
.. django-sslify documentation master file, created by
   sphinx-quickstart on Mon Dec 29 22:42:22 2014.
   You can adapt this file completely to your liking, but it should at least
   contain the root `toctree` directive.

Welcome to django-sslify's documentation!
=========================================

Contents:

.. toctree::
   :maxdepth: 2

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


text:
# -*- coding: utf-8 -*-
#
# django-sslify documentation build configuration file, created by
# sphinx-quickstart on Mon Dec 29 22:42:22 2014.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

from os.path import abspath
from sys import path

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
path.insert(0, abspath('../..'))

from sslify import __version__

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.viewcode',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'django-sslify'
copyright = u'2014, Randall Degges'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = __version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'django-sslifydoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  ('index', 'django-sslify.tex', u'django-sslify Documentation',
   u'Randall Degges', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'django-sslify', u'django-sslify Documentation',
     [u'Randall Degges'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'django-sslify', u'django-sslify Documentation',
   u'Randall Degges', 'django-sslify', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False


text:
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    # Examples:
    # url(r'^$', 'test_project.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    url(r'^admin/', admin.site.urls),
]


text:
import logging
try:
    # Django 1.9
    from django.template import TemplateDoesNotExist
except Exception as e:
    # Django >= 1.8
    from django.template.base import TemplateDoesNotExist
    logging.exception(e)
try:
    # Django >= 1.8
    # https://docs.djangoproject.com/en/1.8/releases/1.8/#django-template-loader-baseloader
    from django.template.loaders.base import Loader as BaseLoader
except Exception as e:
    from django.template.loader import BaseLoader
    logging.exception(e)

class TestLoader(BaseLoader):
    is_usable = True

    def load_template_source(self, template_name, template_dirs=None):
        if template_name == '404.html':
            return '404 error', None
        else:
            raise TemplateDoesNotExist(template_name)


text:
"""
Django settings for test_project project.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from django import VERSION
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'df^bdzeh^h@pq)0+nj*bvwm67oew$pr6ubmzw$s%b#ch)$g=8h'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'sslify',
)

MIDDLEWARE = (
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

if VERSION < (1, 10):
    MIDDLEWARE_CLASSES = MIDDLEWARE

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

ROOT_URLCONF = 'test_project.urls'

WSGI_APPLICATION = 'test_project.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/

STATIC_URL = '/static/'


text:
"""
WSGI config for test_project project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()


text:
"""Django middlewares."""

try:
    # Python 2.x
    from urlparse import urlsplit, urlunsplit
except ImportError:
    # Python 3.x
    from urllib.parse import urlsplit
    from urllib.parse import urlunsplit

from django.conf import settings
from django.http import HttpResponsePermanentRedirect

try:
    # Django 1.10
    from django.utils.deprecation import MiddlewareMixin
except ImportError:
    # Django <1.10
    class MiddlewareMixin(object):
        def __init__(self, get_response=None):
            self.get_response = get_response
            super(MiddlewareMixin, self).__init__()

        def __call__(self, request):
            response = None
            if hasattr(self, 'process_request'):
                response = self.process_request(request)
            if not response:
                response = self.get_response(request)
            if hasattr(self, 'process_response'):
                response = self.process_response(request, response)
            return response

class SSLifyMiddleware(MiddlewareMixin):
    """Force all requests to use HTTPs. If we get an HTTP request, we'll just
    force a redirect to HTTPs.

    .. note::
        You can also disable this middleware when testing by setting
        ``settings.SSLIFY_DISABLE`` to True.
    """
    @staticmethod
    def process_request(request):
        # If the user has explicitly disabled SSLify, do nothing.
        if getattr(settings, 'SSLIFY_DISABLE', False):
            return None

        # Evaluate callables that can disable SSL for the current request
        per_request_disables = getattr(settings, 'SSLIFY_DISABLE_FOR_REQUEST', [])
        for should_disable in per_request_disables:
            if should_disable(request):
                return None

        # If we get here, proceed as normal.
        if not request.is_secure():
            url = request.build_absolute_uri(request.get_full_path())
            url_split = urlsplit(url)
            scheme = 'https' if url_split.scheme == 'http' else url_split.scheme
            ssl_port = getattr(settings, 'SSLIFY_PORT', 443)
            url_secure_split = (scheme, "%s:%d" % (url_split.hostname or '', ssl_port)) + url_split[2:]
            secure_url = urlunsplit(url_secure_split)

            return HttpResponsePermanentRedirect(secure_url)


text:
"""django-sslify package information."""

VERSION = (0, 2, 8)

def get_version():
    """returns a pep compliant version number"""
    return '.'.join(str(i) for i in VERSION)

__version__ = get_version()


text:
"""Tests."""

try:
    # Python 2.x
    from urlparse import urlsplit
except ImportError:
    # Python 3.x
    from urllib.parse import urlsplit

from django.http import HttpResponsePermanentRedirect
from django.test import TestCase
from django.test.client import RequestFactory

from sslify.middleware import SSLifyMiddleware

class SSLifyMiddlewareTest(TestCase):

    def setUp(self):
        self.factory = RequestFactory()

    def test_perma_redirects_http_to_https(self):
        request = self.factory.get('/woot/')
        self.assertTrue(request.build_absolute_uri().startswith('http://'))

        middleware = SSLifyMiddleware
        result = middleware.process_request(request)

        self.assert_https_redirect(result)
        self.assertEqual(443, urlsplit(result['Location']).port)

    def test_custom_ssl_port(self):
        custom_port = 8443
        with self.settings(SSLIFY_PORT=custom_port):
            request = self.factory.get('/woot/')
            middleware = SSLifyMiddleware
            request = middleware.process_request(request)

            self.assertEqual(custom_port, urlsplit(request['Location']).port)

    def test_disable_for_tests(self):
        with self.settings(SSLIFY_DISABLE=True):
            request = self.client.get('/woot/')
            self.assertEqual(404, request.status_code)

    def test_disable_for_url(self):
        def sslify_disable(request):
            return request.get_full_path().startswith('/disabled')

        with self.settings(SSLIFY_DISABLE_FOR_REQUEST=[sslify_disable]):
            middleware = SSLifyMiddleware
            request = self.factory.get('/disabled/')
            request = middleware.process_request(request)
            self.assertIsNone(request)

            request = self.factory.get('/enabled/')
            result = middleware.process_request(request)
            self.assert_https_redirect(result)

    def assert_https_redirect(self, result):
        self.assertIsInstance(result, HttpResponsePermanentRedirect)
        self.assertTrue(result['Location'].startswith('https://'))

    def tearDown(self):
        del self.factory


text:
import numpy as np
from os.path import splitext
from os.path import basename
from scipy.optimize import minimize
import maya.cmds as cmds
import maya.mel as mel
import math

from Qt import QtCore, QtWidgets
from maya import OpenMayaUI as omui

if int(cmds.about(v = True)) < 2017:
    from shiboken import wrapInstance
else:
    from shiboken2 import wrapInstance

mayaMainWindowPtr = omui.MQtUtil.mainWindow()
mayaMainWindow = wrapInstance(long(mayaMainWindowPtr), QtWidgets.QWidget)

class dm2skin_UI(QtWidgets.QDialog):
    """Builds the GUI"""

    def __init__(self, parent=mayaMainWindow):
        super(dm2skin_UI, self).__init__(parent)

        self.setWindowTitle('dm2skin')

        self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
        self.setFixedHeight(175)

        self.setLayout(QtWidgets.QVBoxLayout())
        self.layout().setAlignment(QtCore.Qt.AlignTop)

        self.setLayout(QtWidgets.QVBoxLayout())
        self.layout().setContentsMargins(7, 7, 7, 7)
        self.layout().setSpacing(5)

        self.sourceLayout = QtWidgets.QVBoxLayout()
        self.layout().addLayout(self.sourceLayout)

        self.sourceFieldLayout = QtWidgets.QHBoxLayout()
        self.sourceLayout.addLayout(self.sourceFieldLayout)

        self.sourceText = QtWidgets.QLabel('Source Mesh:')
        self.sourceField = QtWidgets.QLineEdit()
        self.loadSourceButton = QtWidgets.QPushButton('<<')
        self.loadSourceButton.clicked.connect(self.setSourceMesh)
        self.createMushButton = QtWidgets.QPushButton('Create Mush')
        self.createMushButton.clicked.connect(self.createMush)

        self.sourceFieldLayout.addWidget(self.sourceText)
        self.sourceFieldLayout.addWidget(self.sourceField)
        self.sourceFieldLayout.addWidget(self.loadSourceButton)

        self.sourceLayout.addWidget(self.createMushButton)

        self.displayLayout = QtWidgets.QHBoxLayout()
        self.toggleMeshButton = QtWidgets.QPushButton('Toggle Mesh Visibility')
        self.toggleMushButton = QtWidgets.QPushButton('Toggle Mush Visibility')
        self.toggleMeshButton.clicked.connect(self.toggleMeshVisibility)
        self.toggleMushButton.clicked.connect(self.toggleMushVisbility)
        self.displayLayout.addWidget(self.toggleMeshButton)
        self.displayLayout.addWidget(self.toggleMushButton)

        self.layout().addLayout(self.displayLayout)

        self.infsLayout = QtWidgets.QHBoxLayout()
        self.maxInfsLabel = QtWidgets.QLabel('Max Influences:')
        self.maxInfsLabel.setFixedWidth(83)
        self.maxInfsSpinBox = QtWidgets.QSpinBox()
        self.maxInfsSpinBox.setFixedWidth(50)
        self.maxInfsSpinBox.setMinimum(2)
        self.maxInfsSpinBox.setValue(4)
        self.transferButton = QtWidgets.QPushButton('Transfer To Skin')

        self.infsLayout.addWidget(self.maxInfsLabel)
        self.infsLayout.addWidget(self.maxInfsSpinBox)
        self.infsLayout.addWidget(self.transferButton)
        self.layout().addLayout(self.infsLayout)

        self.transferButton.clicked.connect(self.startWeightTransfer)

        self.transferLayout = QtWidgets.QVBoxLayout()
        self.deleteMushButton = QtWidgets.QPushButton('Delete Mush')
        self.deleteMushButton.clicked.connect(self.deleteMush)

        self.progressBar = QtWidgets.QProgressBar()

        self.layout().addLayout(self.transferLayout)
        self.transferLayout.addWidget(self.deleteMushButton)
        self.transferLayout.addWidget(self.progressBar)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

    def toggleMeshVisibility(self):
        mesh = self.sourceField.text()
        if not mesh:
            return
        if not cmds.objExists(mesh):
            return
        try:
            cmds.setAttr(mesh + '.visibility', not bool(cmds.getAttr(mesh + '.visibility')))
        except Exception as e:
            print(e)

    def toggleMushVisbility(self):
        mush = self.sourceField.text()
        if not mush:
            return
        mush += '_Mush'
        if not cmds.objExists(mush):
            return
        try:
            cmds.setAttr(mush + '.visibility', not bool(cmds.getAttr(mush + '.visibility')))
        except Exception as e:
            print(e)

    def setSourceMesh(self):
        sel = cmds.ls(sl=True, type='transform')
        if not sel:
            return
        childMeshes = cmds.listRelatives(sel[0], c=True, type='mesh')
        if not childMeshes:
            return
        self.sourceField.setText(sel[0])

        maxInfs = dm2skin_getMaxInfluences(sel[0])
        self.maxInfsSpinBox.setMaximum(maxInfs)
        return True

    def createMush(self):
        mesh = self.sourceField.text()
        if not mesh:
            return
        if cmds.objExists(mesh + '_Mush'):
            print(mesh + '_Mush already exists!')
            return
        cmds.currentTime(cmds.playbackOptions(q=True, min=True))
        dup = cmds.duplicate(mesh, inputConnections=True, n=mesh + '_Mush')
        cmds.deltaMush(dup, smoothingIterations=20, smoothingStep=0.5, pinBorderVertices=True, envelope=1)

    def deleteMush(self):
        mesh = self.sourceField.text()
        if not mesh:
            return
        if not cmds.objExists(mesh + '_Mush'):
            return
        cmds.delete(mesh + '_Mush')

    def startWeightTransfer(self):
        mesh = self.sourceField.text()
        if not mesh:
            return
        if not (cmds.objExists(mesh) or cmds.objExists(mesh + '_Mush')):
            print('Missing mesh or mush!')
            return
        dm2skin_doMushOptimization(mesh, mushMesh=mesh + '_Mush', maxInfluences=int(self.maxInfsSpinBox.value()), progressBar=self.progressBar)

def dm2skin_getMatrices(joints, matrixString=".worldMatrix"):
    """Returns a list of numpy arrays representing the transform
    matrices of the given list of joints."""
    retMatrices = []
    for j in joints:
        tempMatrix = cmds.getAttr(j + matrixString)
        trfMatrix = np.array([      [tempMatrix[0], tempMatrix[1], tempMatrix[2], tempMatrix[3]],
                                    [tempMatrix[4], tempMatrix[5], tempMatrix[6], tempMatrix[7]],
                                    [tempMatrix[8], tempMatrix[9], tempMatrix[10], tempMatrix[11]],
                                    [tempMatrix[12], tempMatrix[13], tempMatrix[14], tempMatrix[15]]
                            ])
        retMatrices.append(trfMatrix)
    return retMatrices

def dm2skin_getMatricesOverRange(joints, matrixString='.worldMatrix', startFrame=0, endFrame=1):
    """Gets a list of lists of transform matrices for the given joints. One
    list for each frame between startFrame and endFrame."""
    resultList = []
    for i in range(startFrame, endFrame + 1):
        cmds.currentTime(i)
        resultList.append(dm2skin_getMatrices(joints, matrixString=matrixString))
    return resultList

def dm2skin_getVertexPositionsOverRange(mesh, startFrame=0, endFrame=1):
    """Gets a list of lists of vertex positions for the given mesh. One list for
    each frame between startFrame and endFrame."""
    numVerts = cmds.polyEvaluate(mesh, v=True)
    resultList = []
    for i in range(startFrame, endFrame + 1):
        tempList = []
        cmds.currentTime(i)
        for j in range(0, numVerts):
            tempPos = cmds.xform(mesh + '.vtx[' + str(j) + ']', q=True, ws=True, t=True)
            tempList.append(np.array([tempPos[0], tempPos[1], tempPos[2]]))
        resultList.append(tempList)
    return resultList

def dm2skin_getVertexLocationList(mesh, frame=0):
    """Gets a list of vertex locations on the given frame."""
    numVerts = cmds.polyEvaluate(mesh, v=True)
    resultList = []
    cmds.currentTime(frame)
    for v in range(0, numVerts):
        pos = cmds.xform(mesh + '.vtx[' + str(v) + ']', q=True, ws=True, t=True)
        resultList.append(np.array([pos[0], pos[1], pos[2], 1.0]))
    return resultList

def dm2skin_getLargestInfluenceOnVert(vertex, skinCluster=None):
    """Given a vertex returns the largest influence in the provided
    skin cluster that acts upon it."""
    if not skinCluster:
        return False

    vertInfs = cmds.skinCluster(skinCluster, q=True, inf=True)
    vertVals = cmds.skinPercent(skinCluster, vertex, q=True, value=True)
    return vertInfs[vertVals.index(max(vertVals))]

def dm2skin_getNeighbouringJoints(joint, vertexString=None, cluster=None, influences=3):
    """This gets a list of nearby joints in the skin cluster to joint up to
    the number of influences. These will be the ones we use in our minimization
    later"""

    if not cmds.objExists(joint):
        return False
    if influences < 3:
        return False
    if not cluster:
        return False

    clusterJoints = cmds.skinCluster(cluster, q=True, inf=True)

    pos1 = cmds.xform(vertexString, q=True, ws=True, t=True)

    parentJoint = cmds.listRelatives(joint, parent=True)

    subtract = 1
    # add the main joint
    resultList = [joint]
    # i've found it works best to always include the parent
    if parentJoint and parentJoint in clusterJoints:
        resultList.insert(0, parentJoint[0])
        subtract = 2

    # for the rest of the available influences get a list of nearby joints in space
    measureList = []
    for measureJnt in clusterJoints:
        if measureJnt not in resultList:
            jntPos2 = cmds.xform(measureJnt, q=True, ws=True, t=True)
            #this just gets the length of the vector between the two joints
            dist = math.sqrt(reduce(lambda x, y: x + y, [math.pow(jntPos2[i] - pos1[i], 2) for i in range(len(pos1))]))
            measureList.append((measureJnt, dist))

    # sort the list in ascending order so we get the closest joints first
    measureList.sort(key=lambda dist: dist[1])
    ascendingList = [entry[0] for entry in measureList[0:influences - subtract]]
    return resultList + ascendingList

def dm2skin_computeSourceMushDistance(weights, sourceMesh, targetMesh, index, bindVerts, mushVerts, bindInvMatrices, transMatrices, joints):
    """Computes the distance between a vertex in the source mesh and target mesh. Ultimately this
    distance is what we use scipy to minimize."""
    totalDist = 0
    vert = index
    for frame in range(len(mushVerts)):
        bindVertPos = bindVerts[vert]
        mushedPos = mushVerts[frame][vert]
        transformedPos = dm2skin_transformPoint(bindVertPos, joints=joints, weights=weights, inverseMatrices=bindInvMatrices, transformMatrices=transMatrices[frame])
        totalDist += np.linalg.norm(transformedPos - mushedPos)
    return totalDist

def dm2skin_transformPoint(bindPosition, joints=[], weights=[], inverseMatrices=[], transformMatrices=[]):
    """Given the bind pose position of a vertex computes the eventual position of the
    vertex once it has had the standard linear skinning algorithm applied"""
    finalPos = np.array(np.zeros(4))
    for i in range(len(joints)):
        inversePos = np.dot(bindPosition, inverseMatrices[i])
        finalPos += weights[i] * np.dot(inversePos, transformMatrices[i])
    return finalPos[:3]

def dm2skin_normalizeWeightsConstraint(x):
    """Constraint used in optimization that ensures
    the weights in the solution sum to 1"""
    return sum(x) - 1.0

def dm2skin_getMaxInfluences(mesh):
    """Finds a skin cluster on the given mesh and returns
    the number of influences it is set to have."""
    skinClusterStr = 'findRelatedSkinCluster("' + mesh + '")'
    skinCluster = mel.eval(skinClusterStr)
    if not skinCluster:
        return 0
    allInfluences = cmds.skinCluster(skinCluster, q=True, inf=True)
    return len(allInfluences)

def dm2skin_getNumberOfNonZeroElements(list):
    """Returns the number of non-zero elements in the given list"""
    return int(sum([1 for x in list if x != 0.0]))

def dm2skin_doMushOptimization(mesh, mushMesh=None, maxInfluences=4, progressBar=None):
    """Does the actual job of solving the minimization problem."""

    if not mushMesh:
        return False

    minTime = cmds.playbackOptions(q=True, min=True)
    maxTime = cmds.playbackOptions(q=True, max=True)
    # assume the first frame is the bind pose
    cmds.currentTime(minTime)

    skinClusterStr = 'findRelatedSkinCluster("' + mesh + '")'
    skinCluster = mel.eval(skinClusterStr)
    if not skinCluster:
        return False

    allInfluences = cmds.skinCluster(skinCluster, q=True, inf=True)
    numVerts = cmds.polyEvaluate(mesh, v=True)

    invMatrices = dm2skin_getMatrices(allInfluences, matrixString='.worldInverseMatrix')
    transMatrices = dm2skin_getMatricesOverRange(allInfluences, matrixString='.worldMatrix', startFrame=int(minTime) + 1, endFrame=int(maxTime))

    bindVertList = dm2skin_getVertexLocationList(mesh, frame=int(minTime))
    mushedVertList = dm2skin_getVertexPositionsOverRange(mushMesh, startFrame=int(minTime) + 1, endFrame=int(maxTime))

    # if there is a progress bar provided, set the maximum value to
    # the total number of verts
    if progressBar:
        progressBar.setMaximum(numVerts)

    setWeightList = []

    # reset to the first frame again before we start
    cmds.currentTime(minTime)
    for i in range(numVerts):

        if progressBar:
            progressBar.setValue(i)

        largestInf = dm2skin_getLargestInfluenceOnVert(mesh + '.vtx[' + str(i) + ']', skinCluster)
        vertInfluences = dm2skin_getNeighbouringJoints(largestInf, vertexString=mesh +'.vtx[' +str(i) +']', cluster=skinCluster, influences=maxInfluences)

        if not vertInfluences:
            print("Error getting neighbour joints")
            progressBar.setValue(0)
            return

        properInfIndices = [allInfluences.index(inf) for inf in vertInfluences]

        currentInvMatrices = []
        currentTransMatrices = []

        currentInvMatrices = [invMatrices[pi] for pi in properInfIndices]

        for t in range(len(transMatrices)):
            currentTransMatrices.append([transMatrices[t][ind] for ind in properInfIndices])

        boundsList = []
        startValList = []
        for j in range(len(vertInfluences)):
            boundsList.append((0, 1))
            startValList.append(1.0 / len(vertInfluences))

        cons = ({'type': 'eq', 'fun': dm2skin_normalizeWeightsConstraint})
        result = minimize(dm2skin_computeSourceMushDistance, startValList, method='SLSQP', args=(mushMesh, mesh, i, bindVertList, mushedVertList, currentInvMatrices, currentTransMatrices, vertInfluences), constraints=cons, bounds=boundsList)
        tempList = []
        for j in range(len(vertInfluences)):
            tempList.append((vertInfluences[j], np.around(result.x[j], 3)))
        setWeightList.append(tempList)

    # open an undo chunk
    cmds.undoInfo(openChunk=True)

    # lock the allInfluences, then we can change the max influences on
    # the skin cluster
    for inf in allInfluences:
        cmds.skinCluster(skinCluster, e=True, inf=inf, lw=True)

    # the scipy solve might not neccesarily have used all the
    # available influences, so this block checks through each
    # set of weights and finds the highest number of non zero
    # ones
    maxInfsUsed = 0
    for i in range(numVerts):
        vertInfVals = [setWeightList[i][j][1] for j in range(len(setWeightList[i]))]
        numNonZero = dm2skin_getNumberOfNonZeroElements(vertInfVals)
        if numNonZero > maxInfsUsed:
            maxInfsUsed = numNonZero

    # set the new number of influences on the skin cluster and
    # unlock the influences
    cmds.skinCluster(skinCluster, e=True, mi=maxInfsUsed)
    for inf in allInfluences:
        cmds.skinCluster(skinCluster, e=True, inf=inf, lw=False)

    # set the new weights
    for i in range(numVerts):
        cmds.skinPercent(skinCluster, mesh +'.vtx[' + str(i) + ']', normalize=True, relative=False, zri=True, transformValue=setWeightList[i])

    # close undo chunk and reset progress bar
    cmds.undoInfo(closeChunk=True)

    if progressBar:
        progressBar.setValue(0)

def deleteInstances():
    fileName = splitext(basename(__file__))[0]
    for obj in mayaMainWindow.children():
        if(str(type(obj)) == "<class '{0}.dm2skin_UI'>".format(fileName)):
            if(obj.__class__.__name__ == "dm2skin_UI"):
                obj.setParent(None)
                obj.deleteLater()

def dm2skin():
    deleteInstances()
    ui = dm2skin_UI()
    ui.show()


text:
# dm2skin
A Python script for Maya that allows you to convert the results of a delta-mush deformer to standard skin weights.

You provide the tool with a skinned mesh (with one influence per vertex), a version of the mesh with the delta-mush deformer applied and a set of keyframed poses. For each vertex the script does some mathematical optimization in order to figure out the influence weights that will minimize the the total distance (summed over the poses provided) between the skinned vertex and its mushed partner. Effectively this makes the skinned model as "similar" as possible to the delta-mushed version.

It uses NumPy + SciPy to perform the minimization and should work on Maya 2016. Older Maya versions don't have the Delta-Mush deformer.

I've put a little demo video on Vimeo [here](https://vimeo.com/181910645).

# Note

The script relies on Numpy and Scipy, but you have to use a specific version that is built to work with Maya's Python interpreter. This is included in the **dependencies** directory. I got these builds online from a guy who I forget the name of. I'll update this with his name and a link at some point.

# Installation

Clone the repository using the following command in Git Bash.

```bash
git clone https://github.com/duncanskertchly/dm2skin.git
```

Alternatively there is a little green button on the projects front page that will let you download it as a zipped file.

From the **dependencies** directory extract the two 7-Zip files to Mayas Python libraries directory. On Windows this is usually.

> C:\\Program Files\\Autodesk\\Maya2016\\Python\\Lib\\site-packages

So ultimately you should have these new directories.

> C:\\Program Files\\Autodesk\\Maya2016\\Python\\Lib\\site-packages\\numpy

> C:\\Program Files\\Autodesk\\Maya2016\\Python\\Lib\\site-packages\\scipy

From the dependencies folder copy **Qt.py** to your Maya scripts folder and from the root copy **dm2skin.py** to the same. Usually your script folder is something like this.

> C:\\Users\\\<User\>\\Documents\\maya\\2016\\scripts

Re-boot Maya and test the install using the little bit of code in step 3 of the **How To Use** section below.

# How To Use

1. Skin your mesh to its skeleton using skinning options similar to the ones below. It's important that initially you use only one influence per vertex.

	![](./images/skin_options.png)

2. Paint your skin weights so that each part of your mesh is deformed reasonably (despite only having one weight per vert). Obviously it'll look pretty terrible, but if you make it as tidy as you can the delta mushed version will look better later.

3. Open the dm2skin UI using the following code.

	```python
	import dm2skin
	dm2skin.dm2skin()
	```

	A GUI should appear that looks like this.

	![](./images/gui.png)

4. Select your newly skinned mesh and hit the **<<** button. Your mesh will be added to the **Source Mesh** field on the left.

5. Hit **Create Mush**. A new version of your mesh should be created called **\<YourMesh\>\_Mush**.

6. Keyframe a set of extreme poses making sure to leave the first frame on your timeline as the **bind pose**. I have found 4 / 5 poses works well. Theses poses should move and rotate the characters bones in to all the kinds of poses that your character will need to achieve. You need to provide plenty of information to the minimization algorithm to get decent results.

7. Select this mesh and tweak the **deltaMush** deformer to give the results you are after. You can use the **Toggle Mesh Visibility** and **Toggle Mush Visibility** buttons in the GUI to easily hide and show the two meshes.

8. When you're happy set the **Max Influences** value which corresponds to the maximum number of influences that will be used for your skin cluster once the conversion has completed. In my experiments I've found 4 to be about the lowest you'll want to go to give decent results.

9. Hit **Transfer To Skin**.

10. Wait while the tool does its work. The conversion process will take longer the more verts that are present in the mesh. Increasing the **Max Influences** value will also increase the time the process takes. A 10k vert mesh using 5 influences takes around 2 minutes on my work PC.

11. Once the process finishes you can have a look at the result. If you're not happy just hit undo and the weights will go back to your previous rigid weights.

12. If you are happy hit **Delete Mush** and do any tweaks to the weights that you think are necessary.

# Getting Good Results

* I have found that the best way to improve the results of the skin transfer is to make adjustments to the keyframed poses you provide. Not the number (4 / 5 is usually fine), but the poses themselves. For instance if you find that the shoulder / upper arm vertices on your humanoid character don't deform nicely when you twist the upper arm, undo the transfer and go and put some twist in to the upper arms on a few of your poses and re-run the transfer. Remember it can only optimize the weights from the information you provide.

* On each pose try and move or rotate every bone. For instance if you've got finger bones that you leave stationary in all your poses the optimization algorithm may well conclude that it can just bind those vertices to the wrist joint. It doesn't know any better unless you tell it. Put lots of rotation on them to ensure the algorithm doesn't reach those kind of strange conclusions.

* I've noticed that the optimization process will sometimes put a very small amount of weight on joints that don't make too much sense. For instance you may find that the neck very slightly influences vertices around the chest area and that type of thing. It's just an artefact of the numerical optimization. In this instance I'd usually just use Maya's __Prune Small Weights__ tool until that influence disappears.

* The more influences you allow the better the result will be. Lower influence counts may require a bit more tweaking, but will usually be a very good starting point for your skinning.

# Example Files

There two Maya files in the **examples** folder which give you an idea of the before and after results. The **test_done.ma** file is a warts and all result of the tool, I've not done any tweaking to the skinning. Looking at the **test_start.ma** is a quite useful illustration of some of the points I've made in the previous section.