Not quite as easily solvable with software as you might think. Most such scheduling problems are NP-hard.
The problem is that solutions are always a little bit ill-defined and won't be 100% captured by any generic software model, and that they need to be amenable to further semi-manual tweaking.
I think an overall approach would be to present a number of schedules that could work, and expect the user to do some manual adjustments. Perhaps present a schedule that could work, user checks off which classes to keep, and program shuffles other classes. Repeat until done. I think that's basically the approach people use when they do it manually.